from __future__ import annotations import hashlib import json import shutil from datetime import datetime from pathlib import Path import pandas as pd RUN_ID = "RUN-ANA-WUJI-EXPAND-30-20260608-001" SOURCE_RUN_ID = "RUN-ANA-WUJI-BASELINE-PILOT-20260607-001" CASE_MATTER_ID = "ANA-WUJI-BASELINE-2023-2026" DESIGN_ID = "DESIGN-WUJI-EXPAND-30-20260608" DESIGN_AUDIT_ID = "AUDIT-ANA-WUJI-EXPAND-30-20260608-DESIGN-001" EXEC_AUDIT_ID = "AUDIT-ANA-WUJI-EXPAND-30-20260608-EXEC-001" EXEC_REREVIEW_AUDIT_ID = "AUDIT-ANA-WUJI-EXPAND-30-20260608-EXEC-REREVIEW-001" BOARD_STATUS_ISSUE_ID = "ANA-ISSUE-WUJI-EXPAND-30-BOARD-STATUS-20260608-001" ROOT = Path(__file__).resolve().parents[1] PROJECT_ROOT = ROOT.parents[2] SOURCE_ROOT = PROJECT_ROOT / "ana-data" / "result" / SOURCE_RUN_ID 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 read_json(name: str) -> dict: return json.loads((ROOT / name).read_text(encoding="utf-8")) def read_csv(name: str) -> pd.DataFrame: return pd.read_csv(ROOT / name, encoding="utf-8-sig") def manifest_files() -> list[dict]: rows: list[dict] = [] for path in sorted(ROOT.rglob("*")): if not path.is_file(): continue if "__pycache__" in path.parts: continue rel = path.relative_to(ROOT).as_posix() if rel == "manifest.json": continue rows.append({"path": rel, "exists": True, "size": path.stat().st_size, "sha256": sha256_file(path)}) return rows def maybe_copy_source_db_check() -> None: target = ROOT / "source_db_direct_check.json" source = SOURCE_ROOT / "source_db_direct_check.json" if not target.exists() and source.exists(): shutil.copy2(source, target) def existing_outputs(names: list[str]) -> list[str]: return [name for name in names if (ROOT / name).exists()] def main() -> None: generated_at = datetime.now().astimezone().isoformat(timespec="seconds") maybe_copy_source_db_check() expand_config = read_json("expand_run_config.json") candidate_summary = read_json("candidate_generation_summary.json") candidate_image_summary = read_json("candidate_image_generation_summary.json") entry_review_summary = read_json("entry_review_generation_summary.json") entry_ai_summary = read_json("entry_ai_review_summary.json") exit_review_summary = read_json("exit_review_generation_summary.json") exit_ai_summary = read_json("exit_ai_review_summary.json") self_check = read_json("self_check.json") source_db = read_json("source_db_direct_check.json") if (ROOT / "source_db_direct_check.json").exists() else {} case_index = read_csv("case_index.csv") selected = read_csv("selected_candidate_ledger.csv") image_manifest = read_csv("image_manifest.csv") decisions = read_csv("decision_log.csv") orders = read_csv("order_ledger.csv") lots = read_csv("position_lot_ledger.csv") case_summary = read_csv("case_summary.csv") order_counts = {k: int(v) for k, v in orders.action.value_counts().to_dict().items()} lot_status_counts = {k: int(v) for k, v in lots.lot_status.value_counts().to_dict().items()} image_role_counts = {k: int(v) for k, v in image_manifest.chart_role.value_counts().to_dict().items()} decision_counts = decisions.groupby(["decision_stage", "action_status"]).size().astype(int).to_dict() decision_counts = {f"{k[0]}::{k[1]}": int(v) for k, v in decision_counts.items()} anchor_count = int((case_index.expand_case_role == "ANCHOR_REUSED_FROM_AUDITED_7_CASE_PILOT").sum()) new_count = int((case_index.expand_case_role == "NEW_EXPAND_30_CASE").sum()) no_trade_case_count = int((case_index.market_gate_status == "NO_TRADE_MARKET_GATE_CLOSED").sum()) buy_case_count = int(case_summary.case_id.nunique()) unresolved_lots = lots[lots.lot_status != "CLOSED_BY_AI_SELL"] completed_outputs = existing_outputs( [ "expand_run_config.md", "expand_run_config.json", "run_config.md", "run_config.json", "candidate_ledger.csv", "candidate_date_summary.csv", "expand_case_index.csv", "case_index.csv", "expand_sample_selection_log.csv", "expand_candidate_selection_ledger.csv", "selected_candidate_ledger.csv", "image_manifest.csv", "case_image_board.md", "case_story_board.md", "entry_review_generation_summary.json", "entry_ai_review_summary.json", "sell_signal_candidates.csv", "sell_decision_log.csv", "exit_resolution_log.csv", "order_ledger.csv", "position_lot_ledger.csv", "daily_account_ledger.csv", "case_summary.csv", "chart_evidence_audit.csv", "link_evidence_audit.csv", "self_check.json", "self_check.md", ] ) held_items = [ { "item": "unresolved_lots", "count": int(len(unresolved_lots)), "status_counts": {k: int(v) for k, v in unresolved_lots.lot_status.value_counts().to_dict().items()}, "reason": "Non-real SELL lots are explicitly retained as WINDOW_END_VALUATION_ONLY or EXIT_DATA_GAP_HELD and are not forced into return conclusions.", }, { "item": "return_statistics", "count": 1, "reason": "Execution audit is pending. This expansion package is not RETURN_STAT_READY and is not a full 2023-2026 baseline result.", }, ] source_validation = { "status": "SOURCE_DB_CHECK_INHERITED_FROM_AUDITED_PILOT" if source_db else "SOURCE_DB_CHECK_NOT_ATTACHED", "evidence": "source_db_direct_check.json" if source_db else "", } if source_db.get("execution_boundaries"): boundaries = source_db["execution_boundaries"] source_validation.update( { "daily_price_range": boundaries.get("daily_price_usable_range", {}), "minute_price_range": boundaries.get("minute_price_usable_range_for_intraday_replay", {}), "market_breadth_range": boundaries.get("market_breadth_daily_usable_range", {}), } ) summary = { "schema_version": "1.0", "run_id": RUN_ID, "case_matter_id": CASE_MATTER_ID, "design_id": DESIGN_ID, "design_audit_id": DESIGN_AUDIT_ID, "source_run_id": SOURCE_RUN_ID, "source_audit_ids": expand_config.get("source_audit_ids", []), "updated_at": generated_at, "role_instance_id": "case_analysis.analyst", "stage": "EXPAND_30_EXECUTION_SELF_CHECK_DONE", "status": "expand_30_execution_rereview_passed", "review_repair_context": { "previous_execution_audit_id": EXEC_AUDIT_ID, "execution_rereview_audit_id": EXEC_REREVIEW_AUDIT_ID, "issue_id": BOARD_STATUS_ISSUE_ID, "repair_scope": "image and story board status text, board link audit, self-check, manifest", "repair_boundary": "Candidate pool, buy/sell decisions, ledgers, market gate, return口径 and boundary lots were not changed in this repair.", "rereview_status": "passed", }, "completed_outputs": completed_outputs, "held_items": held_items, "conclusion_boundary": ( "30-case controlled expansion execution package only. No complete 2023-2026 baseline return, " "success rate, win rate, drawdown, or strategy effectiveness conclusion." ), "source_db_validation": source_validation, "candidate_pool": { "source_candidate_rows": int(candidate_summary["candidate_counts"]["candidate_rows"]), "source_candidate_entry_dates": int(candidate_summary["candidate_counts"]["candidate_entry_dates"]), "selected_case_days": int(len(case_index)), "selected_candidate_rows": int(len(selected)), "anchor_case_days": anchor_count, "new_case_days": new_count, "no_trade_case_days": no_trade_case_count, "buy_case_days": buy_case_count, "market_gate_open_dates_in_source": int(candidate_summary["candidate_counts"]["market_gate_open_entry_dates"]), "market_gate_closed_dates_in_source": int(candidate_summary["candidate_counts"]["market_gate_closed_entry_dates"]), }, "image_package": { "image_count": int(len(image_manifest)), "role_counts": image_role_counts, "candidate_daily_images": int(candidate_image_summary["image_count"]), "entry_review_images": int(entry_review_summary["entry_review_images"]), "buy_decision_images": int(entry_ai_summary["buy_decision_image_count"]), "exit_signal_images": int(exit_review_summary["exit_signal_images"]), "sell_decision_images": int(image_role_counts.get("exit_1m_sell_decision_view", 0)), }, "trade_ledger": { "order_counts": order_counts, "lot_status_counts": lot_status_counts, "case_summary_rows": int(len(case_summary)), "closed_lot_account_return_sum_for_recalc_only": float(exit_ai_summary["closed_lot_account_return_sum"]), }, "decision_counts": decision_counts, "self_check": { "status": self_check["overall_status"], "check_count": int(self_check["check_count"]), "fail_count": int(self_check["fail_count"]), "evidence": "self_check.json", }, "strict_baseline_return_ready_flag": False, "execution_review_status": "EXEC_REREVIEW_PASSED", } (ROOT / "summary.json").write_text(json.dumps(summary, ensure_ascii=False, indent=2) + "\n", encoding="utf-8") summary_md = [ f"# {RUN_ID} summary", "", f"- 案例事项:`{CASE_MATTER_ID}`", f"- 设计:`{DESIGN_ID}`", f"- 设计审核:`{DESIGN_AUDIT_ID}`", "- 阶段:`EXPAND_30_EXECUTION_SELF_CHECK_DONE`", "- 状态:`expand_30_execution_rereview_passed`", f"- 执行审核反馈:`{EXEC_AUDIT_ID}` HELD;关联问题 `{BOARD_STATUS_ISSUE_ID}` 已返修并由 `{EXEC_REREVIEW_AUDIT_ID}` 复审通过", "", "## 当前结果", "", f"- 案例日:30(7 个已审核锚点 + 23 个新增分层案例日)", f"- 选中候选:{len(selected)} 行", f"- 市场闸门关闭不交易案例日:{no_trade_case_count}", f"- 有 BUY 的案例日:{buy_case_count}", f"- BUY:{order_counts.get('BUY', 0)},SELL:{order_counts.get('SELL', 0)}", f"- lot:{len(lots)},已闭合:{lot_status_counts.get('CLOSED_BY_AI_SELL', 0)},边界保留:{len(unresolved_lots)}", f"- 图片:{len(image_manifest)} 张,图片板链接和 hash 已自检", f"- 自检:{self_check['check_count']} 项,失败 {self_check['fail_count']} 项", "", "## 边界", "", "- 本包只用于 30 案例日受控扩样执行复审通过记录。", "- 本执行复审通过不等于完整 2023-2026 baseline 结论。", "- 当前不是完整 2023-2026 baseline 收益率、成功率、胜率、回撤或策略有效性结论。", "- `WINDOW_END_VALUATION_ONLY` 与 `EXIT_DATA_GAP_HELD` 保持边界状态,不强行转 SELL。", "", "## 送审入口", "", "- `summary.json` / `summary.md`", "- `case_image_board.md`", "- `expand_run_config.md/json`", "- `expand_sample_selection_log.csv`", "- `decision_log.csv`、`order_ledger.csv`、`position_lot_ledger.csv`、`daily_account_ledger.csv`、`case_summary.csv`", "- `self_check.json`、`self_check_items.csv`、`chart_evidence_audit.csv`、`link_evidence_audit.csv`", "- `manifest.json`", "", ] (ROOT / "summary.md").write_text("\n".join(summary_md), encoding="utf-8") files = manifest_files() manifest = { "schema_version": "1.0", "run_id": RUN_ID, "manifest_stage": "EXPAND_30_EXECUTION_SELF_CHECK_DONE", "generated_at": generated_at, "hash_status": "size_and_sha256_recorded_for_current_artifacts_manifest_self_excluded", "overall_status": self_check["overall_status"], "file_count": len(files), "files": files, "directories": sorted([p.relative_to(ROOT).as_posix() for p in ROOT.rglob("*") if p.is_dir() and "__pycache__" not in p.parts]), "notes": "30-case controlled expansion execution package manifest after board-status repair and execution re-review pass. manifest.json itself is excluded from hashing for stability. This is not a full baseline result.", } (ROOT / "manifest.json").write_text(json.dumps(manifest, ensure_ascii=False, indent=2) + "\n", encoding="utf-8") if __name__ == "__main__": main()