diff --git a/docs/reports/Viewclinic_QB_Results.xlsx b/docs/reports/Viewclinic_QB_Results.xlsx index 12695d3..837e8a0 100644 Binary files a/docs/reports/Viewclinic_QB_Results.xlsx and b/docs/reports/Viewclinic_QB_Results.xlsx differ diff --git a/scripts/export_qb_excel.py b/scripts/export_qb_excel.py index 74a181e..bf1ced5 100644 --- a/scripts/export_qb_excel.py +++ b/scripts/export_qb_excel.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 """Question Bank 120문항 + 2엔진 실측 결과를 엑셀(xlsx)로 내보낸다. -시트: 요약 / 통합결과 / 병원빈도 / GPT상세 / PPLX상세 +시트: 요약 / 통합결과 / 병원빈도 / TOP20 / GPT상세 / PPLX상세 """ import json import os @@ -145,6 +145,54 @@ def main(): for col, w in zip("ABCD", [16, 30, 13, 13]): ws.column_dimensions[col].width = w + # ── TOP20 (논브랜드 84문항 · 2엔진 합산 랭킹) ───────── + ws = wb.create_sheet("TOP20") + ws["A1"] = "성형외과 AI Discovery TOP 20 (논브랜드 84문항 · ChatGPT+Perplexity 합산)" + ws["A1"].font = Font(bold=True, size=14, color=INK) + ws["A2"] = "점수 = 답변 내 등장 순위 가중치(1위 5점 ~ 5위 1점)를 질문·엔진 단위로 합산. 브랜드 직접 질문 36문항은 제외." + ws["A2"].font = Font(size=10, color="666B7A") + ws.append([]) + ws.append(["순위", "병원", "AI Discovery 점수", "언급 질문 수(합산)", "GPT 언급", "PPLX 언급", "1순위 등장", "주요 등장 카테고리"]) + hr = ws.max_row + style_header(ws, 8, hr) + + agg = {} + for eng_key, data in (("gpt", oa), ("pplx", px)): + for q in qs: + if brand(q): + continue + seen = set() + for i, c in enumerate([c for c in data[q["id"]].get("clinics", []) if isinstance(c, str)][:5]): + key = canon(c) + if key in seen: + continue + seen.add(key) + a = agg.setdefault(key, {"label": "뷰성형외과" if key == "뷰성형외과" else c, + "score": 0, "gpt": 0, "pplx": 0, + "first": 0, "cats": {}}) + a["score"] += 5 - i + a[eng_key] += 1 + if i == 0: + a["first"] += 1 + cat = CAT_LABEL[q["id"].split("-")[0]] + a["cats"][cat] = a["cats"].get(cat, 0) + 1 + + ranked = sorted(agg.values(), key=lambda a: (-a["score"], -a["first"], -(a["gpt"] + a["pplx"]))) + view_rank = next((i + 1 for i, a in enumerate(ranked) if canon(a["label"]) == "뷰성형외과"), None) + shown = ranked[:20] + if view_rank and view_rank > 20: + shown = ranked[:20] + [ranked[view_rank - 1]] + for idx, a in enumerate(shown): + rank = idx + 1 if idx < 20 else view_rank + cats = " · ".join(k for k, _ in sorted(a["cats"].items(), key=lambda x: -x[1])[:3]) + ws.append([rank, a["label"], a["score"], a["gpt"] + a["pplx"], a["gpt"], a["pplx"], a["first"], cats]) + if canon(a["label"]) == "뷰성형외과": + for c in range(1, 9): + ws.cell(row=ws.max_row, column=c).fill = PatternFill("solid", fgColor=WARN) + ws.cell(row=ws.max_row, column=c).font = Font(bold=True) + for col, w in zip("ABCDEFGH", [6, 30, 16, 16, 10, 10, 10, 40]): + ws.column_dimensions[col].width = w + # ── 엔진별 상세 (답변 전문 + 인용) ──────────────────── for sheet, data in (("GPT상세", oa), ("PPLX상세", px)): ws = wb.create_sheet(sheet)