from __future__ import annotations import csv import hashlib import json import re from datetime import datetime, timezone, timedelta from pathlib import Path FINAL_RUN_ID = "RUN-ANA-WUJI-V1-STRICT-NOTE-FINAL-CONCLUSION-20260615-001" SOURCE_RUN_ID = "RUN-ANA-WUJI-V1-STRICT-NOTE-FULL-RERUN-20260614-001" ROOT = Path(__file__).resolve().parents[1] PROJECT_ROOT = ROOT.parents[2] SOURCE = PROJECT_ROOT / "ana-data" / "result" / SOURCE_RUN_ID TZ = timezone(timedelta(hours=8)) SOURCE_EXEC_AUDIT_ID = "AUDIT-ANA-WUJI-V1-STRICT-NOTE-FULL-RERUN-20260615-EXEC-001" MOJIBAKE_RE = re.compile(r"\?{3,}|\ufffd|����|À|Ã|Â|澶|鍙|鎬|涓|蹇|瑙|鏃|鐐|甯|瀹|鍚屾剰|鎸夎") OVERREAD_RE = re.compile(r"(? list[dict[str, str]]: with path.open("r", encoding="utf-8-sig", newline="") as f: return list(csv.DictReader(f)) def write_csv(path: Path, rows: list[dict[str, object]], fields: list[str]) -> None: path.parent.mkdir(parents=True, exist_ok=True) with path.open("w", encoding="utf-8-sig", newline="") as f: writer = csv.DictWriter(f, fieldnames=fields) writer.writeheader() for row in rows: writer.writerow({k: row.get(k, "") for k in fields}) def write_text(path: Path, text: str) -> None: path.parent.mkdir(parents=True, exist_ok=True) path.write_text(text, encoding="utf-8") def sha256_file(path: Path) -> str: h = hashlib.sha256() with path.open("rb") as f: for chunk in iter(lambda: f.read(1024 * 1024), b""): h.update(chunk) return h.hexdigest() def rel_to_project(path: Path) -> str: return path.relative_to(PROJECT_ROOT).as_posix() def rel_to_root(path: Path) -> str: return path.relative_to(ROOT).as_posix() def main() -> None: generated_at = datetime.now(TZ).isoformat(timespec="seconds") source_summary = json.loads((SOURCE / "strict_note_execution_summary.json").read_text(encoding="utf-8")) case_rows = read_csv(SOURCE / "strict_note_case_summary.csv") boundary_rows = read_csv(SOURCE / "strict_note_boundary_table.csv") readouts = [ { "readout_id": "STRICT_NOTE_PRIMARY_STRICT_CLOSED_CASE", "value": source_summary["primary_strict_closed_case_count"], "unit": "case", "meaning_cn": "严格笔记版主口径严格闭合 case 数;只包含已严格 BUY 且 lot 全部由外部卖点裁决 SELL 闭合、无未闭合滚动低吸边界的 case。", "source_path": rel_to_project(SOURCE / "strict_note_execution_summary.json"), }, { "readout_id": "STRICT_NOTE_PRIMARY_POSITIVE_CASE", "value": source_summary["primary_positive_case_count"], "unit": "case", "meaning_cn": "严格笔记版主口径中账户贡献为正的 case 数。", "source_path": rel_to_project(SOURCE / "strict_note_case_summary.csv"), }, { "readout_id": "STRICT_NOTE_PRIMARY_SUCCESS_RATE", "value": source_summary["primary_success_rate_readout"], "unit": "ratio", "meaning_cn": "严格笔记版主口径成功率读数,等于正收益主口径 case / 主口径严格闭合 case。", "source_path": rel_to_project(SOURCE / "strict_note_execution_summary.json"), }, { "readout_id": "STRICT_NOTE_PRIMARY_ACCOUNT_CONTRIBUTION", "value": source_summary["primary_account_contribution_readout"], "unit": "account_contribution", "meaning_cn": "严格笔记版主口径账户贡献合计读数。", "source_path": rel_to_project(SOURCE / "strict_note_execution_summary.json"), }, { "readout_id": "STRICT_NOTE_ALL_BUY_CASE_COVERAGE", "value": source_summary["case_count"], "unit": "case", "meaning_cn": "严格笔记版有 strict BUY 的 case 覆盖数;不替代主成功率分母。", "source_path": rel_to_project(SOURCE / "strict_note_case_summary.csv"), }, { "readout_id": "STRICT_NOTE_BOUNDARY_RECORDS", "value": source_summary["boundary_records"], "unit": "record", "meaning_cn": "严格笔记版边界记录数,包含市场闸门关闭、买点待审、分钟缺口、HOLD、REVIEW_HELD 和滚动低吸未闭合边界。", "source_path": rel_to_project(SOURCE / "strict_note_boundary_table.csv"), }, ] write_csv(ROOT / "strict_note_final_readouts.csv", readouts, ["readout_id", "value", "unit", "meaning_cn", "source_path"]) case_index_fields = [ "case_id", "entry_trade_date", "signal_trade_date", "symbols", "case_scope_status", "primary_strict_closed_case_flag", "net_account_contribution", "positive_case_flag", "strict_buy_orders", "sell_orders", "rolling_low_buy_orders", "boundary_lots", "case_image_board_path", "case_story_board_path", ] write_csv(ROOT / "strict_note_case_readout_index.csv", case_rows, case_index_fields) write_csv(ROOT / "strict_note_boundary_table.csv", boundary_rows, list(boundary_rows[0].keys()) if boundary_rows else ["boundary_id"]) summary = { "run_id": FINAL_RUN_ID, "source_run_id": SOURCE_RUN_ID, "generated_at": generated_at, "source_execution_audit_id": SOURCE_EXEC_AUDIT_ID, "strict_note_primary_strict_closed_case_count": source_summary["primary_strict_closed_case_count"], "strict_note_primary_positive_case_count": source_summary["primary_positive_case_count"], "strict_note_primary_success_rate_readout": source_summary["primary_success_rate_readout"], "strict_note_primary_account_contribution_readout": source_summary["primary_account_contribution_readout"], "strict_note_all_buy_case_coverage": source_summary["case_count"], "strict_note_boundary_records": source_summary["boundary_records"], "citation_policy": { "allowed": "可按严格笔记版主口径、审计 ID 和边界表分层引用。", "required_text": "该读数只覆盖严格笔记版已审核主口径,不构成买入建议,不证明策略有效性,不得写成无边界完整 baseline。", "must_cite": [ SOURCE_EXEC_AUDIT_ID, "strict_note_boundary_table.csv", ], }, } write_text(ROOT / "strict_note_final_conclusion_summary.json", json.dumps(summary, ensure_ascii=False, indent=2) + "\n") summary_md = f"""# 严格笔记版最终引用包摘要 来源执行包:`{SOURCE_RUN_ID}` 来源执行审核 ID:`{SOURCE_EXEC_AUDIT_ID}` ## 可引用读数 在严格笔记版主口径 `STRICT_NOTE_PRIMARY_STRICT_CLOSED_CASE` 下,{summary['strict_note_primary_strict_closed_case_count']} 个严格闭合 case 中 {summary['strict_note_primary_positive_case_count']} 个为正收益,成功率读数为 {summary['strict_note_primary_success_rate_readout']},账户贡献合计读数为 {summary['strict_note_primary_account_contribution_readout']}。 同时必须说明:严格笔记版有 strict BUY 的覆盖 case 为 {summary['strict_note_all_buy_case_coverage']} 个;边界记录为 {summary['strict_note_boundary_records']} 条,边界样本不得混入主口径。 ## 必须保留的边界 1. 本包不是买入建议。 2. 本包不证明策略有效性。 3. 本包不得写成无边界完整 baseline 成功率、收益率、胜率或回撤。 4. 引用时必须同时写明来源执行审核 ID、严格笔记版主口径和边界表。 5. 不得与旧 V1 宽口径 / 代理 BUY 池读数混用。 """ write_text(ROOT / "strict_note_final_conclusion_summary.md", summary_md) human_index = f"""# 严格笔记版最终引用包人工第一入口 本入口给同事和审核员快速判断:严格按《笔记精简版.md》重建 BUY 池并重放 V1 卖点 / 趋势止盈 / 滚动低吸后,哪些读数可以引用,哪些不能引用。 ## 可以引用 - 主口径严格闭合 case:{summary['strict_note_primary_strict_closed_case_count']} - 主口径正收益 case:{summary['strict_note_primary_positive_case_count']} - 主口径成功率读数:{summary['strict_note_primary_success_rate_readout']} - 主口径账户贡献读数:{summary['strict_note_primary_account_contribution_readout']} - 覆盖 case:{summary['strict_note_all_buy_case_coverage']},只说明覆盖,不替代主成功率分母 - 边界记录:{summary['strict_note_boundary_records']} ## 必须同时引用 - 来源执行审核 ID:`{SOURCE_EXEC_AUDIT_ID}` - 本最终引用包审核 ID:待审核员填写 - 边界表:[`strict_note_boundary_table.csv`](strict_note_boundary_table.csv) - 来源执行包入口:[`strict_note_human_review_index.md`](../{SOURCE_RUN_ID}/strict_note_human_review_index.md) ## 不能引用 - 不能写成买入建议。 - 不能写成策略有效性证明。 - 不能写成无边界完整 baseline 成功率、收益率、胜率或回撤。 - 不能和旧 V1 宽口径 / 代理 BUY 池读数混用。 ## 文件入口 - [`strict_note_final_conclusion_summary.md`](strict_note_final_conclusion_summary.md) - [`strict_note_final_readouts.csv`](strict_note_final_readouts.csv) - [`strict_note_case_readout_index.csv`](strict_note_case_readout_index.csv) - [`strict_note_boundary_table.csv`](strict_note_boundary_table.csv) - [`source_artifact_manifest.csv`](source_artifact_manifest.csv) - [`self_check_items.csv`](self_check_items.csv) """ write_text(ROOT / "strict_note_final_human_review_index.md", human_index) readme = f"""# {FINAL_RUN_ID} 严格笔记版最终引用包。 本包只整理已审核来源 `{SOURCE_RUN_ID}` 的读数,不重跑候选池、买点裁决、卖点裁决、滚动低吸或账户账本。 第一入口:`strict_note_final_human_review_index.md` """ write_text(ROOT / "README.md", readme) source_paths = [ SOURCE / "strict_note_execution_summary.json", SOURCE / "strict_note_case_summary.csv", SOURCE / "strict_note_boundary_table.csv", SOURCE / "strict_note_order_ledger.csv", SOURCE / "strict_note_position_lot_ledger.csv", SOURCE / "strict_note_human_review_index.md", SOURCE / "strict_note_execution_self_check.json", SOURCE / "strict_note_execution_manifest.csv", ] source_manifest = [] for path in source_paths: source_manifest.append({ "source_run_id": SOURCE_RUN_ID, "path": rel_to_project(path), "size": path.stat().st_size, "sha256": sha256_file(path), }) write_csv(ROOT / "source_artifact_manifest.csv", source_manifest, ["source_run_id", "path", "size", "sha256"]) scan_files = [ ROOT / "README.md", ROOT / "strict_note_final_human_review_index.md", ROOT / "strict_note_final_conclusion_summary.md", ROOT / "strict_note_final_conclusion_summary.json", ROOT / "strict_note_final_readouts.csv", ROOT / "strict_note_case_readout_index.csv", ROOT / "strict_note_boundary_table.csv", ] mojibake_hits = [] overread_hits = [] for path in scan_files: text = path.read_text(encoding="utf-8-sig") if MOJIBAKE_RE.search(text): mojibake_hits.append(rel_to_root(path)) if OVERREAD_RE.search(text): overread_hits.append(rel_to_root(path)) checks = [ {"check_id": "SOURCE_EXECUTION_PACKAGE_EXISTS", "status": "PASS" if SOURCE.exists() else "FAIL", "detail": rel_to_project(SOURCE)}, {"check_id": "READOUTS_MATCH_SOURCE_SUMMARY", "status": "PASS" if readouts[0]["value"] == source_summary["primary_strict_closed_case_count"] else "FAIL", "detail": "source summary copied"}, {"check_id": "BOUNDARY_TABLE_COPIED", "status": "PASS" if len(boundary_rows) == source_summary["boundary_records"] else "FAIL", "detail": str(len(boundary_rows))}, {"check_id": "SOURCE_ARTIFACT_HASHES_RECORDED", "status": "PASS" if len(source_manifest) == len(source_paths) else "FAIL", "detail": str(len(source_manifest))}, {"check_id": "NO_MOJIBAKE_IN_FINAL_PACKAGE", "status": "PASS" if not mojibake_hits else "FAIL", "detail": ";".join(mojibake_hits[:20])}, {"check_id": "NO_OVERREAD_CLAIMS", "status": "PASS" if not overread_hits else "FAIL", "detail": ";".join(overread_hits[:20])}, ] write_csv(ROOT / "self_check_items.csv", checks, ["check_id", "status", "detail"]) self_check = { "run_id": FINAL_RUN_ID, "stage": "PASS_FOR_STRICT_NOTE_FINAL_CONCLUSION_REVIEW_READY" if all(c["status"] == "PASS" for c in checks) else "FAIL", "generated_at": generated_at, "pass_count": sum(1 for c in checks if c["status"] == "PASS"), "fail_count": sum(1 for c in checks if c["status"] != "PASS"), } write_text(ROOT / "self_check.json", json.dumps(self_check, ensure_ascii=False, indent=2) + "\n") manifest_items = [ "README.md", "strict_note_final_human_review_index.md", "strict_note_final_conclusion_summary.md", "strict_note_final_conclusion_summary.json", "strict_note_final_readouts.csv", "strict_note_case_readout_index.csv", "strict_note_boundary_table.csv", "source_artifact_manifest.csv", "self_check_items.csv", "self_check.json", "tools/build_strict_note_final_conclusion_package.py", ] manifest = [] for item in manifest_items: path = ROOT / item manifest.append({"path": item, "size": path.stat().st_size, "sha256": sha256_file(path)}) write_csv(ROOT / "manifest.csv", manifest, ["path", "size", "sha256"]) write_text(ROOT / "manifest.json", json.dumps(manifest, ensure_ascii=False, indent=2) + "\n") print(json.dumps(summary, ensure_ascii=False, indent=2)) if __name__ == "__main__": main()