# -*- coding: utf-8 -*- """Create only a blank manual-decision review template. This script is intentionally not allowed to generate final human_decision_*, decision_time, or decision_source fields. The V1 external manual decision source must be filled by case_analysis.analyst / laoan outside this script, then applied with tools/apply_manual_decision_repair.py. """ from __future__ import annotations import csv import json from pathlib import Path import pandas as pd ROOT = Path(__file__).resolve().parents[1] WORKBOOK = "manual_decision_review_workbook.csv" TEMPLATE = "manual_decision_external_source_template.csv" def read_csv(name: str) -> pd.DataFrame: return pd.read_csv(ROOT / name, dtype=str, keep_default_na=False).fillna("") def code_reason(row: pd.Series) -> str: return row.get("code_evidence_reason_cn", "") or row.get("code_suggested_reason_cn", "") or row.get("human_decision_reason_cn", "") def build_workbook() -> pd.DataFrame: strict = read_csv("strict_sell_candidate_ledger.csv") rolling = read_csv("rolling_low_buy_candidate_ledger.csv") rows: list[dict[str, str]] = [] for _, row in strict.iterrows(): rows.append( { "artifact_type": "STRICT_SELL_SIGNAL", "signal_id": row.get("signal_id", ""), "rolling_signal_id": "", "case_id": row.get("case_id", ""), "candidate_id": row.get("candidate_id", ""), "symbol": row.get("symbol", ""), "signal_type": row.get("signal_type", ""), "trade_date": row.get("observation_trade_date", ""), "trade_time": row.get("candidate_time", ""), "code_suggested_action": row.get("code_suggested_action", ""), "code_suggested_reason_cn": code_reason(row), "review_input_chart_path": row.get("review_input_chart_path", "") or row.get("chart_path", ""), "numeric_context": json.dumps( { "gain_pct": row.get("gain_pct", ""), "action_price": row.get("action_price", ""), "entry_support_price": row.get("entry_support_price", ""), "trend_first3_time": row.get("trend_first3_time", ""), "fast_breakout_minutes": row.get("fast_breakout_minutes", ""), }, ensure_ascii=False, ), } ) for _, row in rolling.iterrows(): rows.append( { "artifact_type": "ROLLING_LOW_BUY_SIGNAL", "signal_id": "", "rolling_signal_id": row.get("rolling_signal_id", ""), "case_id": row.get("case_id", ""), "candidate_id": row.get("candidate_id", ""), "symbol": row.get("symbol", ""), "signal_type": row.get("signal_type", ""), "trade_date": row.get("rolling_trade_date", ""), "trade_time": row.get("rolling_time", ""), "code_suggested_action": row.get("code_suggested_action", ""), "code_suggested_reason_cn": code_reason(row), "review_input_chart_path": row.get("review_input_chart_path", "") or row.get("chart_path", ""), "numeric_context": json.dumps( { "rolling_price": row.get("rolling_price", ""), "ma5_close": row.get("ma5_close", ""), "near_ma5_pct": row.get("near_ma5_pct", ""), "volume_ratio_vs_prev20m": row.get("volume_ratio_vs_prev20m", ""), }, ensure_ascii=False, ), } ) workbook = pd.DataFrame(rows) workbook.to_csv(ROOT / WORKBOOK, index=False, encoding="utf-8-sig") return workbook def build_blank_template(workbook: pd.DataFrame) -> pd.DataFrame: fieldnames = [ "external_decision_id", "artifact_type", "signal_id", "rolling_signal_id", "case_id", "candidate_id", "symbol", "code_suggested_action", "accept_code_suggestion_flag", "human_decision_action", "human_decision_reason_cn", "decision_operator", "decision_time", "decision_source", "review_input_chart_path", "review_input_chart_sha256", "manual_draft_path", "manual_draft_sha256", "reviewer_notes", ] rows = [] for seq, (_, row) in enumerate(workbook.iterrows(), start=1): rows.append( { "external_decision_id": f"EXT-MANUAL-RUN-ANA-WUJI-STRICT-SELL-ROLLING-REPAIR-20260609-001-{seq:06d}", "artifact_type": row.get("artifact_type", ""), "signal_id": row.get("signal_id", ""), "rolling_signal_id": row.get("rolling_signal_id", ""), "case_id": row.get("case_id", ""), "candidate_id": row.get("candidate_id", ""), "symbol": row.get("symbol", ""), "code_suggested_action": row.get("code_suggested_action", ""), "accept_code_suggestion_flag": "", "human_decision_action": "", "human_decision_reason_cn": "", "decision_operator": "", "decision_time": "", "decision_source": "", "review_input_chart_path": row.get("review_input_chart_path", ""), "review_input_chart_sha256": "", "manual_draft_path": "", "manual_draft_sha256": "", "reviewer_notes": "", } ) template = pd.DataFrame(rows, columns=fieldnames) template.to_csv(ROOT / TEMPLATE, index=False, encoding="utf-8-sig", quoting=csv.QUOTE_MINIMAL) return template def main() -> None: workbook = build_workbook() template = build_blank_template(workbook) print( json.dumps( { "workbook": WORKBOOK, "template": TEMPLATE, "rows": len(template), "note": "Template only. Fill manual_decision_external_source_ledger.csv manually before running apply_manual_decision_repair.py.", }, ensure_ascii=False, ) ) if __name__ == "__main__": main()