from __future__ import annotations import hashlib import json from datetime import datetime, timezone, timedelta from pathlib import Path import pandas as pd RUN_ID = "RUN-ANA-WUJI-V1-STRICT-NOTE-FULL-RERUN-20260614-001" ROOT = Path(__file__).resolve().parents[1] TZ = timezone(timedelta(hours=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 main() -> None: template = pd.read_csv(ROOT / "manual_buy_decision_external_template.csv", encoding="utf-8-sig") chart_audit = pd.read_csv(ROOT / "chart_evidence_audit.csv", encoding="utf-8-sig") required = { "external_decision_id", "case_id", "candidate_id", "symbol", "signal_trade_date", "entry_trade_date", "code_suggested_action", "review_input_chart_path", "review_input_chart_sha256", } missing = sorted(required - set(template.columns)) if missing: raise RuntimeError(f"manual template missing columns: {missing}") workbench = template.copy() for col in [ "human_decision_action", "human_decision_reason_cn", "decision_operator", "decision_time", "decision_source", "accept_code_suggestion_flag", "reviewer_notes", ]: workbench[col] = "" for row in workbench.itertuples(index=False): chart = ROOT / row.review_input_chart_path if not chart.exists(): raise RuntimeError(f"missing chart: {row.review_input_chart_path}") if sha256_file(chart) != row.review_input_chart_sha256: raise RuntimeError(f"chart hash mismatch: {row.review_input_chart_path}") workbench.to_csv(ROOT / "manual_buy_decision_workbench_blank.csv", index=False, encoding="utf-8-sig") summary = { "run_id": RUN_ID, "generated_at": datetime.now(TZ).isoformat(timespec="seconds"), "rows": int(len(workbench)), "chart_rows": int(len(chart_audit)), "purpose": "blank workbench only; final human decisions are not generated here", "forbidden_fields_left_blank": [ "human_decision_action", "human_decision_reason_cn", "decision_operator", "decision_time", "decision_source", "accept_code_suggestion_flag", "reviewer_notes", ], } (ROOT / "manual_buy_decision_workbench_summary.json").write_text( json.dumps(summary, ensure_ascii=False, indent=2), encoding="utf-8", ) if __name__ == "__main__": main()