"""FINAL_JUDGMENT_GATE_V1
판단 결정론 계층 — 모든 게이트·신호 JSON + _harness_context를 읽어
종목별 단일 action_verdict를 결정론으로 산출한다.

action_verdict ∈ {BUY_PILOT, HOLD, TRIM, SELL, WATCH, BLOCKED}

매도 판단 우선순위:
  1. SELL   : stop_breach=BREACH  OR  sell_waterfall stage_label ∈ {EMERGENCY_FULL, DISTRIBUTION_EXIT}
  2. TRIM   : sell_waterfall stage_label ∈ {URGENT_TRIM, TRIM_ONLY}
  3. TP_TRIM: tp_trigger=TRIGGERED (미래 확장 예약)

매수 판단 AND-11 조건 (전부 PASS여야 BUY_PILOT):
  J01. anti_late_entry(alpha_lead buy_permission_state ≠ BLOCKED)
  J02. distribution(anti_distribution_state ≠ BLOCK_BUY)
  J03. breakout(Breakout_Gate ≠ WAIT  OR  lead_entry_state ≠ BLOCKED_LATE_CHASE)
  J04. smart_money_liquidity(gate_status ≠ BLOCK_BUY)
  J05. liquidity(execution_mode ≠ FROZEN)
  J06. macro(primary_gate ∉ {AVOID_NEW_BUY, HEDGE})
  J07. fundamental(grade ∈ {A,B,C} — ETF 제외)
  J08. heat_gate(heat_gate_status ≠ BLOCK_NEW_BUY)
  J09. cash_floor(cash_floor_status == PASS)
  J10. position_count(position_count_gate ≠ POSITION_COUNT_BLOCK)
  J11. single_position_weight(single_position_weight_gate ≠ OVERWEIGHT_TRIM)

harness_key 부재 → DATA_MISSING (silent PASS 금지)

effective_confidence = round(raw_confidence × (0.4 + 0.6 × invest_quality_score/100), 1)
"""
from __future__ import annotations

import argparse
import json
import sys
from pathlib import Path
from typing import Any


def _ensure_utf8_stdio() -> None:
    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)
    if sys.stderr.encoding and sys.stderr.encoding.lower() not in ("utf-8", "utf8"):
        sys.stderr = open(sys.stderr.fileno(), mode="w", encoding="utf-8", buffering=1)


ROOT = Path(__file__).resolve().parents[1]
DEFAULT_JSON = ROOT / "GatherTradingData.json"
DEFAULT_SM_GATE = ROOT / "Temp" / "smart_money_liquidity_gate_v1.json"
DEFAULT_LIQUIDITY = ROOT / "Temp" / "liquidity_flow_signal_v1.json"
DEFAULT_MACRO = ROOT / "Temp" / "macro_event_ticker_impact_v1.json"
DEFAULT_FUNDAMENTAL = ROOT / "Temp" / "fundamental_multifactor_v3.json"
DEFAULT_HORIZON = ROOT / "Temp" / "horizon_classification_v1.json"
DEFAULT_SELL_WATERFALL = ROOT / "Temp" / "sell_waterfall_engine_v2.json"
DEFAULT_DQ_RECONCILE = ROOT / "Temp" / "data_quality_reconciliation_v1.json"
DEFAULT_OUT = ROOT / "Temp" / "final_judgment_gate_v1.json"

# 매수 차단 집합
MACRO_BLOCK_NEW_BUY = {"AVOID_NEW_BUY", "HEDGE"}
FUND_PASS_GRADES = {"A", "B", "C"}
HEAT_BLOCK_VALUES = {"BLOCK_NEW_BUY"}
CASH_PASS_VALUES = {"PASS"}
POSITION_COUNT_BLOCK_VALUES = {"POSITION_COUNT_BLOCK"}
SINGLE_WEIGHT_BLOCK_VALUES = {"OVERWEIGHT_TRIM", "OVERWEIGHT_BLOCK"}

