o2o-infinith-demo/docs/reports/build_viewclinic_deck.py
Haewon Kam d14a1ed51f docs: Viewclinic AI Answer Readiness Report 덱 (PPTX 13장 + PDF), haewon_signature_deck 규격
- HTML 인쇄 방식 PDF는 폐기, PPTX에서 PDF 내보내기로 교체
- 빌드 스크립트 docs/reports/build_viewclinic_deck.py 동봉

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-28 10:54:16 +09:00

211 lines
12 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import sys
sys.path.insert(0, "/Users/haewonkam/.claude/skills/haewon_signature_deck/scripts")
import hsd
from hsd import Deck
OUT = sys.argv[1]
d = Deck(footer_base="INFINITH · Viewclinic AI Answer Readiness")
# 01 Cover
d.cover(
eyebrow="INFINITH AI DISCOVERY · AEO / GEO AUDIT v1.0",
title="VIEWCLINIC",
subtitle="AI Answer Readiness Report",
lede=["뷰성형외과 홈페이지가 ChatGPT·Gemini·Perplexity 답변에 인용될 준비가 됐는지 36개 항목으로 진단했습니다.",
"결과는 44점(C). 크롤러 접근은 정상이지만, AI가 인용할 문장과 엔티티 근거가 없습니다."],
meta=[("PREPARED FOR", "뷰성형외과"),
("PREPARED BY", "INFINITH AI Discovery"),
("AUDITED", "2026. 08. 28"),
("SCOPE", "viewclinic.com · 36 criteria")],
)
# 02 Executive summary (KPI 4-up)
s = d.slide(eyebrow="EXECUTIVE SUMMARY",
title="읽을 수는 있지만, ", title_accent="인용할 근거가 없다",
lede=["36개 항목 중 28개를 실측했고, 오너 접근이 필요한 8개는 점수 분모에서 제외했습니다.",
"접근성(A)은 92%로 상위권, 엔티티(B) 33%·콘텐츠(C) 20%가 총점을 끌어내립니다."])
s.kpi_4up([
("44 / 100", "AI Answer Readiness", "등급 C. 검증 항목 75점 만점 중 33.1점"),
("92%", "A · 크롤러 접근성", "AI 검색봇 12종 전부 허용, SSR 렌더링"),
("20%", "C · 인용 가능 콘텐츠", "정의문 0, FAQ 0, HTML 표 0, H1 5개"),
("8 / 36", "미검증 항목", "강남언니·GBP·GA4·로그는 오너 확인 필요"),
])
# 03 What AI sees
s = d.slide(eyebrow="WHAT AI SEES · 진단의 결론",
title="문제는 차단이 아니라 ", title_accent="내용이다")
L, R = s.cards_2up()
L.pill("정상").rows([
("크롤러 접근 정상", "robots.txt가 OAI-SearchBot·PerplexityBot 등 12종을 허용. Cloudflare 차단 없음"),
("서버 렌더링 본문", "WordPress가 본문을 서버에서 렌더. JS 없이 10,757자 추출됨"),
("YouTube 활발", "VIEW SHORTS·FILM 채널이 최신 영상을 꾸준히 발행. 오픈웹 언급 자산"),
])
R.pill("치명", tone="rival").rows([
("누구인지 확정 불가", "MedicalClinic 스키마 없음, sameAs 비어 있음, 시술 목록 JSON-LD는 문법 오류로 무효"),
("인용할 문장 없음", "H1 5개가 전부 영문 라벨. FAQ 0개, HTML 표 0개, 정의문 0개"),
("15개월 갱신 신호 없음", "post-sitemap 최신 lastmod 2024-05-31. dateModified 마크업 없음"),
])
# 04 Scorecard table
s = d.slide(eyebrow="SCORECARD · 6 CATEGORIES",
title="카테고리별 점수와 ", title_accent="검증 범위",
lede="점수는 검증된 항목만으로 정규화했습니다. 미검증 항목을 확인하면 총점이 바뀔 수 있습니다.")
W, Rail = s.wide_rail()
W.grid(
headers=["카테고리", "배점", "획득", "판정"],
col_w=[2.45, 0.80, 0.95, 0.85], start=0.20,
header_tones=[hsd.MUTED, hsd.MUTED, hsd.VIOLET, hsd.MUTED],
rows=[
["A · AI 크롤러 접근성", "20", "15.7 / 17", "92% 우수"],
["B · 엔티티 식별·구조화", "20", "6.7 / 20", "33% 미흡"],
["C · 답변 인용 가능 콘텐츠", "25", "5.0 / 25", "20% 치명"],
["D · AI 인용 표면 확보", "20", "4.0 / 6", "67% 양호"],
["E · AI 가시성 측정 인프라", "10", "0 / 2", "0% 미설치"],
],
)
Rail.bullets("읽는 법", items=[
"획득 분모는 검증 항목 배점 합. D·E는 미검증이 많아 분모가 작다",
"F 기술 위생은 5점 중 1.7점(33%). 표에서 생략",
"총점 = 33.1 / 75 = 44점, 등급 C",
])
# 05 A 접근성
s = d.slide(eyebrow="A · ACCESS & CRAWLABILITY · 92%",
title="크롤러 접근은 ", title_accent="열려 있다")
L, R = s.cards_2up()
L.pill("우수 · 3/3").rows([
("A1 검색용 AI 봇 허용", "robots.txt: Disallow는 /wp-admin/ 뿐. 12종 UA 실제 GET 전부 200"),
("A2 CDN 차단 없음", "nginx 직접 응답. Cloudflare 헤더·AI UA 챌린지 없음"),
("A4 HTTPS 정합", "http://viewclinic.com → 301 → https://www.viewclinic.com/ 정본 수렴"),
])
R.pill("보완 · 2/3", tone="neutral").rows([
("A5 sitemap.xml", "선언·200 응답. 페이지 249개. 단 post lastmod 최신이 2024-05-31"),
("A7 응답 속도·무게", "TTFB 0.135s는 양호. 초기 HTML 838KB로 기준 300KB의 2.8배"),
("A6 Bing 색인 (미검증)", "ChatGPT 검색은 Bing 인덱스 의존. Webmaster 등록 여부 오너 확인 필요"),
])
# 06 B 엔티티
s = d.slide(eyebrow="B · ENTITY & STRUCTURED DATA · 33%",
title="AI가 이 병원을 ", title_accent="하나의 엔티티로 못 묶는다")
L, R = s.cards_2up()
L.pill("치명", tone="rival").rows([
("B1 JSON-LD 문법 무효", "ItemList 블록 쉼표 누락(line 11). 시술 목록 구조화 데이터 전체가 파서에서 버려짐"),
("B2 MedicalClinic 없음", "Organization·WebSite만 존재. 주소·전화·진료시간·전문분야 미구조화"),
("B3 Physician 없음", "의료진 정보는 텍스트·이미지로만. 누가 수술하는지 기계가 못 읽음"),
])
R.pill("미흡", tone="neutral").rows([
("B5 sameAs 비어 있음", "푸터에 YouTube·Instagram·Facebook 링크가 있으나 스키마에 미연결"),
("B4 NAP 표기 2종", "\"서울 강남구 봉은사로 107\" / \"서울특별시 강남구 봉은사로 107,\" 혼재"),
("B7 단일 언어", "hreflang 없음. 외국인 환자 대상 언어 페이지 미확인"),
])
# 07 C 콘텐츠
s = d.slide(eyebrow="C · ANSWER-READY CONTENT · 20%",
title="답변엔진이 잘라 갈 ", title_accent="단락이 없다")
L, R = s.cards_2up()
L.pill("0점 항목", tone="rival").rows([
("C1 정의문 없음", "메인 첫 화면은 슬라이더와 \"VIEW SELFIE\" 라벨. 시술 페이지 첫 텍스트는 상담 폼"),
("C2 FAQ 없음", "메인·가슴성형 페이지에서 FAQ·Q&A 섹션 탐지 안 됨"),
("C3 H1 5개, 전부 라벨", "VIEW SELFIE / EVENT / SHORTS / FILM / SPECIALITY. 시술 페이지는 H1 없음"),
])
R.pill("부분 점수", tone="neutral").rows([
("C7 의료진 권위 신호 있음", "모티바 KOL·복지부 표창·갈더마 방문 등 17회 언급. 단 구조화 안 됨"),
("C4 수치 표기 미흡", "\"보형물 사용량 1위\" 같은 주장형. 가격대·수술시간·회복기간 표 없음"),
("C6 신선도 신호 약함", "dateModified 없음. 유튜브 활동이 사이트 갱신으로 전달되지 않음"),
])
# 08 D 표면 + E 측정
s = d.slide(eyebrow="D · SURFACES / E · MEASUREMENT",
title="병의원 AI 인용의 핵심 표면은 ", title_accent="아직 미확인")
L, R = s.cards_2up()
L.pill("D · 표면 67%").rows([
("D1 강남언니 (미검증)", "2026-08 실측: 병의원 질의는 강남언니 등 버티컬이 인용을 장악. P0 확인 항목"),
("D2 Google 비즈니스 프로필", "소유 여부 미확인. Gemini·AI Mode의 구조화 데이터 원천"),
("D5 네이버 플레이스", "리뷰 776건. 단 네이버 자산은 글로벌 AI 답변 기여도 0, AI 브리핑 전용"),
])
R.pill("E · 측정 0%", tone="rival").rows([
("E1 크롤러 로그", "nginx 직접 호스팅. AI 봇 히트 추출·IP 검증 파이프라인 없음"),
("E2 GA4 AI 유입 채널", "chatgpt.com·perplexity.ai 커스텀 채널그룹 없음. 오너 접근 필요"),
("E4 기준선 스팟체크", "미실시. 질의 10개 × 5회 반복 측정이 개선 증명의 전제"),
])
# 09 Action P0 / P1 (dark)
s = d.slide(eyebrow="ACTION PLAN · P0 / P1",
title="즉시 고칠 것과 ", title_accent="4주 안에 만들 것", dark=True)
L, R = s.cards_2up()
L.dark_block("P0 · 즉시 (1일~1주)", "엔티티·표면·측정", [
"깨진 JSON-LD 수정 + MedicalClinic 스키마 삽입, sameAs 연결",
"강남언니·GBP 실사, 4개 표면 NAP 한 글자까지 통일",
"GA4 AI 채널그룹 + 서버로그 IP 검증 + GSC·Bing 등록",
])
R.dark_block("P1 · 4주 내", "콘텐츠 구조", [
"핵심 페이지 첫 단락에 정의문, 시술별 FAQ 5~8개 + FAQPage 스키마",
"페이지당 H1 1개 서술형, 비교표를 HTML table로, Physician 스키마",
"WordPress 5.5.3 업그레이드, alt 누락 111개 보완, HTML 무게 절감",
])
# 10 Roadmap (asym)
s = d.slide(eyebrow="ROADMAP · 8 WEEKS",
title="8주 안에 ", title_accent="C에서 B로")
L, R = s.cards_asym()
L.timeline("실행 순서", [
("Week 1", "JSON-LD 수정, MedicalClinic·sameAs 삽입. 강남언니·GBP 실사"),
("Week 2", "측정 인프라 설치. 기준선 스팟체크 1회 (질의 10개 × 5회)"),
("Week 3–6", "정의문·FAQ·H1 재설계·HTML 표. WordPress 업그레이드"),
("Week 7–8", "재진단. 미검증 8개 항목 반영해 총점 재산정"),
])
R.numbered("P2 · 지속 항목", [
"dateModified 마크업 활성화, 월 1회 사이트 갱신",
"유튜브 영상을 사이트 글로 전사해 신선도 전달",
"티스토리·언론 기고로 naver.com 외 언급 도메인 확보",
"분기 1회 반복 측정, 언급률과 신뢰구간으로만 보고",
])
# 11 What we don't sell (3-up)
s = d.slide(eyebrow="WHAT WE DON'T SELL · 정직성 규약",
title="근거 없는 것은 ", title_accent="팔지 않는다")
c1, c2, c3 = s.cards_3up()
c1.bullets("만들지 않는 것", items=[
"llms.txt. Google 공식 효과 0, 채택률 0.13%, 서버 로그에 요청 자체가 없음",
"네이버 블로그 증량. robots.txt가 AI 크롤링 전면 금지, 글로벌 AI 기여도 0",
])
c2.bullets("약속하지 않는 것", items=[
"\"스키마 넣으면 인용됩니다\". 인과 증거 없음, 위생 조치로만 설명",
"순위·노출 보장. LLM 답변은 동일 질문에도 10~34% 흔들림",
])
c3.bullets("측정 규율", items=[
"단발 조회로 성과 보고 금지. 반복 측정 + 신뢰구간만",
"답변엔진 UI 자동 조회 금지. OpenAI·Perplexity 약관 위반",
"AI 유입은 항상 하한값. 35~70%가 Direct로 유실",
])
# 12 Rubric method (3-up)
s = d.slide(eyebrow="METHOD · AEO / GEO RUBRIC v1.0",
title="36개 항목, 100점, ", title_accent="근거 등급을 명시")
c1, c2, c3 = s.cards_3up()
c1.bullets("채점 방식", items=[
"항목별 0~3 레벨 × 배점. 미검증은 0점이 아니라 분모 제외",
"A 20 · B 20 · C 25 · D 20 · E 10 · F 5",
"등급 A 81+ · B 61~80 · C 41~60 · D 40 이하",
])
c2.bullets("배점 근거", items=[
"차단되면 절대 인용될 수 없다는 사실은 논쟁이 없다. A·B에 무게",
"GEO 콘텐츠 전술 효과는 학술적으로 논쟁 중. C는 좋은 콘텐츠 원칙으로만",
"병의원은 강남언니가 인용을 장악. D1 단일 항목 최고 배점 6",
])
c3.bullets("항목마다 붙는 태그", items=[
"근거 등급: 실측 / 학술 / 벤더 / 추정",
"통제 가능성: 직접 / 영향 / 불가",
"채점 방식: 자동 / 반자동 / 수동. 자동 항목은 Edge Function으로 이식",
])
# 13 Closing
d.closing(eyebrow="NEXT STEP",
title="확인 2개면 점수가 ", title_accent="실제 노출력에 수렴합니다.",
lede=["강남언니 프로필과 Google 비즈니스 프로필 상태만 확인하면 미검증 8개 중 가장 큰 10점이 채워집니다.",
"그 다음은 P0 세 가지: 스키마, NAP 통일, 측정 인프라."],
signature="INFINITH AI Discovery · AEO / GEO Audit v1.0 · 2026. 08. 28")
d.save(OUT)
print("saved", OUT)