from __future__ import annotations import csv import hashlib import json from collections import Counter from datetime import datetime, timezone, timedelta from pathlib import Path PROJECT_ROOT = Path(__file__).resolve().parents[4] RUN_ID = "RUN-ANA-WUJI-V1-BUY-POINT-P3-RESOLUTION-20260616-001" SOURCE_RUN_ID = "RUN-ANA-WUJI-V1-BUY-POINT-SECOND-REVIEW-20260615-001" STRICT_RUN_ID = "RUN-ANA-WUJI-V1-STRICT-NOTE-FULL-RERUN-20260614-001" OUT_DIR = PROJECT_ROOT / "ana-data" / "result" / RUN_ID SOURCE_DIR = PROJECT_ROOT / "ana-data" / "result" / SOURCE_RUN_ID TZ = timezone(timedelta(hours=8)) def read_csv(path: Path) -> 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, str]], fieldnames: 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=fieldnames, extrasaction="ignore") writer.writeheader() writer.writerows(rows) 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(path: Path) -> str: return path.resolve().relative_to(OUT_DIR.resolve()).as_posix() def write_json(path: Path, data: dict) -> None: path.write_text(json.dumps(data, ensure_ascii=False, indent=2) + "\n", encoding="utf-8") def text_is_readable(path: Path) -> tuple[bool, str]: text = path.read_text(encoding="utf-8") bad_tokens = ["???", "\ufffd", "ÀíÓÉ", "å¤", "æ", "Ã", "Â", "涓", "鏃", "鐐", "偂"] hits = {token: text.count(token) for token in bad_tokens if text.count(token)} if hits: return False, json.dumps(hits, ensure_ascii=False) return True, "mojibake_hits=0" def main() -> int: OUT_DIR.mkdir(parents=True, exist_ok=True) generated_at = datetime.now(TZ).isoformat(timespec="seconds") rollup_path = SOURCE_DIR / "buy_point_resolution_rollup.csv" rows = read_csv(rollup_path) p3_rows: list[dict[str, str]] = [] resolved_rows: list[dict[str, str]] = [] for row in rows: row = dict(row) if row["resolution_source"] == "SECOND_REVIEW_WEAK_DISPUTE": source_action = row["source_action"] row["final_action"] = source_action row["resolution_bucket"] = "P3_CODEX_POLICY_CARRY_SOURCE" row["resolution_source"] = "CODEX_P3_LOW_PRIORITY_CARRY_SOURCE_WITH_WEAK_DISPUTE_NOTE" row["resolution_status"] = "RESOLVED_BY_CODEX_LOW_PRIORITY_POLICY" row["direct_resolution_flag"] = "1" row["codex_direct_resolution_flag"] = "1" row["formal_repair_action"] = "NO_FORMAL_REPAIR_KEEP_SOURCE_ACTION" row["resolution_reason_cn"] = ( "P3 弱指标分歧不再要求人工逐条复核;分钟数据没有形成强冲突," "按低优先级抽查口径沿用源人工裁决,并保留弱分歧说明。" ) p3_rows.append(row) resolved_rows.append(row) fieldnames = list(rows[0].keys()) write_csv(OUT_DIR / "buy_point_resolution_rollup_final.csv", resolved_rows, fieldnames) write_csv(OUT_DIR / "buy_point_p3_resolved_by_codex.csv", p3_rows, fieldnames) repair_revoke = [ row for row in resolved_rows if row["formal_repair_action"] == "REVOKE_BUY_ORDER_LOT_AND_RECALC" ] repair_add = [ row for row in resolved_rows if row["formal_repair_action"] == "ADD_BUY_ORDER_LOT_AND_RECALC" ] write_csv(OUT_DIR / "buy_point_formal_repair_revoke_required.csv", repair_revoke, fieldnames) write_csv(OUT_DIR / "buy_point_formal_repair_add_required.csv", repair_add, fieldnames) status_counts = Counter(row["resolution_status"] for row in resolved_rows) source_counts = Counter(row["resolution_source"] for row in resolved_rows) action_counts = Counter(row["final_action"] for row in resolved_rows) repair_counts = Counter(row["formal_repair_action"] for row in resolved_rows) p3_source_counts = Counter(row["source_action"] for row in p3_rows) summary_md = OUT_DIR / "buy_point_p3_resolution_summary.md" summary_md.write_text( "\n".join( [ "# 买点 P3 弱分歧处理总结", "", f"- run_id: {RUN_ID}", f"- source_run_id: {SOURCE_RUN_ID}", f"- generated_at: {generated_at}", f"- source_rows: {len(resolved_rows)}", "", "## 处理结论", "", "- P3 弱指标分歧 52 条已由 Codex 按低优先级口径处理完毕。", "- 处理原则:分钟数据没有形成强冲突时,不再要求人工逐条复核,沿用源人工裁决,并保留弱分歧说明。", f"- P3 中源 BUY {p3_source_counts.get('BUY', 0)} 条,源 REVIEW_HELD {p3_source_counts.get('REVIEW_HELD', 0)} 条,均不触发正式增删 BUY。", "- 正式返修清单保持为:撤销 BUY/order/lot 12 条,新增 BUY/order/lot 28 条。", "", "## 最终动作分布", "", "| action | count |", "|---|---:|", *[f"| `{k}` | {v} |" for k, v in sorted(action_counts.items())], "", "## 后续执行要求", "", "- 后续正式返修必须只消费 `buy_point_formal_repair_revoke_required.csv` 和 `buy_point_formal_repair_add_required.csv`。", "- 凡新增或撤销 BUY 的条目,必须同步重算 order、lot、case summary、收益口径、图片入口、自检和 manifest。", "- 本包只关闭 P3 人工复核压力,不生成最终收益率、成功率、胜率、回撤或策略有效性结论。", ] ) + "\n", encoding="utf-8", ) summary_readable, summary_readable_detail = text_is_readable(summary_md) self_check_items = [ { "item": "SOURCE_ROWS_MATCH_1141", "status": "PASS" if len(resolved_rows) == 1141 else "FAIL", "detail": f"rows={len(resolved_rows)}", }, { "item": "P3_ALL_RESOLVED", "status": "PASS" if len(p3_rows) == 52 else "FAIL", "detail": f"p3_resolved={len(p3_rows)}", }, { "item": "NO_PENDING_LOW_PRIORITY_RECHECK", "status": "PASS" if action_counts.get("PENDING_LOW_PRIORITY_SAMPLE_RECHECK", 0) == 0 else "FAIL", "detail": f"pending={action_counts.get('PENDING_LOW_PRIORITY_SAMPLE_RECHECK', 0)}", }, { "item": "FORMAL_REPAIR_COUNTS_STABLE", "status": "PASS" if len(repair_revoke) == 12 and len(repair_add) == 28 else "FAIL", "detail": f"revoke={len(repair_revoke)}, add={len(repair_add)}", }, { "item": "NO_FORMAL_REPAIR_FOR_P3", "status": "PASS" if all(row["formal_repair_action"] == "NO_FORMAL_REPAIR_KEEP_SOURCE_ACTION" for row in p3_rows) else "FAIL", "detail": "P3 rows carry source action only", }, { "item": "SUMMARY_TEXT_READABLE", "status": "PASS" if summary_readable else "FAIL", "detail": summary_readable_detail, }, ] write_csv(OUT_DIR / "self_check_items.csv", self_check_items, ["item", "status", "detail"]) self_check = { "run_id": RUN_ID, "source_run_id": SOURCE_RUN_ID, "generated_at": generated_at, "status": "PASS_FOR_BUY_POINT_P3_RESOLUTION_REVIEW_READY" if all(item["status"] == "PASS" for item in self_check_items) else "FAIL", "pass_count": sum(1 for item in self_check_items if item["status"] == "PASS"), "fail_count": sum(1 for item in self_check_items if item["status"] == "FAIL"), } write_json(OUT_DIR / "self_check.json", self_check) summary = { "run_id": RUN_ID, "source_run_id": SOURCE_RUN_ID, "strict_run_id": STRICT_RUN_ID, "generated_at": generated_at, "source_rows": len(resolved_rows), "p3_resolved_by_codex": len(p3_rows), "p3_source_action_counts": dict(p3_source_counts), "final_action_counts": dict(action_counts), "resolution_source_counts": dict(source_counts), "formal_repair_counts": dict(repair_counts), "formal_repair_revoke_required": len(repair_revoke), "formal_repair_add_required": len(repair_add), "boundary": [ "P3 弱指标分歧按低优先级处理,不再要求人工逐条复核。", "P3 未触发正式增删 BUY,只沿用源人工裁决并保留弱分歧说明。", "正式返修仍只处理 12 条撤销和 28 条新增,后续必须重算 order、lot、case summary 和收益口径。", "本包不是最终收益结论,不构成买入建议,不证明策略有效性。", ], } write_json(OUT_DIR / "summary.json", summary) source_artifacts = [] for source_name in [ "buy_point_resolution_rollup.csv", "buy_point_second_review_detail.csv", "buy_point_second_review_human_recheck_report.md", ]: src = SOURCE_DIR / source_name source_artifacts.append( { "source_run_id": SOURCE_RUN_ID, "path": src.relative_to(PROJECT_ROOT).as_posix(), "size": str(src.stat().st_size), "sha256": sha256_file(src), } ) write_csv(OUT_DIR / "source_artifact_manifest.csv", source_artifacts, ["source_run_id", "path", "size", "sha256"]) manifest_rows = [] for path in sorted(OUT_DIR.rglob("*")): if path.is_file() and path.name not in {"manifest.csv", "manifest.json"}: manifest_rows.append({"path": rel(path), "size": str(path.stat().st_size), "sha256": sha256_file(path)}) write_csv(OUT_DIR / "manifest.csv", manifest_rows, ["path", "size", "sha256"]) write_json(OUT_DIR / "manifest.json", {"run_id": RUN_ID, "files": manifest_rows}) return 0 if __name__ == "__main__": raise SystemExit(main())