# 매도 waterfall stage 레이블 → verdict 매핑
SELL_STAGE_LABELS = {"EMERGENCY_FULL", "DISTRIBUTION_EXIT"}
TRIM_STAGE_LABELS = {"URGENT_TRIM", "TRIM_ONLY"}

# BUY AND-11 게이트 키
AND_GATE_KEYS = [
    "J01_anti_late_entry",
    "J02_distribution",
    "J03_breakout",
    "J04_smart_money_liquidity",
    "J05_liquidity",
    "J06_macro",
    "J07_fundamental",
    "J08_heat_gate",
    "J09_cash_floor",
    "J10_position_count",
    "J11_single_position_weight",
]


def _load_json(path: Path) -> dict[str, Any] | list:
    if not path.exists():
        return {}
    try:
        obj = json.loads(path.read_text(encoding="utf-8"))
    except Exception:
        return {}
    return obj


def _as_dict(obj: Any) -> dict[str, Any]:
    if isinstance(obj, dict):
        return obj
    return {}


def _extract_harness_root(payload: dict[str, Any]) -> dict[str, Any]:
    h_apex = payload.get("hApex")
    data_apex = ((payload.get("data") or {}).get("_harness_context")) if isinstance(payload.get("data"), dict) else None
    if isinstance(h_apex, dict) and isinstance(data_apex, dict):
        merged = dict(data_apex)
        merged.update(h_apex)
        return merged
    if isinstance(h_apex, dict):
        return h_apex
    if isinstance(data_apex, dict):
        return data_apex
    return payload


def _extract_data_feed(payload: dict[str, Any]) -> list[dict[str, Any]]:
    data_node = payload.get("data") or {}
    if isinstance(data_node, dict):
        feed = data_node.get("data_feed")
        if isinstance(feed, list):
            return feed
    return []


def _rows_by_ticker(obj: Any) -> dict[str, dict[str, Any]]:
    """JSON 산출물(list 또는 {rows:[]} dict)을 ticker→row dict로 변환."""
    rows: list[dict[str, Any]] = []
    if isinstance(obj, list):
        rows = [r for r in obj if isinstance(r, dict)]
    elif isinstance(obj, dict):
        for key in ("rows", "tickers", "data"):
            candidate = obj.get(key)
            if isinstance(candidate, list):
                rows = [r for r in candidate if isinstance(r, dict)]
                break
    return {str(r.get("ticker") or ""): r for r in rows if r.get("ticker")}


def _parse_hctx_list(hctx: dict[str, Any], key: str) -> dict[str, dict[str, Any]]:
    val = hctx.get(key)
    if isinstance(val, str):
        try:
            val = json.loads(val)
        except Exception:
            return {}
    return _rows_by_ticker(val or {})


def _as_float(v: Any, default: float = 0.0) -> float:
    try:
        return float(v)
    except Exception:
        return default


# ─────────────────────────────────────────────
# BUY AND-11 게이트 평가 함수들
# ─────────────────────────────────────────────

def _gate_result(gate_key: str, status: str, value: Any, detail: str = "") -> dict[str, Any]:
    return {"gate": gate_key, "status": status, "value": value, "detail": detail}


def _eval_j01_anti_late_entry(
    alpha_row: dict[str, Any] | None,
    df_row: dict[str, Any],
) -> dict[str, Any]:
    """J01: anti_late_entry — alpha_lead buy_permission_state ≠ BLOCKED."""
    if alpha_row is None:
        return _gate_result("J01_anti_late_entry", "DATA_MISSING", None, "alpha_lead_json row not found")
    bps = str(alpha_row.get("buy_permission_state") or "")
    passed = bps != "BLOCKED"
    return _gate_result(
        "J01_anti_late_entry",
        "PASS" if passed else "BLOCK",
        bps,
        str(alpha_row.get("lead_entry_state") or ""),
    )


