from __future__ import annotations
|
|
import hashlib
|
import json
|
from datetime import datetime
|
from pathlib import Path
|
|
import pandas as pd
|
|
|
RUN_ID = "RUN-ANA-WUJI-BASELINE-PILOT-20260607-001"
|
CASE_MATTER_ID = "ANA-WUJI-BASELINE-2023-2026"
|
DESIGN_ID = "DESIGN-WUJI-BASELINE-FLOW-20260607"
|
DESIGN_AUDIT_ID = "AUDIT-ANA-WUJI-BASELINE-FLOW-20260607-001"
|
ROOT = Path(__file__).resolve().parents[1]
|
|
|
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(path: Path) -> dict:
|
return json.loads(path.read_text(encoding="utf-8"))
|
|
|
def manifest_files() -> list[dict]:
|
rows: list[dict] = []
|
for path in sorted(ROOT.rglob("*")):
|
if not path.is_file():
|
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 main() -> None:
|
generated_at = datetime.now().astimezone().isoformat(timespec="seconds")
|
candidate_summary = read_json(ROOT / "candidate_generation_summary.json")
|
candidate_counts = candidate_summary["candidate_counts"]
|
candidate_image_summary = read_json(ROOT / "candidate_image_generation_summary.json")
|
entry_review_summary = read_json(ROOT / "entry_review_generation_summary.json")
|
entry_ai_summary = read_json(ROOT / "entry_ai_review_summary.json")
|
exit_review_summary = read_json(ROOT / "exit_review_generation_summary.json")
|
exit_ai_summary = read_json(ROOT / "exit_ai_review_summary.json")
|
self_check = read_json(ROOT / "self_check.json")
|
source_db = read_json(ROOT / "source_db_direct_check.json")
|
source_boundaries = source_db["execution_boundaries"]
|
daily_range = source_boundaries["daily_price_usable_range"]
|
minute_range = source_boundaries["minute_price_usable_range_for_intraday_replay"]
|
breadth_range = source_boundaries["market_breadth_daily_usable_range"]
|
|
image_manifest = pd.read_csv(ROOT / "image_manifest.csv", encoding="utf-8-sig")
|
decisions = pd.read_csv(ROOT / "decision_log.csv", encoding="utf-8-sig")
|
orders = pd.read_csv(ROOT / "order_ledger.csv", encoding="utf-8-sig")
|
lots = pd.read_csv(ROOT / "position_lot_ledger.csv", encoding="utf-8-sig")
|
case_summary = pd.read_csv(ROOT / "case_summary.csv", encoding="utf-8-sig")
|
|
completed_outputs = [
|
"run_config.md",
|
"run_config.json",
|
"baseline_rule_mapping.csv",
|
"baseline_excluded_rule_table.csv",
|
"code_validation_report.md",
|
"code_validation_checks.json",
|
"baseline_mapping_check.csv",
|
"sample_recalc_check.csv",
|
"chart_smoke_manifest.csv",
|
"source_db_direct_check.json",
|
"candidate_ledger.csv",
|
"candidate_date_summary.csv",
|
"case_index.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",
|
"self_check.json",
|
"self_check.md",
|
]
|
|
held_items = [
|
{
|
"item": "unresolved_lots",
|
"count": int(exit_ai_summary["unresolved_lot_count"]),
|
"reason": "Exit review has resolved all previous held lots under existing rules. Remaining non-SELL lots are explicit WINDOW_END_VALUATION_ONLY or EXIT_DATA_GAP_HELD rows, not forced SELL.",
|
},
|
{
|
"item": "return_statistics",
|
"count": 1,
|
"reason": "Execution audit is pending and strict_baseline_return_ready_flag=false.",
|
},
|
]
|
|
summary = {
|
"run_id": RUN_ID,
|
"case_matter_id": CASE_MATTER_ID,
|
"design_id": DESIGN_ID,
|
"design_audit_id": DESIGN_AUDIT_ID,
|
"created_at": "2026-06-07T23:37:00+08:00",
|
"updated_at": generated_at,
|
"role_instance_id": "case_analysis.analyst",
|
"stage": "STRUCTURE_PILOT_EXIT_REVIEW_RESOLVED_SELF_CHECK_DONE",
|
"status": "exit_review_held_lots_resolved_self_check_pass_return_stat_held_execution_review_submission_ready",
|
"completed_outputs": completed_outputs,
|
"held_items": held_items,
|
"conclusion_boundary": "Structure pilot only. No complete baseline return, success rate, win rate, drawdown, or strategy effectiveness conclusion. Closed-lot contribution is an internal ledger check and must not be quoted as final return.",
|
"source_db_validation": {
|
"status": "PASS_WITH_MINUTE_BOUNDARY",
|
"evidence": "source_db_direct_check.json",
|
"daily_price_range": f"{daily_range['min_date']} to {daily_range['max_date']}",
|
"minute_price_range": f"{minute_range['min_date']} to {minute_range['max_date']}",
|
"market_breadth_range": f"{breadth_range['min_date']} to {breadth_range['max_date']}",
|
"market_breadth_scope": "ALL_A_SHARE/ALL",
|
"market_breadth_run_id": breadth_range["min_run_id"],
|
},
|
"candidate_pool": {
|
"candidate_rows": candidate_counts["candidate_rows"],
|
"candidate_entry_dates": candidate_counts["candidate_entry_dates"],
|
"selected_pilot_cases": len(candidate_summary["selected_cases"]),
|
"market_gate_open_dates": candidate_counts["market_gate_open_entry_dates"],
|
"market_gate_closed_dates": candidate_counts["market_gate_closed_entry_dates"],
|
},
|
"image_package": {
|
"image_count": len(image_manifest),
|
"role_counts": image_manifest.chart_role.value_counts().to_dict(),
|
"candidate_daily_images": candidate_image_summary["image_count"],
|
"entry_review_images": entry_review_summary["entry_review_images"],
|
"buy_decision_images": entry_ai_summary["buy_decision_image_count"],
|
"exit_signal_images": exit_review_summary["exit_signal_images"],
|
"sell_decision_images": int((image_manifest.chart_role == "exit_1m_sell_decision_view").sum()),
|
},
|
"trade_ledger": {
|
"order_counts": orders.action.value_counts().to_dict(),
|
"lot_status_counts": lots.lot_status.value_counts().to_dict(),
|
"case_count": len(case_summary),
|
"closed_lot_account_return_sum_for_recalc_only": exit_ai_summary["closed_lot_account_return_sum"],
|
},
|
"decision_counts": decisions.groupby(["decision_stage", "action_status"]).size().astype(int).to_dict(),
|
"self_check": {
|
"status": self_check["overall_status"],
|
"check_count": self_check["check_count"],
|
"fail_count": self_check["fail_count"],
|
"evidence": "self_check.json",
|
},
|
"repair_context": {
|
"previous_execution_audit_id": "AUDIT-ANA-WUJI-BASELINE-PILOT-20260608-EXEC-001",
|
"previous_review_message_id": "msg_20260608012706751_a5dbc58b",
|
"fixed_issue": "ANA-ISSUE-WUJI-ACCOUNT-LEDGER-DIRECTION-20260608-001",
|
"repair_summary": "Fixed daily_account_ledger cash/open-position direction, added account semantic self-checks, refreshed root case_image_board.md and run_config.md status.",
|
},
|
"exit_review_resolution_context": {
|
"previous_issue": "ANA-ISSUE-WUJI-EXIT-REVIEW-20260608-001",
|
"resolution_summary": "Re-scanned the approved 10-trading-day observation window with 1-minute evidence. Four previously held lots were closed by minute-confirmed SELL points; one lot is WINDOW_END_VALUATION_ONLY; one lot remains EXIT_DATA_GAP_HELD because minute data coverage ends before the signal date.",
|
"evidence": "exit_resolution_log.csv",
|
},
|
"strict_baseline_return_ready_flag": False,
|
"execution_review_status": "SUBMISSION_READY",
|
}
|
# Convert tuple keys from groupby for JSON stability.
|
summary["decision_counts"] = {f"{k[0]}::{k[1]}": v for k, v in summary["decision_counts"].items()}
|
(ROOT / "summary.json").write_text(
|
json.dumps(summary, ensure_ascii=False, indent=2) + "\n",
|
encoding="utf-8",
|
)
|
|
summary_md = [
|
f"# {RUN_ID} summary",
|
"",
|
"当前阶段:`STRUCTURE_PILOT_EXIT_REVIEW_RESOLVED_SELF_CHECK_DONE`",
|
"当前状态:6 笔待审 lot 已按现有规则复核,结构试点自检通过;执行审核待提交;`RETURN_STAT_READY=false`。",
|
"",
|
"## 已完成",
|
"",
|
"1. 完成 run_config、蓝本映射、代码验证、T+1 / 账本烟测、中文 K 线烟测和源库直连校验。",
|
f"2. 生成真实候选池:{candidate_counts['candidate_rows']} 行,覆盖 {candidate_counts['candidate_entry_dates']} 个入场日。",
|
f"3. 选择 {len(candidate_summary['selected_cases'])} 个代表性小样本案例,并生成 100 日选股日 K 图。",
|
f"4. 生成买入分时复核图 {entry_review_summary['entry_review_images']} 张,AI 买入裁决图 {entry_ai_summary['buy_decision_image_count']} 张。",
|
f"5. 生成卖点日 K 信号图 {exit_review_summary['exit_signal_images']} 张,AI SELL 分时裁决图 {summary['image_package']['sell_decision_images']} 张。",
|
f"6. 建立订单、lot、事件账户账本和案例汇总:BUY {summary['trade_ledger']['order_counts'].get('BUY', 0)} 笔,SELL {summary['trade_ledger']['order_counts'].get('SELL', 0)} 笔。",
|
f"7. 完成机器自检:{self_check['check_count']} 项,失败 {self_check['fail_count']} 项,图片 manifest 共 {len(image_manifest)} 张图且 hash 可反查。",
|
"8. 根据执行审核反馈 `AUDIT-ANA-WUJI-BASELINE-PILOT-20260608-EXEC-001` 修复账户流水方向,并补充现金 / 持仓方向自检。",
|
"9. 针对 `ANA-ISSUE-WUJI-EXIT-REVIEW-20260608-001` 重新按 1 分钟 evidence 扫描观察窗口:4 笔原待审 lot 转为分钟线确认 SELL,1 笔为窗口末估值保留,1 笔为分钟数据缺口保留。",
|
"",
|
"## 未完成 / 保留项",
|
"",
|
"1. 本次退出复核执行审核尚未通过,审核通过前不得把本包读成正式结论。",
|
f"2. 未闭合 / 非真实 SELL lot 共 {exit_ai_summary['unresolved_lot_count']} 笔:1 笔窗口末估值保留,1 笔分钟数据缺口保留。",
|
"3. 当前闭合 lot 的账户贡献合计只用于账本复算,不是完整 baseline 收益率、成功率、胜率或回撤。",
|
"",
|
"## 结论边界",
|
"",
|
"本包只证明第一轮小样本结构试点可以串起候选、图片、AI 手工裁决、订单 / lot / 账户账本、自检和人工审核入口。",
|
"它不产生完整无忌 baseline 收益、成功率、胜率、回撤或策略有效性结论。",
|
"",
|
"后续必须提交 `case_analysis.reviewer` 执行审核;若审核要求继续修正口径、图或账本,按审计意见回写后再继续下一批。",
|
"",
|
]
|
(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": "STRUCTURE_PILOT_EXIT_REVIEW_RESOLVED_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()]),
|
"notes": "Exit-review-resolved structure pilot package manifest. manifest.json itself is excluded from hashing for stability. Execution review submission is pending; return statistics are not ready.",
|
}
|
(ROOT / "manifest.json").write_text(
|
json.dumps(manifest, ensure_ascii=False, indent=2) + "\n",
|
encoding="utf-8",
|
)
|
|
|
if __name__ == "__main__":
|
main()
|