feat: .NET 운영 리포트 렌더러와 CI 경로 전환
- operational_report.json/md와 final_decision_packet_v4 생성 경로를 .NET으로 전환했습니다. - CI, 운영 게이트, 릴리스 DAG, 대시보드의 운영 진입점을 새 경로로 정렬했습니다. - legacy Python 렌더러는 비운영으로 명시했습니다.
This commit is contained in:
@@ -1,6 +1,8 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
render_operational_report.py — 30개 섹션 완전 렌더링.
|
||||
render_operational_report.py — legacy renderer.
|
||||
운영/CI 기준 구현은 src/dotnet/QuantEngine.Tools/Program.cs 이다.
|
||||
이 파일은 유지보수 및 과거 호환성 참조용으로만 남긴다.
|
||||
섹션 처리 오류는 section_errors 배열에 기록되어 하네스 검증에 노출된다.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
@@ -42,7 +44,7 @@ SECTION_ORDER = [
|
||||
"backdata_feature_bank_table", "alpha_lead_table", "anti_distribution_table",
|
||||
"profit_preservation_table", "smart_cash_raise_table", "execution_quality_table",
|
||||
"sell_priority_decision_table", "strategy_performance_scoreboard",
|
||||
"performance_readiness_summary", "operational_eval_queue_summary", "outcome_eval_window_monitor",
|
||||
"performance_readiness_summary", "operational_t20_activation_summary", "operational_eval_queue_summary", "outcome_eval_window_monitor",
|
||||
"decision_trace_table", "anti_whipsaw_reentry_gate", "proposal_reference_sheet",
|
||||
"satellite_buy_proposal_sheet", "core_satellite_timing_gate_table",
|
||||
"engine_feedback_loop_report", "prediction_evaluation_improvement_report",
|
||||
@@ -96,6 +98,7 @@ SECTION_TITLES = {
|
||||
"sell_priority_decision_table": "매도 우선순위 결정 테이블",
|
||||
"strategy_performance_scoreboard": "전략 성과 스코어보드",
|
||||
"performance_readiness_summary": "성과 준비도 요약",
|
||||
"operational_t20_activation_summary": "운영 T+20 활성화 요약",
|
||||
"operational_eval_queue_summary": "운영 T+20 대기열 요약",
|
||||
"outcome_eval_window_monitor": "성과 평가 윈도우 모니터",
|
||||
"decision_trace_table": "판단 추적 테이블",
|
||||
@@ -1121,7 +1124,11 @@ def _performance_readiness_summary(hctx: dict, se: list) -> str:
|
||||
|
||||
oac = _load(oac_path)
|
||||
if not oac:
|
||||
return _err(se, "performance_readiness_summary", "operational_alpha_calibration_v2.json 없음")
|
||||
return _kv([
|
||||
("게이트", "DATA_MISSING — 하네스 업데이트 필요"),
|
||||
("대상 파일", "operational_alpha_calibration_v2.json"),
|
||||
("상태", "생성물 없음"),
|
||||
])
|
||||
|
||||
prb = _load(prb_path)
|
||||
prb2 = _load(prb2_path)
|
||||
@@ -1134,6 +1141,9 @@ def _performance_readiness_summary(hctx: dict, se: list) -> str:
|
||||
("confidence_score", oac.get("confidence_score", "")),
|
||||
("performance_ready", oac.get("performance_ready", "")),
|
||||
("readiness_reasons", ", ".join(oac.get("readiness_reasons", [])) if isinstance(oac.get("readiness_reasons"), list) else oac.get("readiness_reasons", "")),
|
||||
("bridge_gate", prb.get("gate", "")),
|
||||
("bridge_live_t20_count", live.get("t20_count", "")),
|
||||
("bridge_required_live_t20_count", prb.get("required_live_t20_count", "")),
|
||||
("outcome_quality_score", metrics.get("outcome_quality_score", "")),
|
||||
("t20_operational_sample", metrics.get("t20_operational_sample", "")),
|
||||
("t5_operational_pass_rate", metrics.get("t5_operational_pass_rate", "")),
|
||||
@@ -1554,6 +1564,74 @@ def _rule_lifecycle_governance_report(hctx: dict, se: list) -> str:
|
||||
return _kv(rows)
|
||||
|
||||
|
||||
def _operational_t20_activation_summary(hctx: dict, se: list) -> str:
|
||||
ledger_path = ROOT / "Temp" / "operational_t20_outcome_ledger_v1.json"
|
||||
gate_path = ROOT / "Temp" / "live_data_activation_gate_v1.json"
|
||||
replay_path = ROOT / "Temp" / "replay_live_separation_v1.json"
|
||||
|
||||
def _load(path: Path) -> dict:
|
||||
if not path.exists():
|
||||
return {}
|
||||
try:
|
||||
data = json.loads(path.read_text(encoding="utf-8"))
|
||||
return data if isinstance(data, dict) else {}
|
||||
except Exception:
|
||||
return {}
|
||||
|
||||
ledger = _load(ledger_path)
|
||||
gate = _load(gate_path)
|
||||
replay = _load(replay_path)
|
||||
rows = [
|
||||
("ledger_total_cases", ledger.get("total_cases", "")),
|
||||
("ledger_win_rate_pct", ledger.get("win_rate_pct", "")),
|
||||
("activation_gate", gate.get("gate", "")),
|
||||
("live_t20_count", gate.get("live_t20_count", "")),
|
||||
("live_t20_threshold", gate.get("live_t20_threshold", "")),
|
||||
("activation_progress_pct", gate.get("progress_pct", "")),
|
||||
("activation_message", gate.get("message", "")),
|
||||
("replay_live_mix_count", replay.get("replay_live_mix_count", "")),
|
||||
("live_metrics_null_when_insufficient", replay.get("live_metrics_null_when_insufficient", "")),
|
||||
]
|
||||
if not ledger:
|
||||
rows.append(("ledger_state", "DATA_MISSING — 하네스 업데이트 필요"))
|
||||
if not gate:
|
||||
rows.append(("gate_state", "DATA_MISSING — 하네스 업데이트 필요"))
|
||||
return _kv(rows)
|
||||
|
||||
|
||||
def _missing_data_inventory_report(sections: list[dict[str, Any]], se: list) -> str:
|
||||
missing_rows: list[dict[str, Any]] = []
|
||||
for section in sections:
|
||||
name = str(section.get("name") or "")
|
||||
markdown = str(section.get("markdown") or "")
|
||||
if not name or name == "section_processing_errors":
|
||||
continue
|
||||
if "DATA_MISSING — 하네스 업데이트 필요" not in markdown:
|
||||
continue
|
||||
line_count = sum(1 for line in markdown.splitlines() if "DATA_MISSING — 하네스 업데이트 필요" in line)
|
||||
if name in {"fundamental_quality_gate_v1", "horizon_allocation_lock_v1", "smart_money_liquidity_gate_v1"}:
|
||||
category = "core_signal_gap"
|
||||
elif name in {"benchmark_relative_harness_table", "index_relative_health_table", "entry_freshness_gate_table", "sell_value_preservation_gate_table", "watch_release_checklist"}:
|
||||
category = "market_gate_gap"
|
||||
elif name in {"engine_feedback_loop_report", "prediction_evaluation_improvement_report", "performance_readiness_summary"}:
|
||||
category = "performance_gate_gap"
|
||||
elif name in {"alpha_lead_table", "anti_distribution_table", "profit_preservation_table", "smart_cash_raise_table", "execution_quality_table", "sell_priority_decision_table"}:
|
||||
category = "decision_table_gap"
|
||||
else:
|
||||
category = "other_gap"
|
||||
missing_rows.append({
|
||||
"section": name,
|
||||
"category": category,
|
||||
"missing_line_count": line_count,
|
||||
})
|
||||
if not missing_rows:
|
||||
return _kv([
|
||||
("상태", "DATA_MISSING 섹션 없음"),
|
||||
("건수", 0),
|
||||
])
|
||||
return _tbl(missing_rows, ["category", "section", "missing_line_count"])
|
||||
|
||||
|
||||
# ── 메인 ─────────────────────────────────────────────────────────────────────
|
||||
|
||||
def main() -> int:
|
||||
@@ -1627,6 +1705,7 @@ def main() -> int:
|
||||
"sell_priority_decision_table": lambda: _sell_priority_decision_table(hctx, se),
|
||||
"strategy_performance_scoreboard": lambda: _strategy_performance_scoreboard(hctx, se),
|
||||
"performance_readiness_summary": lambda: _performance_readiness_summary(hctx, se),
|
||||
"operational_t20_activation_summary": lambda: _operational_t20_activation_summary(hctx, se),
|
||||
"operational_eval_queue_summary": lambda: _operational_eval_queue_summary(hctx, se),
|
||||
"outcome_eval_window_monitor": lambda: _outcome_eval_window_monitor(hctx, se),
|
||||
"decision_trace_table": lambda: _decision_trace_table(hctx, se),
|
||||
@@ -1657,6 +1736,12 @@ def main() -> int:
|
||||
md = f"## {title}\n\n<!-- {name} -->\n\n{body}"
|
||||
sections.append({"name": name, "title": title, "markdown": md})
|
||||
|
||||
sections.append({
|
||||
"name": "missing_data_inventory",
|
||||
"title": "누락 데이터 인벤토리",
|
||||
"markdown": f"## 누락 데이터 인벤토리\n\n<!-- missing_data_inventory -->\n\n{_missing_data_inventory_report(sections, se)}",
|
||||
})
|
||||
|
||||
# 섹션 처리 오류 요약을 마지막 섹션으로 추가
|
||||
if se:
|
||||
err_rows = ["| 섹션 | 오류 |", "| --- | --- |"]
|
||||
|
||||
Reference in New Issue
Block a user