def _eval_j02_distribution(dist_row: dict[str, Any] | None) -> dict[str, Any]:
    """J02: distribution — anti_distribution_state ≠ BLOCK_BUY."""
    if dist_row is None:
        return _gate_result("J02_distribution", "DATA_MISSING", None, "distribution_risk_json row not found")
    ads = str(dist_row.get("anti_distribution_state") or "")
    passed = ads != "BLOCK_BUY"
    return _gate_result(
        "J02_distribution",
        "PASS" if passed else "BLOCK",
        ads,
        f"score={dist_row.get('distribution_risk_score')}",
    )


def _eval_j03_breakout(
    alpha_row: dict[str, Any] | None,
    df_row: dict[str, Any],
) -> dict[str, Any]:
    """J03: breakout — Breakout_Gate ≠ WAIT OR alpha_lead confirmed."""
    breakout_gate = str(df_row.get("Breakout_Gate") or "MISSING")
    if alpha_row is not None:
        les = str(alpha_row.get("lead_entry_state") or "")
        confirmed = breakout_gate not in {"WAIT", "MISSING"} or les not in {"BLOCKED_LATE_CHASE", "WATCH"}
    else:
        confirmed = breakout_gate not in {"WAIT", "MISSING"}
    return _gate_result(
        "J03_breakout",
        "PASS" if confirmed else "BLOCK",
        breakout_gate,
        f"Breakout_Score={df_row.get('Breakout_Score')}",
    )


def _eval_j04_smart_money(sm_row: dict[str, Any] | None) -> dict[str, Any]:
    """J04: smart_money_liquidity gate_status ≠ BLOCK_BUY."""
    if sm_row is None:
        return _gate_result("J04_smart_money_liquidity", "DATA_MISSING", None, "smart_money_liquidity_gate row not found")
    gs = str(sm_row.get("gate_status") or "")
    passed = gs != "BLOCK_BUY"
    return _gate_result("J04_smart_money_liquidity", "PASS" if passed else "BLOCK", gs)


def _eval_j05_liquidity(lq_row: dict[str, Any] | None) -> dict[str, Any]:
    """J05: liquidity execution_mode ≠ FROZEN."""
    if lq_row is None:
        return _gate_result("J05_liquidity", "DATA_MISSING", None, "liquidity_flow_signal row not found")
    em = str(lq_row.get("execution_mode") or "")
    passed = em != "FROZEN"
    return _gate_result("J05_liquidity", "PASS" if passed else "BLOCK", em)


def _eval_j06_macro(macro_row: dict[str, Any] | None) -> dict[str, Any]:
    """J06: macro primary_gate ∉ {AVOID_NEW_BUY, HEDGE}."""
    if macro_row is None:
        return _gate_result("J06_macro", "DATA_MISSING", None, "macro_event_ticker_impact row not found")
    pg = str(macro_row.get("primary_gate") or "")
    passed = pg not in MACRO_BLOCK_NEW_BUY
    return _gate_result("J06_macro", "PASS" if passed else "BLOCK", pg)


def _eval_j07_fundamental(fund_row: dict[str, Any] | None, is_etf: bool) -> dict[str, Any]:
    """J07: fundamental grade ∈ {A,B,C} — ETF 제외."""
    if is_etf:
        return _gate_result("J07_fundamental", "EXEMPT", "ETF", "ETF는 펀더멘털 게이트 면제")
    if fund_row is None:
        return _gate_result("J07_fundamental", "DATA_MISSING", None, "fundamental_multifactor row not found")
    grade = str(fund_row.get("grade") or "")
    passed = grade in FUND_PASS_GRADES
    return _gate_result("J07_fundamental", "PASS" if passed else "BLOCK", grade)


def _eval_j08_heat(hctx: dict[str, Any]) -> dict[str, Any]:
    """J08: heat_gate_status ≠ BLOCK_NEW_BUY (포트폴리오 레벨)."""
    status = str(hctx.get("heat_gate_status") or "")
    if not status:
        return _gate_result("J08_heat_gate", "DATA_MISSING", None, "heat_gate_status not found")
    passed = status not in HEAT_BLOCK_VALUES
    return _gate_result("J08_heat_gate", "PASS" if passed else "BLOCK", status)


