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-FULL-2023-2026-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()
|