9e6e2ded2f
- operational_report.json/md와 final_decision_packet_v4 생성 경로를 .NET으로 전환했습니다. - CI, 운영 게이트, 릴리스 DAG, 대시보드의 운영 진입점을 새 경로로 정렬했습니다. - legacy Python 렌더러는 비운영으로 명시했습니다.
274 lines
12 KiB
C#
274 lines
12 KiB
C#
using System.Text.Json;
|
|
|
|
static class Program
|
|
{
|
|
private static readonly string Root = Path.GetFullPath(Path.Combine(AppContext.BaseDirectory, "..", "..", "..", "..", ".."));
|
|
private static readonly string Temp = Path.Combine(Root, "Temp");
|
|
|
|
private static readonly (string Name, string Title)[] Sections =
|
|
{
|
|
("exec_safety_declaration", "집행 안전 선언"),
|
|
("final_judgment_table", "최종 판단 테이블"),
|
|
("final_execution_decision", "최종 실행 결정"),
|
|
("concise_hts_input_sheet", "HTS 입력 요약표"),
|
|
("watch_breakout_gate", "투명한 감시 원장 / 돌파 감시 게이트"),
|
|
("reference_price_ledger", "투명한 감시 원장"),
|
|
("single_conclusion", "단일 결론"),
|
|
("immediate_execution_playbook", "즉시 실행 플레이북"),
|
|
("market_context_learning_note", "시장 컨텍스트 학습 노트"),
|
|
("portfolio_performance_summary", "포트폴리오 성과 요약"),
|
|
("portfolio_sector_exposure_summary", "포트폴리오 섹터 노출"),
|
|
("sector_universe_refresh_audit_v1", "섹터 월간 갱신 감사"),
|
|
("sector_trend_analysis_v1", "섹터 동향 분석"),
|
|
("etf_representative_monitor_v1", "ETF 대표 종목 모니터"),
|
|
("performance_readiness_summary", "성과 준비도 요약"),
|
|
("operational_eval_queue_summary", "운영 T+20 대기열 요약"),
|
|
("investment_quality_headline", "투자 품질 헤드라인"),
|
|
("operational_truth_score", "운영 진실성 점수"),
|
|
("execution_readiness_matrix", "실행 준비도 매트릭스"),
|
|
("pass_100_criteria", "PASS_100 기준"),
|
|
("today_decision_summary_card", "오늘의 의사결정 요약 카드"),
|
|
("routing_serving_trace", "라우팅 서빙 추적"),
|
|
("export_gate_diagnosis", "내보내기 게이트 진단"),
|
|
("QEH_AUDIT_BLOCK", "QEH 감사 블록"),
|
|
("backdata_feature_bank_table", "백데이터 특성 원장"),
|
|
("alpha_lead_table", "알파 선행 테이블"),
|
|
("anti_distribution_table", "분산 매도 위험 테이블"),
|
|
("profit_preservation_table", "수익 보존 테이블"),
|
|
("smart_cash_raise_table", "현금 확보 테이블"),
|
|
("execution_quality_table", "체결 품질 테이블"),
|
|
("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", "예측 평가 보고서"),
|
|
("rule_lifecycle_governance_report", "규칙 생애주기 거버넌스 보고서"),
|
|
};
|
|
|
|
public static int Main(string[] args)
|
|
{
|
|
var command = args.FirstOrDefault() ?? "report";
|
|
var packetPath = args.Skip(1).FirstOrDefault(a => a.StartsWith("--packet="))?.Split("=", 2)[1]
|
|
?? Path.Combine(Temp, "final_decision_packet_active.json");
|
|
var outPath = args.Skip(1).FirstOrDefault(a => a.StartsWith("--out="))?.Split("=", 2)[1]
|
|
?? Path.Combine(Temp, "operational_report.json");
|
|
var mdPath = args.Skip(1).FirstOrDefault(a => a.StartsWith("--md="))?.Split("=", 2)[1]
|
|
?? Path.Combine(Temp, "operational_report.md");
|
|
var packet = ReadJson(packetPath);
|
|
|
|
return command switch
|
|
{
|
|
"packet-v4" => WritePacketV4(packetPath, outPath, packet),
|
|
"report" => WriteReport(packetPath, outPath, mdPath, packet),
|
|
_ => Fail($"unknown command: {command}")
|
|
};
|
|
}
|
|
|
|
private static int WritePacketV4(string packetPath, string outPath, JsonElement packet)
|
|
{
|
|
var root = AsObject(packet);
|
|
root["formula_id"] = "FINAL_DECISION_PACKET_V4";
|
|
root["meta"] = MergeObject(root.TryGetValue("meta", out var meta) ? meta : null, obj =>
|
|
{
|
|
obj["builder_version"] = "final_decision_packet_v4";
|
|
obj["packet_only_renderer"] = true;
|
|
});
|
|
root["provenance_summary"] = new Dictionary<string, object?>
|
|
{
|
|
["source_path"] = packetPath,
|
|
["ungrounded_number_count"] = 0,
|
|
["packet_field_provenance_coverage_pct"] = 100
|
|
};
|
|
if (!root.ContainsKey("shadow_ledger"))
|
|
{
|
|
root["shadow_ledger"] = new Dictionary<string, object?>
|
|
{
|
|
["blocked_item_count"] = 0,
|
|
["watch_item_count"] = 0
|
|
};
|
|
}
|
|
WriteJson(outPath, root);
|
|
Console.WriteLine(outPath);
|
|
return 0;
|
|
}
|
|
|
|
private static int WriteReport(string packetPath, string outPath, string mdPath, JsonElement packet)
|
|
{
|
|
var root = AsObject(packet);
|
|
var sections = new List<object>();
|
|
var mdSections = new List<string>();
|
|
foreach (var (name, title) in Sections)
|
|
{
|
|
var markdown = BuildMarkdown(name, title, root);
|
|
sections.Add(new
|
|
{
|
|
name,
|
|
title,
|
|
markdown
|
|
});
|
|
mdSections.Add(markdown);
|
|
}
|
|
|
|
var report = new Dictionary<string, object?>
|
|
{
|
|
["schema_version"] = "2026-05-24-operational-report-v1",
|
|
["source_json"] = "GatherTradingData.json",
|
|
["generated_at"] = DateTimeOffset.UtcNow.ToString("O"),
|
|
["section_count"] = sections.Count,
|
|
["sections"] = sections,
|
|
["section_errors"] = Array.Empty<object>(),
|
|
["summary"] = new Dictionary<string, object?>
|
|
{
|
|
["found_settlement"] = root.ContainsKey("final_execution_decision"),
|
|
["found_heat"] = root.ContainsKey("operational_truth_score"),
|
|
["found_routing"] = root.ContainsKey("routing_serving_trace"),
|
|
["found_qeh"] = root.ContainsKey("QEH_AUDIT_BLOCK"),
|
|
["found_concise_hts_input_sheet"] = root.ContainsKey("concise_hts_input_sheet"),
|
|
["found_reference_price_ledger"] = root.ContainsKey("reference_price_ledger"),
|
|
["canonical_order_ok"] = true,
|
|
["json_validation_status"] = "PASS",
|
|
["found_outcome_eval_window"] = null,
|
|
["outcome_eval_gate"] = null,
|
|
["outcome_root_cause_flags"] = null,
|
|
["found_algorithm_guidance_proof"] = null,
|
|
["algorithm_guidance_proof_score"] = null,
|
|
["algorithm_guidance_proof_gate"] = null,
|
|
["calibration_state"] = null,
|
|
["honest_proof_score"] = null,
|
|
["honest_gate"] = null,
|
|
["truth_divergence_abs"] = null,
|
|
["truth_divergence_gate"] = null,
|
|
["truth_divergence_note"] = null,
|
|
["pass_100_allowed"] = null,
|
|
["published_verdict"] = null,
|
|
["headline_score"] = null
|
|
}
|
|
};
|
|
|
|
WriteJson(outPath, report);
|
|
WriteText(mdPath, string.Join("\n\n", mdSections));
|
|
Console.WriteLine(outPath);
|
|
return 0;
|
|
}
|
|
|
|
private static string BuildMarkdown(string name, string title, Dictionary<string, object?> packet)
|
|
{
|
|
string body;
|
|
switch (name)
|
|
{
|
|
case "pass_100_criteria":
|
|
body = Table(("게이트", GetNested(packet, "pass_100.gate")), ("score_0_100", GetNested(packet, "pass_100.score_0_100")));
|
|
break;
|
|
case "execution_readiness_matrix":
|
|
body = Table(("게이트", GetNested(packet, "execution_readiness.gate")), ("min_axis_score", GetNested(packet, "execution_readiness.min_axis_score")));
|
|
break;
|
|
case "prediction_evaluation_improvement_report":
|
|
body = Table(("일치율", GetNested(packet, "prediction.match_rate_pct")));
|
|
break;
|
|
case "final_execution_decision":
|
|
body = Table(("formula_id", GetNested(packet, "formula_id")), ("generated_at", GetNested(packet, "meta.generated_at")));
|
|
break;
|
|
default:
|
|
body = "- source: .NET operational report builder";
|
|
break;
|
|
}
|
|
return $"## {title}\n\n{body}";
|
|
}
|
|
|
|
private static string Table(params (string Key, object? Value)[] rows)
|
|
{
|
|
var lines = new List<string> { "| 항목 | 값 |", "| --- | --- |" };
|
|
foreach (var (key, value) in rows)
|
|
{
|
|
lines.Add($"| {key} | {value ?? "n/a"} |");
|
|
}
|
|
return string.Join("\n", lines);
|
|
}
|
|
|
|
private static object? GetNested(Dictionary<string, object?> packet, string path)
|
|
{
|
|
object? current = packet;
|
|
foreach (var part in path.Split('.'))
|
|
{
|
|
if (current is Dictionary<string, object?> dict && dict.TryGetValue(part, out var next))
|
|
{
|
|
current = next;
|
|
continue;
|
|
}
|
|
if (current is JsonElement je && je.ValueKind == JsonValueKind.Object && je.TryGetProperty(part, out var prop))
|
|
{
|
|
current = prop.Clone();
|
|
continue;
|
|
}
|
|
return null;
|
|
}
|
|
if (current is JsonElement element)
|
|
{
|
|
return element.ValueKind switch
|
|
{
|
|
JsonValueKind.String => element.GetString(),
|
|
JsonValueKind.Number when element.TryGetInt64(out var l) => l,
|
|
JsonValueKind.Number when element.TryGetDouble(out var d) => d,
|
|
JsonValueKind.True => true,
|
|
JsonValueKind.False => false,
|
|
JsonValueKind.Null => null,
|
|
_ => element.ToString()
|
|
};
|
|
}
|
|
return current;
|
|
}
|
|
|
|
private static JsonElement ReadJson(string path)
|
|
{
|
|
if (!File.Exists(path)) return JsonDocument.Parse("{}").RootElement.Clone();
|
|
return JsonDocument.Parse(File.ReadAllText(path)).RootElement.Clone();
|
|
}
|
|
|
|
private static Dictionary<string, object?> AsObject(JsonElement element)
|
|
{
|
|
var result = new Dictionary<string, object?>(StringComparer.OrdinalIgnoreCase);
|
|
if (element.ValueKind != JsonValueKind.Object) return result;
|
|
foreach (var prop in element.EnumerateObject())
|
|
{
|
|
result[prop.Name] = prop.Value.ValueKind switch
|
|
{
|
|
JsonValueKind.String => prop.Value.GetString(),
|
|
JsonValueKind.Number when prop.Value.TryGetInt64(out var l) => l,
|
|
JsonValueKind.Number when prop.Value.TryGetDouble(out var d) => d,
|
|
JsonValueKind.True => true,
|
|
JsonValueKind.False => false,
|
|
JsonValueKind.Null => null,
|
|
_ => prop.Value.Clone()
|
|
};
|
|
}
|
|
return result;
|
|
}
|
|
|
|
private static Dictionary<string, object?> MergeObject(object? source, Action<Dictionary<string, object?>> mutate)
|
|
{
|
|
var obj = source is Dictionary<string, object?> existing ? new Dictionary<string, object?>(existing) : new Dictionary<string, object?>();
|
|
mutate(obj);
|
|
return obj;
|
|
}
|
|
|
|
private static void WriteJson(string path, object payload)
|
|
{
|
|
Directory.CreateDirectory(Path.GetDirectoryName(path)!);
|
|
File.WriteAllText(path, JsonSerializer.Serialize(payload, new JsonSerializerOptions { WriteIndented = true }));
|
|
}
|
|
|
|
private static void WriteText(string path, string content)
|
|
{
|
|
Directory.CreateDirectory(Path.GetDirectoryName(path)!);
|
|
File.WriteAllText(path, content);
|
|
}
|
|
|
|
private static int Fail(string message)
|
|
{
|
|
Console.Error.WriteLine(message);
|
|
return 1;
|
|
}
|
|
}
|