def _eval_j09_cash_floor(hctx: dict[str, Any]) -> dict[str, Any]:
    """J09: cash_floor_status == PASS."""
    status = str(hctx.get("cash_floor_status") or "")
    if not status:
        return _gate_result("J09_cash_floor", "DATA_MISSING", None, "cash_floor_status not found")
    passed = status in CASH_PASS_VALUES
    return _gate_result("J09_cash_floor", "PASS" if passed else "BLOCK", status)


def _eval_j10_position_count(hctx: dict[str, Any]) -> dict[str, Any]:
    """J10: position_count_gate ≠ POSITION_COUNT_BLOCK."""
    status = str(hctx.get("position_count_gate") or "")
    if not status:
        return _gate_result("J10_position_count", "DATA_MISSING", None, "position_count_gate not found")
    passed = status not in POSITION_COUNT_BLOCK_VALUES
    return _gate_result("J10_position_count", "PASS" if passed else "BLOCK", status)


def _eval_j11_single_weight(hctx: dict[str, Any]) -> dict[str, Any]:
    """J11: single_position_weight_gate ≠ OVERWEIGHT (포트폴리오 레벨)."""
    status = str(hctx.get("single_position_weight_gate") or "")
    if not status:
        return _gate_result("J11_single_position_weight", "DATA_MISSING", None, "single_position_weight_gate not found")
    passed = status not in SINGLE_WEIGHT_BLOCK_VALUES
    return _gate_result("J11_single_position_weight", "PASS" if passed else "BLOCK", status)


# ─────────────────────────────────────────────
# 매도 신호 평가
# ─────────────────────────────────────────────

def _eval_stop_breach(sb_row: dict[str, Any] | None) -> str:
    """BREACH → SELL, APPROACHING → WARN, else SAFE."""
    if sb_row is None:
        return "SAFE"
    return str(sb_row.get("stop_breach_gate") or "SAFE")


def _eval_tp_trigger(tp_row: dict[str, Any] | None) -> str:
    """TP 트리거 상태."""
    if tp_row is None:
        return "NONE"
    return str(tp_row.get("tp_trigger_gate") or "NONE")


def _eval_sell_waterfall(sw_row: dict[str, Any] | None) -> tuple[int, str]:
    """(stage, stage_label) — 미존재 시 (0, NONE)."""
    if sw_row is None:
        return 0, "NONE"
    stage = int(sw_row.get("stage") or 0)
    label = str(sw_row.get("stage_label") or "NONE")
    return stage, label


# ─────────────────────────────────────────────
# 종목별 판단 집약
# ─────────────────────────────────────────────

def _compute_raw_confidence(and_trace: list[dict[str, Any]]) -> float:
    """AND-11 결과로부터 원시 신뢰도 산출 (PASS/EXEMPT=1.0, DATA_MISSING=0.5, BLOCK=0.0)."""
    if not and_trace:
        return 0.0
    weights = {"PASS": 1.0, "EXEMPT": 1.0, "DATA_MISSING": 0.5, "BLOCK": 0.0}
    total = sum(weights.get(g["status"], 0.0) for g in and_trace)
    return round(total / len(and_trace) * 100.0, 1)


def _compute_effective_confidence(raw: float, invest_quality_score: float) -> float:
    """effective_confidence = raw × (0.4 + 0.6 × iq/100)."""
    cap_factor = 0.4 + 0.6 * (invest_quality_score / 100.0)
    return round(raw * cap_factor, 1)


