#!/usr/bin/env python3
"""vnkr 해설보기 비교 결과 → 공용 스타일 문서 생성.

전환된 기준: luna 응답은 서식 화이트리스트 규칙(v3)이 반영된 프롬프트로 생성된 최종본,
비용은 2026-07-30 인하 단가(luna $0.20/$1.20 per M) 기준.
"""
import json, html, re

SP = "/private/tmp/claude-501/-Users-ymjung-Project-wordbit2/4ac326f2-ee60-44f4-ab5c-4b7aff5d25e6/scratchpad"
OUT = "/Users/ymjung/Project/wordbit2/WordbitDBMakerVer2/docs/vnkr_luna_compare.html"

recs = [json.loads(l) for l in open(f"{SP}/results_final.jsonl")]
js = {j["id"]: j for j in (json.loads(l) for l in open(f"{SP}/judgments_final.jsonl"))}
obj = json.load(open(f"{SP}/objective_report_final.json"))
tally = json.load(open(f"{SP}/judge_tally_final.json"))
items = json.load(open(f"{SP}/vnkr_items_100.json"))
by = {}
for r in recs:
    by.setdefault(r["id"], {})[r["model"]] = r

M41, LUNA = "gpt-4.1-mini", "gpt-5.6-luna"
o41, olu = obj[M41], obj[LUNA]

# 2026-07-30 인하 단가 ($/M tokens): 4.1-mini 0.40/1.60, luna 1.00/6.00 → 0.20/1.20
PRICE = {M41: (0.40, 1.60), LUNA: (0.20, 1.20)}
def cost(model, o):
    pi, po = PRICE[model]
    return o["입력토큰/평균"]/1e6*pi + o["출력토큰/평균"]/1e6*po
c41, clu = cost(M41, o41), cost(LUNA, olu)

def render_answer(t):
    s = html.escape(t)
    s = re.sub(r"&lt;b&gt;(.*?)&lt;/b&gt;", r"<strong>\1</strong>", s, flags=re.S)
    s = re.sub(r"&lt;fg:#4[eE][dD]3[fF]6&gt;(.*?)&lt;/fg&gt;", r'<span class="ai-fg">\1</span>', s, flags=re.S)
    return s

def winner_of(j):
    if not j or j.get("error"): return None, ""
    o = j.get("overall")
    w = j["A_model"] if o == "A" else j["B_model"] if o == "B" else "무승부"
    return w, j.get("reason", "")

def tally_row(c, label):
    t = tally[c]
    return f"<tr><td>{label}</td><td class='num'>{t[M41]}</td><td class='num'>{t[LUNA]}</td><td class='num'>{t['tie']}</td></tr>"

qa_sections = []
for n, it in enumerate(items, 1):
    d = by.get(it["id"], {})
    r41, rlu = d.get(M41), d.get(LUNA)
    if not (r41 and rlu):
        continue
    j = js.get(it["id"])
    w, reason = winner_of(j)
    if w == LUNA: badge = "<span class='tag good'>luna 승</span>"
    elif w == M41: badge = "<span class='tag'>4.1-mini 승</span>"
    else: badge = "<span class='tag warn'>무승부</span>"
    qa_sections.append(f"""
<h3 id="q{n}">{n}. {html.escape(it['content'])} {badge}</h3>
<pre><code>{html.escape(it['question'])}</code></pre>
<div class="before-after">
  <div class="ba"><h4>gpt-4.1-mini ({r41['elapsed']:.1f}s, {(r41.get('usage') or {}).get('output_tokens','?')}tok)</h4>
  <div class="ai-answer">{render_answer(r41['text'])}</div></div>
  <div class="ba"><h4>gpt-5.6-luna ({rlu['elapsed']:.1f}s, {(rlu.get('usage') or {}).get('output_tokens','?')}tok)</h4>
  <div class="ai-answer">{render_answer(rlu['text'])}</div></div>
</div>
<p class="sub">판정: {html.escape(w or '-') } — {html.escape(reason)}</p>
""")

