#!/usr/bin/env python3
"""#21 — 판정 118.5초가 **무엇 때문인지** 무료로 먼저 가른다.

★사용자 확정(2026-08-28): 제약은 돈이 아니라 **생성 시간**이다. 완주 주행
 127분 중 `scene_image_pipeline` 94.3분(74%), 그 안 판정이 49.4분(전체 39%).
 같은 함수가 별도 프로세스에서 28~43초인데 백엔드 안에서 118.5초 — **3~4배**.

★추측으로 고치지 않는다. 첫 물음은 **「그 시간이 대기인가 처리인가」**다.

가르는 법 — 유료 호출 0, Opik span 시각만 읽는다:

  ① 판정 콜들이 **겹쳤나**  겹쳤으면 각 콜의 벽시계에 **남의 대기**가 섞인다.
     그러면 「콜당 118초」는 처리 시간이 아니라 **혼잡**이다.
  ② 겹치지 않았다면 진짜로 콜 하나가 118초다 — 그때는 provider 대기인지
     우리 쪽 처리인지를 다음 단계에서 계측한다.
  ③ 같은 시간창의 **다른 계열**(이미지 생성 등)과의 겹침도 본다 — 판정만
     느린 것인지 그 시각 전체가 느린 것인지.

★내 오늘 실측 습관: avg × count 로 합을 만들면 순환 논리다. **실제 시각**을
 읽는다.
"""
import sys
from collections import Counter

sys.path.insert(0, "/Users/manta/Documents/Projects/TheRoad-I1/scratchpad")
from _opik_env import opik_target  # noqa: E402
from tools.opik_prompt_audit.audit.fetch import fetch_spans  # noqa: E402

BASE, WS, PROJ = opik_target()
# 완주 주행 = 2026-08-28 12:37~14:45 KST = 03:37~05:45 UTC
SINCE, UNTIL = "2026-08-28T03:30:00", "2026-08-28T06:00:00"


def ms(span):
    """(시작, 끝) — Opik span 의 시각 두 칸. 없으면 None."""
    a, b = span.get("start_time"), span.get("end_time")
    return (str(a)[:23], str(b)[:23]) if a and b else None


def to_sec(t: str) -> float:
    """`2026-08-28T04:12:33.123` → 초. 날짜가 하루라 시분초만 쓴다."""
    hh, mm, rest = t.split("T")[1].split(":")[:3][0], "", ""
    part = t.split("T")[1]
    h, m, s = part.split(":")
    return int(h) * 3600 + int(m) * 60 + float(s)


spans = fetch_spans(BASE, WS, PROJ, SINCE, UNTIL)
print(f"주행 시간창 span {len(spans)}")

# 판정 계열 — 모델로 고른다(grok-4.6 이 판정 텍스트 모델)
judge = [s for s in spans if (s.get("model") or "") == "x-ai/grok-4.6"]
print(f"판정(grok-4.6) span {len(judge)}")
if not judge:
    raise SystemExit("★판정 span 을 못 찾았다 — 모델 이름이나 시간창을 확인하라.")

iv = []
for s in judge:
    t = ms(s)
    if t:
        iv.append((to_sec(t[0]), to_sec(t[1]), s))
iv.sort()
print(f"시각 두 칸이 다 있는 것 {len(iv)}/{len(judge)}")

durs = [b - a for a, b, _ in iv]
durs.sort()
print(f"\n── 콜 하나의 벽시계 ──")
print(f"  중앙값 {durs[len(durs)//2]:.1f}s · 최소 {durs[0]:.1f}s · "
      f"최대 {durs[-1]:.1f}s · 합 {sum(durs):.0f}s ({sum(durs)/60:.1f}분)")

# ── ① 겹침 ────────────────────────────────────────────────────
overlap_pairs = 0
max_concurrent = 0
events = []
for a, b, _ in iv:
    events.append((a, +1))
    events.append((b, -1))
events.sort()
cur = 0
for _, d in events:
    cur += d
    max_concurrent = max(max_concurrent, cur)
for i in range(len(iv)):
    for j in range(i + 1, len(iv)):
        if iv[j][0] < iv[i][1]:
            overlap_pairs += 1
        else:
            break

span_wall = iv[-1][1] - iv[0][0]
print(f"\n── ① 겹쳤나 ──")
print(f"  동시 최대 {max_concurrent}개 · 겹친 쌍 {overlap_pairs}")
print(f"  첫 콜 시작 ~ 마지막 콜 끝 = {span_wall:.0f}s ({span_wall/60:.1f}분)")
print(f"  콜 벽시계 합 = {sum(durs):.0f}s ({sum(durs)/60:.1f}분)")
ratio = sum(durs) / span_wall if span_wall else 0
print(f"  합/구간 = {ratio:.2f}  "
      f"→ {'★겹침 있음 (합이 구간보다 큼)' if ratio > 1.05 else '겹침 거의 없음 — 순차'}")

# ── ③ 같은 시각 다른 계열 ────────────────────────────────────
others = [s for s in spans
          if (s.get("model") or "") != "x-ai/grok-4.6" and ms(s)]
oiv = []
for s in others:
    t = ms(s)
    oiv.append((to_sec(t[0]), to_sec(t[1]), s.get("model") or "?"))
busy = 0
for a, b, _ in oiv:
    for ja, jb, _ in iv:
        if a < jb and ja < b:
            busy += 1
            break
print(f"\n── ③ 판정과 겹친 **다른 계열** span ──")
print(f"  {busy}/{len(oiv)} 이 판정 구간과 겹친다")
print("  모델별:", dict(Counter(
    m for a, b, m in oiv
    if any(a < jb and ja < b for ja, jb, _ in iv))))