def _evaluate_ticker(
    df_row: dict[str, Any],
    hctx: dict[str, Any],
    alpha_by_ticker: dict[str, dict],
    dist_by_ticker: dict[str, dict],
    sm_by_ticker: dict[str, dict],
    lq_by_ticker: dict[str, dict],
    macro_by_ticker: dict[str, dict],
    fund_by_ticker: dict[str, dict],
    horizon_by_ticker: dict[str, dict],
    sb_by_ticker: dict[str, dict],
    tp_by_ticker: dict[str, dict],
    sw_by_ticker: dict[str, dict],
    invest_quality_score: float,
) -> dict[str, Any]:
    ticker = str(df_row.get("Ticker") or "UNKNOWN")
    name = str(df_row.get("Name") or "")
    is_etf = str(df_row.get("SS001_Grade") or "") == "ETF" or ticker in {"0117V0", "494670", "471990", "091160"}

    # fund row로 is_etf 재확인
    fund_row = fund_by_ticker.get(ticker)
    if fund_row is not None:
        is_etf = bool(fund_row.get("is_etf") or False)

    # AND-11 게이트 평가
    and_trace: list[dict[str, Any]] = [
        _eval_j01_anti_late_entry(alpha_by_ticker.get(ticker), df_row),
        _eval_j02_distribution(dist_by_ticker.get(ticker)),
        _eval_j03_breakout(alpha_by_ticker.get(ticker), df_row),
        _eval_j04_smart_money(sm_by_ticker.get(ticker)),
        _eval_j05_liquidity(lq_by_ticker.get(ticker)),
        _eval_j06_macro(macro_by_ticker.get(ticker)),
        _eval_j07_fundamental(fund_row, is_etf),
        _eval_j08_heat(hctx),
        _eval_j09_cash_floor(hctx),
        _eval_j10_position_count(hctx),
        _eval_j11_single_weight(hctx),
    ]

    blocking_gates = [g["gate"] for g in and_trace if g["status"] == "BLOCK"]
    data_missing_gates = [g["gate"] for g in and_trace if g["status"] == "DATA_MISSING"]

    # 매도 신호 평가
    stop_breach_status = _eval_stop_breach(sb_by_ticker.get(ticker))
    tp_status = _eval_tp_trigger(tp_by_ticker.get(ticker))
    sw_stage, sw_label = _eval_sell_waterfall(sw_by_ticker.get(ticker))

    # 호라이즌
    horiz_row = horizon_by_ticker.get(ticker)
    horizon = str((horiz_row or {}).get("horizon") or "UNKNOWN")

    # 신뢰도 산출
    raw_confidence = _compute_raw_confidence(and_trace)
    effective_confidence = _compute_effective_confidence(raw_confidence, invest_quality_score)

    # ── Verdict 결정 (우선순위 엄격 적용) ──────────────────────────────────
    verdict_reason: list[str] = []

    if stop_breach_status == "BREACH":
        action_verdict = "SELL"
        verdict_reason.append(f"stop_breach=BREACH")
    elif sw_label in SELL_STAGE_LABELS:
        action_verdict = "SELL"
        verdict_reason.append(f"sell_waterfall={sw_label}(stage={sw_stage})")
    elif sw_label in TRIM_STAGE_LABELS and sw_stage > 0:
        action_verdict = "TRIM"
        verdict_reason.append(f"sell_waterfall={sw_label}(stage={sw_stage})")
    elif tp_status == "TRIGGERED":
        action_verdict = "TRIM"
        verdict_reason.append(f"tp_trigger=TRIGGERED")
    elif not blocking_gates and not data_missing_gates:
        action_verdict = "BUY_PILOT"
        verdict_reason.append("all_AND11_pass")
    elif blocking_gates:
        action_verdict = "BLOCKED"
        verdict_reason.extend([f"blocked_by:{g}" for g in blocking_gates[:3]])
    elif data_missing_gates:
        action_verdict = "WATCH"
        verdict_reason.append(f"data_missing:{len(data_missing_gates)}gates")
    else:
        action_verdict = "HOLD"
        verdict_reason.append("no_active_signals")

    # stop_approaching → WATCH 강등(HOLD만 해당, SELL/TRIM 아닌 경우)
    if stop_breach_status == "APPROACHING" and action_verdict in {"HOLD", "BUY_PILOT"}:
        action_verdict = "WATCH"
        verdict_reason.append("stop_approaching")

    return {
        "ticker": ticker,
        "name": name,
        "action_verdict": action_verdict,
        "verdict_reason": verdict_reason,
        "raw_confidence": raw_confidence,
        "effective_confidence": effective_confidence,
        "invest_quality_cap_factor": round(0.4 + 0.6 * (invest_quality_score / 100.0), 3),
        "horizon": horizon,
        "is_etf": is_etf,
        "and_trace": and_trace,
        "blocking_gates": blocking_gates,
        "data_missing_gates": data_missing_gates,
        "sell_signals": {
            "stop_breach_gate": stop_breach_status,
            "tp_trigger_gate": tp_status,
            "sell_waterfall_stage": sw_stage,
            "sell_waterfall_label": sw_label,
        },
        "formula_id": "FINAL_JUDGMENT_GATE_V1",
    }


