#!/usr/bin/env python3
"""
validate_capital_style_allocation_v1.py
───────────────────────────────────────────────────────────────────────────────
CAPITAL_STYLE_ALLOCATION_V1 검증기

결정론 및 범위 체크:
  (1) conviction_score ∈ [0, 100]
  (2) recommended_pct ∈ {0.0, 1.5, 3.0, 5.0, 7.0}
  (3) 가중치 합 = 1.0 (각 스타일별)
  (4) LLM 계산 금지 확인 (meta.llm_computed=false)
  (5) 결정론 확인 (meta.deterministic=true)
  (6) 모든 종목에 4개 스타일 행 존재
  (7) gate=PASS이면 errors=[]

출력 토큰: CAPITAL_ALLOC_OK / CAPITAL_ALLOC_FAIL
"""
from __future__ import annotations

import json
import sys
from pathlib import Path

ROOT   = Path(__file__).resolve().parent.parent
INPUT  = ROOT / "Temp" / "capital_style_allocation_v1.json"
OUTPUT = ROOT / "Temp" / "capital_style_allocation_validation_v1.json"

if sys.stdout.encoding and sys.stdout.encoding.lower() not in ("utf-8", "utf8"):
    sys.stdout = open(sys.stdout.fileno(), mode="w", encoding="utf-8", buffering=1)

VALID_STYLES    = {"SCALP", "SWING", "MOMENTUM", "POSITION"}
VALID_REC_PCTS  = {0.0, 1.5, 3.0, 5.0, 7.0}
WEIGHT_SUM_TOL  = 0.001


def main() -> int:
    if not INPUT.exists():
        print("CAPITAL_ALLOC_FAIL")
        print(f"  MISSING: {INPUT}")
        print("  먼저 build_capital_style_allocation_v1.py 실행 필요")
        return 1

    data = json.loads(INPUT.read_text(encoding="utf-8"))
    violations: list[str] = []
    warnings:   list[str] = []

    # (4) LLM 계산 금지
    meta = data.get("meta", {})
    if meta.get("llm_computed") is not False:
        violations.append("meta.llm_computed must be false")

    # (5) 결정론
    if meta.get("deterministic") is not True:
        violations.append("meta.deterministic must be true")

    # gate 체크
    gate = data.get("gate")
    if gate != "PASS":
        violations.append(f"gate={gate!r} (expected PASS)")

    errors_field = data.get("errors", [])
    if errors_field:
        violations.append(f"errors field non-empty: {errors_field[:3]}")

    # (3) 가중치 합 검증
    weights = data.get("weights", {})
    for style, w in weights.items():
        total = sum(w.values())
        if abs(total - 1.0) > WEIGHT_SUM_TOL:
            violations.append(f"weights[{style}] sum={total:.4f} != 1.0")

    rows = data.get("rows", [])
    if not rows:
        violations.append("rows is empty — no tickers processed")

    # 종목별 검증
    for row in rows:
        ticker = row.get("ticker", "?")
        styles = row.get("styles", [])

        # (6) 4개 스타일 존재
        style_names = {s.get("style") for s in styles}
        missing = VALID_STYLES - style_names
        if missing:
            violations.append(f"{ticker}: missing styles {missing}")

        for s in styles:
            sname = s.get("style", "?")
            conv  = s.get("conviction_score")
            rpct  = s.get("recommended_pct")

            # (1) conviction_score 범위
            if conv is None or not isinstance(conv, (int, float)):
                violations.append(f"{ticker}.{sname}: conviction_score missing or non-numeric")
            elif not (0.0 <= float(conv) <= 100.0):
                violations.append(f"{ticker}.{sname}: conviction_score={conv} not in [0,100]")

            # (2) recommended_pct 유효값
            if rpct is None:
                violations.append(f"{ticker}.{sname}: recommended_pct missing")
            elif float(rpct) not in VALID_REC_PCTS:
                violations.append(f"{ticker}.{sname}: recommended_pct={rpct} not in {VALID_REC_PCTS}")

        # 신호 분해 존재
        sb = row.get("signal_breakdown", {})
        for field in ("technical_score", "smart_money_score", "fundamental_score",
                      "macro_event_score", "liquidity_modifier"):
            if field not in sb:
                warnings.append(f"{ticker}: signal_breakdown missing {field}")

    # ── 결과 ───────────────────────────────────────────────────────────────
    status = "CAPITAL_ALLOC_OK" if not violations else "CAPITAL_ALLOC_FAIL"

    result = {
        "status":         status,
        "violation_count": len(violations),
        "warning_count":  len(warnings),
        "violations":     violations,
        "warnings":       warnings,
        "ticker_count":   len(rows),
        "styles_checked": sorted(VALID_STYLES),
        "gate_from_build": gate,
    }

    OUTPUT.parent.mkdir(parents=True, exist_ok=True)
    OUTPUT.write_text(json.dumps(result, ensure_ascii=False, indent=2), encoding="utf-8")

    sep = "=" * 70
    print(sep)
    print("  CAPITAL_STYLE_ALLOCATION_V1 검증기")
    print(sep)
    print(f"  tickers={len(rows)}  violations={len(violations)}  warnings={len(warnings)}")
    if violations:
        print("\n  [위반]:")
        for v in violations:
            print(f"    {v}")
    if warnings:
        print(f"\n  [경고] {len(warnings)}건 (허용)")
    print(f"\n  → 저장: {OUTPUT}")
    print(f"  {status}\n")

    return 0 if not violations else 1


if __name__ == "__main__":
    raise SystemExit(main())