doc = f"""<!doctype html>
<html lang="ko">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>vnkr AI 해설보기 — 4.1-mini vs 5.6-luna 100항목 비교</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<div class="wrap">

<header>
  <p class="eyebrow">AI 해설보기 모델 비교 · 2026-07-31 · 전환 기준</p>
  <h1>vnkr 단어 해설 — gpt-4.1-mini vs gpt-5.6-luna</h1>
  <p class="lead">
    vnkr 단어 DB에서 뽑은 100개 단어로 실서비스 해설보기 요청을 API로 재현해 두 모델을 비교했다.
    luna 응답은 <strong>전환 시 적용할 최종 프롬프트(서식 규칙 반영)</strong> 기준이고,
    비용은 <strong>2026-07-30 인하 단가</strong> 기준이다. 아래에 100개 전체 질문·답변 원문이 있다.
  </p>
</header>

<nav class="toc">
  <p>목차</p>
  <ol>
    <li><a href="#s1">요약</a></li>
    <li><a href="#s2">테스트 방법</a></li>
    <li><a href="#s3">종합 결과</a></li>
    <li><a href="#s4">전체 문답 100건</a></li>
  </ol>
</nav>

<h2 id="s1"><span class="num">01</span>요약</h2>
<div class="kpi">
  <div><div class="v">200/200</div><div class="l">요청 성공 (모델당 100)</div></div>
  <div><div class="v">100 : 0</div><div class="l">블라인드 저지 종합 (luna : 4.1)</div></div>
  <div><div class="v">-32%</div><div class="l">luna 응답 총 소요 (4.4s vs 6.5s 중앙값)</div></div>
  <div><div class="v">-28%</div><div class="l">luna 회당 비용 (${clu:.5f} vs ${c41:.5f})</div></div>
</div>
<div class="callout good">
  <p>품질·속도·비용 모두 luna 우세. 4.1의 고질 결함이던 “해설을 베트남어로만 작성”(7건)이
  luna에선 0건. 2026-07-30 OpenAI 단가 인하(luna 80%↓: $0.20/$1.20 per M)로
  비용 역전 — luna가 4.1-mini보다 회당 28% 저렴하다.</p>
</div>

<h2 id="s2"><span class="num">02</span>테스트 방법</h2>
<ul>
  <li>단어: vnkr DB(mimetype 150, 9,109개)에서 시드 고정 랜덤 100개</li>
  <li>요청: 앱과 동일 — 질문 <code>단어\\n뜻\\n[발음]</code>, 시스템 프롬프트는 실서비스 RC <code>gpt_prompt_vnkr_v2.step_word_prompt</code>, 메시지 순서까지 재현</li>
  <li>luna 프롬프트: 전환 시 적용할 <strong>서식 화이트리스트 규칙(v3) 반영본</strong> — 규칙이 없으면 마크다운 볼드 48%·허용 외 태그(&lt;i&gt; 등) 7%가 섞였고, "&lt;b&gt;와 &lt;fg:#4ED3F6&gt;만 허용" 규칙으로 둘 다 0건이 됨을 전수 재테스트로 확인한 최종본</li>
  <li>경로: all-ai-proxy 워커 경유. luna는 구클라이언트 형태(temperature/top_p 포함, 토큰 2500)로 보내 워커 정규화(파라미터 제거·effort none 주입·토큰 상향)를 실전 검증</li>
  <li>품질 판정: 항목별 A/B 순서를 랜덤화한 블라인드 비교(저지: gpt-5.6-luna, reasoning low). 저지가 luna 자신이므로 자기편향 가능성 있음 — 형식 지표는 규칙 기반이라 무관</li>
</ul>

<h2 id="s3"><span class="num">03</span>종합 결과</h2>
<div class="table-scroll"><table>
<thead><tr><th>기준</th><th>gpt-4.1-mini</th><th>gpt-5.6-luna</th></tr></thead>
<tbody>
<tr><td>성공 / incomplete</td><td class="num">100 / 0</td><td class="num">100 / 0</td></tr>
<tr><td>5개 섹션 완비</td><td class="num">100%</td><td class="num">100%</td></tr>
<tr><td>마크다운 혼입(앱 노출 결함)</td><td class="num">0%</td><td class="num">0% <span class="tag good">규칙 반영</span></td></tr>
<tr><td>허용 외 태그·깨진 태그(앱 노출 결함)</td><td class="num">10건(꺾쇠 발음표기)</td><td class="num">0건 <span class="tag good">규칙 반영</span></td></tr>
<tr><td>예문 3개</td><td class="num">100%</td><td class="num">100%</td></tr>
<tr><td>fg 하이라이트 존재</td><td class="num">100%</td><td class="num">99%</td></tr>
<tr><td>해설이 베트남어로만 작성(결함)</td><td class="num">7건</td><td class="num">0건</td></tr>
<tr><td>TTFB 중앙값</td><td class="num">{o41['TTFB초/중앙값']}s</td><td class="num">{olu['TTFB초/중앙값']}s</td></tr>
<tr><td>총 소요 중앙값 / p95</td><td class="num">{o41['총소요초/중앙값']}s / {o41['총소요초/p95']}s</td><td class="num">{olu['총소요초/중앙값']}s / {olu['총소요초/p95']}s</td></tr>
<tr><td>토큰 평균 (입력 / 출력)</td><td class="num">{o41['입력토큰/평균']} / {o41['출력토큰/평균']}</td><td class="num">{olu['입력토큰/평균']} / {olu['출력토큰/평균']}</td></tr>
<tr><td>단가 ($/M, 입력/출력)</td><td class="num">0.40 / 1.60</td><td class="num">0.20 / 1.20 <span class="tag good">7/30 인하</span></td></tr>
<tr><td>회당 비용</td><td class="num">${c41:.5f}</td><td class="num">${clu:.5f} (-28%)</td></tr>
<tr><td>reasoning 토큰</td><td class="num">0</td><td class="num">0 (effort none)</td></tr>
</tbody>
</table></div>

<h3>블라인드 저지 판정 (100쌍)</h3>
<div class="table-scroll"><table>
<thead><tr><th>기준</th><th>4.1-mini 승</th><th>luna 승</th><th>무승부</th></tr></thead>
<tbody>
{tally_row('accuracy','의미·문법 정확성')}
{tally_row('pronunciation','한글 발음 표기')}
{tally_row('usefulness','학습 유용성')}
{tally_row('korean_fluency','한국어 자연스러움')}
{tally_row('overall','종합')}
</tbody>
</table></div>

<h2 id="s4"><span class="num">04</span>전체 문답 100건</h2>
<p class="sub">각 항목: 입력(코드 블록) → 두 모델 응답 원문(앱 렌더링 재현: &lt;b&gt;·&lt;fg&gt;만 서식 적용) → 블라인드 판정.
luna 응답은 전환 시 사용할 최종 프롬프트로 생성된 것이다.</p>
{''.join(qa_sections)}

<footer>
  <p>생성: 2026-07-31 · 전환 절차는 <a href="luna_migration.html">luna 무배포 전환 정리</a> 참고 ·
  단가 출처: OpenAI 2026-07-30 인하 발표</p>
</footer>

</div>
</body>
</html>
"""
open(OUT, "w").write(doc)
print(OUT, len(doc), "bytes,", len(qa_sections), "Q&A sections, cost", f"{c41:.5f}/{clu:.5f}")
