o2o-infinith-demo/scripts/merge_qb_results.py
Haewon Kam 13a8fca775 chore(reports): docs/reports를 고객·단계별 폴더로 분류
- viewclinic/01_readiness_report (진단 덱 v1.0·v2_표준캔버스·v1.1 + 빌더) / 02_poc_proposal (POC 정본·v3 + 빌더) / 03_question_bank (QB 120문항·실측·엑셀) / 04_supporters (GEO 계획·콘텐츠 기획·감사 엑셀)
- taeha/ (태하 리포트 + 빌더), ibk/ (기업은행 블로그 감사), landing/ (랜딩 시안·스크린샷·HTML 내보내기)
- README.md에 구조·정본/파생본·빌드 명령 안내
- 경로 참조 갱신: 핸드오버 문서 4개, QB 스크립트 3개, 빌더 docstring, discovery 페이지 주석
- 이전에 삭제된 POC v2_표준캔버스 2파일 삭제 반영

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-07 13:03:53 +09:00

124 lines
5.3 KiB
Python

#!/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()