def main() -> int:
    _ensure_utf8_stdio()
    ap = argparse.ArgumentParser(description="FINAL_JUDGMENT_GATE_V1 — 판단 결정론 계층")
    ap.add_argument("--json", default=str(DEFAULT_JSON))
    ap.add_argument("--sm-gate", default=str(DEFAULT_SM_GATE))
    ap.add_argument("--liquidity", default=str(DEFAULT_LIQUIDITY))
    ap.add_argument("--macro", default=str(DEFAULT_MACRO))
    ap.add_argument("--fundamental", default=str(DEFAULT_FUNDAMENTAL))
    ap.add_argument("--horizon", default=str(DEFAULT_HORIZON))
    ap.add_argument("--sell-waterfall", default=str(DEFAULT_SELL_WATERFALL))
    ap.add_argument("--dq-reconcile", default=str(DEFAULT_DQ_RECONCILE))
    ap.add_argument("--out", default=str(DEFAULT_OUT))
    args = ap.parse_args()

    def _rp(s: str) -> Path:
        p = Path(s)
        return p if p.is_absolute() else ROOT / p

    json_path = _rp(args.json)
    out_path = _rp(args.out)

    # ─── 데이터 로드 ─────────────────────────────────────────────────────
    main_data = _as_dict(_load_json(json_path))
    hctx = _extract_harness_root(main_data)
    feed = _extract_data_feed(main_data)
    if not feed:
        print("ERROR: data_feed 비어 있음", file=sys.stderr)
        return 1

    sm_gate = _as_dict(_load_json(_rp(args.sm_gate)))
    lq_data = _load_json(_rp(args.liquidity))
    macro_data = _as_dict(_load_json(_rp(args.macro)))
    fund_data = _as_dict(_load_json(_rp(args.fundamental)))
    horizon_data = _as_dict(_load_json(_rp(args.horizon)))
    sw_data = _as_dict(_load_json(_rp(args.sell_waterfall)))
    dq_data = _as_dict(_load_json(_rp(args.dq_reconcile)))

    invest_quality_source_score = float(dq_data.get("investment_quality_score") or 0.0)
    invest_quality_cap_basis_score = float(dq_data.get("confidence_cap_basis_score") or invest_quality_source_score or 0.0)
    invest_quality_score = invest_quality_cap_basis_score or invest_quality_source_score

    # harness_context에서 per-ticker JSON 추출
    alpha_by_ticker = _parse_hctx_list(hctx, "alpha_lead_json")
    dist_by_ticker = _parse_hctx_list(hctx, "distribution_risk_json")
    sb_by_ticker = _parse_hctx_list(hctx, "stop_breach_alert_json")
    tp_by_ticker = _parse_hctx_list(hctx, "tp_trigger_alert_json")

    # 외부 JSON 파일에서 per-ticker 추출
    sm_by_ticker = _rows_by_ticker(sm_gate)
    lq_by_ticker = _rows_by_ticker(lq_data)
    macro_by_ticker = _rows_by_ticker(macro_data)
    fund_by_ticker = _rows_by_ticker(fund_data)
    horizon_by_ticker = _rows_by_ticker(horizon_data)
    sw_by_ticker = _rows_by_ticker(sw_data)

    # ─── 평가 실행 ────────────────────────────────────────────────────────
    rows: list[dict[str, Any]] = []
    verdict_counts: dict[str, int] = {}
    silent_pass_violations = 0  # 반드시 0이어야 함

    for df_row in feed:
        result = _evaluate_ticker(
            df_row, hctx,
            alpha_by_ticker, dist_by_ticker, sm_by_ticker,
            lq_by_ticker, macro_by_ticker, fund_by_ticker,
            horizon_by_ticker, sb_by_ticker, tp_by_ticker,
            sw_by_ticker, invest_quality_score,
        )
        rows.append(result)
        v = result["action_verdict"]
        verdict_counts[v] = verdict_counts.get(v, 0) + 1

        # silent PASS 감시: DATA_MISSING이 있는데 BUY_PILOT이면 위반
        if result["data_missing_gates"] and v == "BUY_PILOT":
            silent_pass_violations += 1

    # 뒷박 후보 검증 (velocity≥3% OR distribution≥2.0인데 BUY_PILOT이면 위반)
    late_chase_buy_violations = []
    for r in rows:
        if r["action_verdict"] != "BUY_PILOT":
            continue
        ticker = r["ticker"]
        df_row = next((x for x in feed if x.get("Ticker") == ticker), {})
        ret5d = abs(float(df_row.get("Ret5D") or 0.0))
        dist_r = dist_by_ticker.get(ticker) or {}
        dist_score = int(dist_r.get("distribution_risk_score") or 0)
        if ret5d >= 3.0 or dist_score >= 60:
            late_chase_buy_violations.append({"ticker": ticker, "ret5d": ret5d, "dist_score": dist_score})

    coverage_pct = round(100.0 * len(rows) / max(1, len(feed)), 2)
    gate_ok = "PASS" if (silent_pass_violations == 0 and not late_chase_buy_violations) else "FAIL"

    out = {
        "formula_id": "FINAL_JUDGMENT_GATE_V1",
        "gate": gate_ok,
        "coverage_pct": coverage_pct,
        "ticker_count": len(rows),
        "invest_quality_score": invest_quality_score,
        "invest_quality_source_score": invest_quality_source_score,
        "invest_quality_cap_basis_score": invest_quality_cap_basis_score,
        "invest_quality_cap_factor": round(0.4 + 0.6 * (invest_quality_score / 100.0), 3),
        "verdict_counts": verdict_counts,
        "silent_pass_violations": silent_pass_violations,
        "late_chase_buy_violations": late_chase_buy_violations,
        "rows": rows,
    }

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

    print("FINAL_JUDGMENT_GATE_V1")
    print(f"  tickers: {len(rows)}/{len(feed)} (coverage={coverage_pct}%)")
    print(f"  invest_quality_score: {invest_quality_score} (source={invest_quality_source_score}) → cap_factor={round(0.4+0.6*invest_quality_score/100,3)}")
    print(f"  verdict_counts: {verdict_counts}")
    print(f"  silent_pass_violations: {silent_pass_violations}  (반드시 0)")
    print(f"  late_chase_buy_violations: {len(late_chase_buy_violations)}  (반드시 0)")
    print(f"  gate: {gate_ok}")
    print()
    print(f"  {'TICKER':<10} {'VERDICT':<12} {'RAW_CONF':>8} {'EFF_CONF':>8}  BLOCKING_GATES")
    for r in rows:
        blocking = ",".join(r['blocking_gates'][:3]) if r['blocking_gates'] else "NONE"
        print(f"  {r['ticker']:<10} {r['action_verdict']:<12} {r['raw_confidence']:>8.1f} {r['effective_confidence']:>8.1f}  {blocking}")
    return 0 if gate_ok == "PASS" else 1


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