1
2026-07-19 228d838fdb7f7dde7edc4993fdbb9654c9c31df7
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
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()