#!/usr/bin/env python3 """OpenAI·Perplexity 실측 JSONL 2개를 합쳐 질문별 통합 비교표 MD를 생성한다.""" import json import os import time ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) QUESTIONS = os.path.join(ROOT, "scripts", "question_bank_viewclinic.json") OUT_MD = os.path.join(ROOT, "docs", "reports", "viewclinic", "03_question_bank", "Viewclinic_QB_Results_Combined.md") CAT_LABEL = { "A": "A. 브랜드 직접", "B": "B. 위치·운영", "C": "C. 지역 추천·비교", "D1": "D1. 가슴성형", "D2": "D2. 눈성형", "D3": "D3. 코성형", "D4": "D4. 안면윤곽·양악", "D5": "D5. 지방성형", "D6": "D6. 피부·리프팅", "D7": "D7. 재수술", "E": "E. 신뢰·안전", "F": "F. 가격·이벤트", "G": "G. 후기·평판", } # 질문 문장에 병원명이 들어간 브랜드 질문 (뷰 언급이 당연한 그룹) def is_brand(q): return "뷰성형외과" in q["question"] def canon(name): """빈도 집계용 병원명 정규화: 표기 변형을 하나로 합친다.""" n = name.strip().lower().replace(" ", "") if "view" in n and ("plastic" in n or "성형" in n) or n.startswith("뷰성형"): return "뷰성형외과" for suf in ("성형외과의원", "성형외과", "의원", "클리닉", "clinic", "plasticsurgery"): if n.endswith(suf) and len(n) > len(suf): n = n[: -len(suf)] break return n def load(engine): p = os.path.join(ROOT, "scripts", "out", f"qb_{engine}_results.jsonl") rows = {} if os.path.exists(p): for line in open(p): if line.strip(): r = json.loads(line) rows[r["id"]] = r return rows def cell(r): if not r: return "미실측", "-" clinics = [c for c in r.get("clinics", []) if isinstance(c, str)][:5] rank = r.get("view_rank") mark = f"O ({rank}위)" if r.get("view_mentioned") else "X" return mark, ", ".join(clinics) if clinics else "(병원 언급 없음)" def main(): qs = json.load(open(QUESTIONS)) oa, px = load("openai"), load("perplexity") lines = ["# 뷰성형외과 Question Bank 실측 결과: ChatGPT(OpenAI) x Perplexity 통합", "", f"측정일: {time.strftime('%Y-%m-%d')}", "측정 방식: OpenAI Responses API + web_search(gpt-4o, 서울 고정) / Perplexity API(sonar). " "답변엔진 소비자 UI 자동 조회는 약관 위반이므로 정식 API로 측정했다.", ""] # 집계 stats = {} for eng, data in (("ChatGPT(OpenAI)", oa), ("Perplexity", px)): b = [q for q in qs if is_brand(q) and q["id"] in data] nb = [q for q in qs if not is_brand(q) and q["id"] in data] bm = sum(1 for q in b if data[q["id"]].get("view_mentioned")) nbm = sum(1 for q in nb if data[q["id"]].get("view_mentioned")) stats[eng] = (len(b), bm, len(nb), nbm) lines += ["## 요약", "", "| 엔진 | 브랜드 질문 뷰 언급 | 논브랜드 질문 뷰 언급 |", "|---|---|---|"] for eng, (b, bm, nb, nbm) in stats.items(): lines.append(f"| {eng} | {bm}/{b} ({bm*100//max(b,1)}%) | {nbm}/{nb} ({nbm*100//max(nb,1)}%) |") lines += ["", "브랜드 질문은 질문 문장에 '뷰성형외과'가 들어간 문항이다. " "승부처인 논브랜드 질문(추천·비교·시술 정보)에서의 언급률이 AI Discovery의 실제 성적이다.", ""] # 병원별 빈도 (논브랜드 질문 한정) for eng, data in (("ChatGPT(OpenAI)", oa), ("Perplexity", px)): freq, label = {}, {} for q in qs: if is_brand(q) or q["id"] not in data: 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) label.setdefault(key, c) freq.setdefault(key, [0, 0]) freq[key][0] += 1 if i == 0: freq[key][1] += 1 freq = {label[k]: v for k, v in freq.items()} lines += [f"### {eng}: 논브랜드 질문에서 언급된 병원 상위 15", "", "| 병원 | 언급 질문 수 | 1순위 등장 수 |", "|---|---|---|"] for name, (cnt, first) in sorted(freq.items(), key=lambda x: -x[1][0])[:15]: lines.append(f"| {name} | {cnt} | {first} |") lines.append("") # 문항별 표 (카테고리 섹션별) lines += ["## 문항별 결과", "", "GPT/PPLX 컬럼: O(순위) = 뷰성형외과 언급과 답변 내 등장 순번, X = 미언급.", ""] cur = None for q in qs: cat = q["id"].split("-")[0] if cat != cur: cur = cat lines += [f"### {CAT_LABEL.get(cat, cat)}", "", "| # | 질문 | 사전판정 | GPT | GPT 상위 병원 | PPLX | PPLX 상위 병원 |", "|---|---|---|---|---|---|---|"] om, oc = cell(oa.get(q["id"])) pm, pc = cell(px.get(q["id"])) lines.append(f"| {q['id']} | {q['question']} | {q['prior']} | {om} | {oc} | {pm} | {pc} |") open(OUT_MD, "w").write("\n".join(lines) + "\n") print(f"저장: {OUT_MD} (openai {len(oa)} / perplexity {len(px)}문항)") if __name__ == "__main__": main()