""" Viewclinic AI Answer Readiness Report v1.1 덱 빌드 (INFINITH Deck 규격) v1.0(2026-09-01)을 2026-09-07 재검증으로 정정한 판. 원본 v1.0 파일은 haewon이 폰트를 직접 손본 정본이라 덮어쓰지 않고 v1.1 파일로 새로 저장한다. 정정 3건: 블로그 정지(오판) · FAQ 0개(프리저베 페이지 FAQ 7문항) · 수술시간·회복 표기 없음(프리저베·코·윤곽 페이지에 있음) 점수: 50 → 54 (C 유지). C 카테고리 28% → 44%. 추가 2장: 재검증 정정 내역 · 뷰성형외과에 드리는 요청(서포터즈 강화). 폰트: haewon 확정 기준(본문 20 / 리드 24 / 카드 제목 28). ifd 분량 검사(폰트 축소 금지) 안에서 카피를 줄였다. python3 docs/reports/build_viewclinic_deck_v1_1.py docs/reports/Viewclinic_AI_Answer_Readiness_Report_v1.1.pptx """ import sys sys.path.insert(0, "/Users/haewonkam/.claude/skills/infinith-deck/scripts") from ifd import Deck OUT = sys.argv[1] if len(sys.argv) > 1 else "Viewclinic_AI_Answer_Readiness_Report_v1.1.pptx" BODY, HEAD = 20.0, 28.0 d = Deck() # 01 Cover d.cover( eyebrow="AI DISCOVERY · AEO / GEO AUDIT v1.1", title_lines=["AI Answer", "Readiness Report."], subtitle="뷰성형외과 · viewclinic.com · 실측 2026. 08. 28 · 재채점 09. 01 · 재검증 정정 09. 07", meta_right="AI Discovery Report · 2026", ) # 02 Executive summary s = d.slide(eyebrow="Executive Summary", title="읽을 수는 있지만, ", title_accent="인용할 근거가 부족하다", lede=["36개 항목 중 32개를 실측했고, 오너 계정이 필요한 4개는 분모에서 제외했습니다.", "접근성(A) 98%와 표면(D) 68%는 양호. 엔티티(B) 25%와 콘텐츠(C) 44%가 총점을 끌어내립니다."], section="Summary") k1, k2, k3, k4 = s.cards4(y=s.band_y, h=3.4) k1.kpi("54 / 100", "AI ANSWER READINESS", "등급 C · 48.0 / 89점 · v1.0 50점 정정") k2.kpi("98%", "A · 크롤러 접근성", "AI 검색봇 12종 전부 허용") k3.kpi("44%", "C · 인용 가능 콘텐츠", "정의문 0 · FAQ 1페이지 · 표 0") k4.kpi("4 / 36", "미검증 항목", "GA4 · 서버로그 · GSC · Bing") # 03 What AI sees (dark) s = d.slide(eyebrow="What AI Sees · 진단의 결론", title="문제는 차단이 아니라 ", title_accent="내용이다", dark=True, section="Findings") L, R = s.cards2(y=s.band_y) L.chip("정상", "good").rows([ ("크롤러 접근 정상", "robots.txt가 AI 검색봇 12종을 허용. Cloudflare 차단 없음."), ("서버 렌더링 본문", "WordPress가 본문을 서버에서 렌더. JS 없이 10,757자 추출."), ("표면이 넓다", "강남언니 후기 19,373건. 유튜브 1,215편. 블로그는 발행 중이고 AI 봇 허용."), ], t_size=HEAD, b_size=BODY) R.chip("치명", "critical").rows([ ("누구인지 확정 불가", "MedicalClinic 스키마 없음, sameAs 비어 있음. 시술 목록 JSON-LD 무효."), ("인용할 문장 부족", "H1 5개가 영문 라벨. HTML 표 0, 병원 정의문 0. FAQ는 프리저베뿐."), ("표면 간 NAP 불일치", "GBP 전화번호 없음. 웹사이트가 영문 도메인. 주소 표기 3종."), ], t_size=HEAD, b_size=BODY) # 04 Scorecard s = d.slide(eyebrow="Scorecard · 6 Categories", title="카테고리별 점수와 ", title_accent="검증 범위", lede="검증된 항목만으로 정규화했습니다. 09-07 재검증으로 C2·C4를 정정해 C가 28%에서 44%가 됐습니다.", section="Scorecard") W, Rail = s.cards2(y=s.band_y, split=0.66) W.grid( headers=["카테고리", "배점", "획득", "검증", "판정"], col_w=[4.6, 1.3, 2.0, 1.4, 1.6], rows=[ ["A · AI 크롤러 접근성", "20", "16.7 / 17", "6 / 7", "우수"], ["B · 엔티티 식별·구조화", "20", "5.0 / 20", "7 / 7", "미흡"], ["C · 답변 인용 가능 콘텐츠", "25", "11.0 / 25", "8 / 8", "미흡"], ["D · AI 인용 표면 확보", "20", "13.7 / 20", "6 / 6", "양호"], ["E · AI 가시성 측정 인프라", "10", "0 / 2", "1 / 4", "미설치"], ["F · 기술 위생", "5", "1.7 / 5", "4 / 4", "미흡"], ], tones=[[None, None, None, None, "info"], [None, None, None, None, "warning"], [None, None, None, None, "warning"], [None, None, None, None, "good"], [None, None, None, None, "critical"], [None, None, None, None, "warning"]], row_h=0.62, size=BODY, ) Rail.title("읽는 법", size=HEAD).bullets([ "분모는 검증 항목의 배점 합. E는 분모가 2점뿐.", "48.0 / 89 = 54점, 등급 C. v1.0 50점은 오판 포함.", "미검증 4개는 계정 접근 후 1일 안에 채운다.", ], size=BODY) # 05 A 접근성 s = d.slide(eyebrow="A · Access & Crawlability · 98%", title="크롤러 접근은 ", title_accent="열려 있다", section="A · Access") L, R = s.cards2(y=s.band_y) L.chip("우수 · 3/3", "info").rows([ ("A1 검색용 AI 봇 허용", "Disallow는 /wp-admin/ 뿐. 12종 UA로 GET, 전부 200."), ("A2 CDN 차단 없음", "nginx 직접 응답. Cloudflare 헤더와 AI UA 챌린지 없음."), ("A4 HTTPS 정합", "http → 301 → https://www.viewclinic.com/ 정본 수렴."), ], t_size=HEAD, b_size=BODY) R.chip("보완·미검증", "warning").rows([ ("A5 sitemap 우수", "page·portfolio lastmod 2026-08-31. 블로그는 별도 서브도메인(정정)."), ("A7 응답 속도·무게", "TTFB 0.135s 양호. 초기 HTML 838KB, 기준 300KB의 2.8배."), ("A6 Bing 색인 (미검증)", "ChatGPT 검색은 Bing 인덱스 의존. 계정 확인 필요."), ], t_size=HEAD, b_size=BODY) # 06 B 엔티티 (dark) s = d.slide(eyebrow="B · Entity & Structured Data · 25%", title="AI가 이 병원을 ", title_accent="하나의 엔티티로 못 묶는다", dark=True, section="B · Entity") L, R = s.cards2(y=s.band_y) L.chip("치명", "critical").rows([ ("B1 JSON-LD 문법 무효", "ItemList 블록 쉼표 누락. 09-07에도 그대로. 파서가 통째로 버림."), ("B2 MedicalClinic 없음", "Organization·WebSite·WebPage만. 주소·전화·진료시간 미구조화."), ("B3 Physician 없음", "의료진은 텍스트와 이미지뿐. 누가 수술하는지 기계가 못 읽음."), ], t_size=HEAD, b_size=BODY) R.chip("미흡", "warning").rows([ ("B5 sameAs 비어 있음", "푸터에 유튜브·인스타·블로그 링크가 있으나 스키마 미연결."), ("B4 NAP 표기 3종 혼재", "주소 표기 3종. 전화도 2회선과 휴대폰 혼재."), ("B7 영문 사이트 미연결", "viewplasticsurgery.com이 별도. hreflang 없음."), ], t_size=HEAD, b_size=BODY) # 07 C 콘텐츠 s = d.slide(eyebrow="C · Answer-Ready Content · 44%", title="프리저베 한 페이지만 ", title_accent="인용할 구조를 갖췄다", section="C · Content") L, R = s.cards2(y=s.band_y) L.chip("0점 항목", "critical").rows([ ("C1 병원 정의문 없음", "메인은 슬라이더와 라벨. 가슴성형 페이지 첫 텍스트는 상담 폼."), ("C3 H1 5개, 전부 라벨", "가슴·코·오시는길은 H1 없음. 프리저베만 H1 1개 서술형."), ("C5 HTML 표 없음", "5개 페이지 모두 table 0개. 비교 정보는 이미지."), ], t_size=HEAD, b_size=BODY) R.chip("정정·부분 점수", "warning").rows([ ("C2 FAQ는 프리저베에만 (정정)", "FAQ 7문항과 유효한 FAQPage 스키마. 다른 시술 페이지는 없음."), ("C4 수치는 시술 상세에 (정정)", "프리저베 20~40분·회복 1~3일, 코 1~2시간·실밥 7일. 가격대 없음."), ("C6 신선도 정상", "시술 페이지 2026-04-17, sitemap 08-31, 블로그 09-07 게시."), ], t_size=HEAD, b_size=BODY) # 08 D 표면 / E 측정 (dark) s = d.slide(eyebrow="D · Surfaces / E · Measurement", title="강남언니는 강하고, ", title_accent="GBP는 전화번호가 없다", dark=True, section="D · Surfaces") L, R = s.cards2(y=s.band_y) L.chip("D · 표면 68%", "good").rows([ ("D1 강남언니 우수", "후기 19,373건, 의료진 26명, 이벤트 52건. 핵심 표면 확보."), ("D2 GBP 4.9점 (901)", "게시물 활성. 전화번호 미등록, 웹사이트 영문 도메인(08-28 실측)."), ("D3 오픈웹 표면", "유튜브 1,215편, 블로그 발행 중, 언론 1,137건. NAP 불일치로 3점 보류."), ], t_size=HEAD, b_size=BODY) R.chip("E · 측정 0%", "critical").rows([ ("E1 크롤러 로그", "nginx 직접 호스팅. AI 봇 히트 추출 파이프라인 없음."), ("E2 GA4 AI 유입 채널", "chatgpt.com·perplexity.ai 채널그룹 없음. 계정 접근 필요."), ("E4 기준선 반복 측정", "120문항 API 실측 1회(09-01). 반복 측정은 미실시."), ], t_size=HEAD, b_size=BODY) # 09 Action plan s = d.slide(eyebrow="Action Plan", title="즉시 고칠 것과 ", title_accent="4주 안에 만들 것", section="Action Plan") a, b, c = s.cards3(y=s.band_y) a.chip("P0 · 1일~1주", "critical").title("엔티티·표면·측정", size=HEAD).bullets([ "깨진 JSON-LD 수정, MedicalClinic 스키마.", "GBP 전화번호 등록, 웹사이트 교정.", "4개 표면 NAP 통일. hreflang 연결.", "GA4 AI 채널, 서버로그, GSC·Bing.", ], size=BODY) b.chip("P1 · 4주 내", "warning").title("콘텐츠 구조", size=HEAD).bullets([ "프리저베 구조를 가슴·코·눈·윤곽으로 확산.", "핵심 페이지 첫 단락에 병원 정의문.", "비교표를 HTML 표로. Physician 스키마.", "WordPress 업그레이드, alt 111개 보완.", ], size=BODY) c.chip("P2 · 지속", "good").title("연결·시딩", size=HEAD).bullets([ "블로그 글과 시술 페이지를 내부 링크로.", "유튜브 영상을 블로그 글로 전사.", "월 1회 갱신, 분기 1회 반복 측정.", ], size=BODY) # 10 Roadmap (dark) s = d.slide(eyebrow="Roadmap · 8 Weeks", title="8주 안에 ", title_accent="C에서 B로", dark=True, section="Roadmap") s.stats3([ ("W1–2", "스키마·NAP·측정 인프라 설치.\n기준선 반복 측정 1회."), ("W3–6", "정의문·FAQ·H1 재설계·HTML 표.\nWordPress 업그레이드."), ("W7–8", "재진단. 미검증 4개 반영해\n총점 재산정."), ], y=s.band_y + 0.6) # 11 Validation s = d.slide(eyebrow="Validation · 120-Question Live Test", title="진단을 120문항 실측으로 ", title_accent="검증했다", lede="잠재 환자 질문 120개를 OpenAI·Perplexity 정식 API로 질의해 예측력을 채점했습니다 (09-01).", section="Validation") L, R = s.cards2(y=s.band_y) L.chip("예측 적중", "good").rows([ ("논브랜드 질문에서 부재", "84문항 중 Perplexity 3, ChatGPT 10문항만 언급. 시술 정보 52문항은 0건."), ("원인은 콘텐츠(C)", "병원 정의문 0, HTML 표 0, FAQ는 한 페이지. 시술별 인용 단락 부재."), ("표면(D)이 브랜드 방어", "병원명을 아는 36문항은 97~100% 답변. 근거는 강남언니·GBP·뉴스."), ], t_size=HEAD, b_size=BODY) R.chip("진단 밖 발견 · 정정 반영", "info").rows([ ("제3자 표면이 더 넓다", "뉴스·채용공고·바비톡·네이버 예약이 개원연도·주차·휴진을 답함."), ("발행 중인 블로그가 인용됨", "ChatGPT 추천 근거가 blog.viewclinic.com. v1.0은 정지로 오판."), ("엔진별 오답 편차", "주차 질문에 Perplexity는 정답, ChatGPT는 추측. 구조화 부재의 비용."), ], t_size=HEAD, b_size=BODY) # 12 Corrections (dark) s = d.slide(eyebrow="Corrections · 2026-09-07 재검증", title="v1.0에서 틀린 것 ", title_accent="세 가지를 바로잡습니다", dark=True, lede="홈·프리저베·가슴·코·오시는길 5개 URL의 원본 HTML을 다시 받아 파싱하고 점수를 다시 계산했습니다.", section="Corrections") L, R = s.cards2(y=s.band_y) L.chip("정정", "warning").rows([ ("블로그 정지 → 발행 중", "www post-sitemap만 본 오판. blog.viewclinic.com은 09-07까지 발행, AI 봇 14종 허용."), ("FAQ 0개 → 프리저베 7문항", "2개 URL 결과를 전체로 일반화. 프리저베에 FAQ 7문항과 FAQPage 스키마. C2 0→2."), ("수치 없음 → 시술 상세에 있음", "프리저베·코·윤곽 페이지에 수술시간·마취·회복 텍스트. 가격대는 없음. C4 1→2."), ], t_size=HEAD, b_size=BODY) R.chip("유지 · 재확인", "info").rows([ ("그대로 확인된 것", "H1 5개 라벨, ItemList 파싱 실패, sameAs 빈 값, 표 0개, 진료시간 이미지, WP 5.5.3."), ("점수", "44.0/89 = 50점 → 48.0/89 = 54점. 등급 C 유지. C 카테고리 28% → 44%."), ("재확인 필요", "GBP 전화번호·웹사이트는 08-28 실측값. 병원이 고쳤다면 D2·D4 재채점."), ], t_size=HEAD, b_size=BODY) # 13 Request to the clinic s = d.slide(eyebrow="Request · 뷰 서포터즈 강화를 위해", title="뷰성형외과에 ", title_accent="부탁드리는 것", lede=["서포터즈 사이트 18편(검토 완료 10편)을 병원의 공식 답으로 완성하려면 병원만 아는 정보가 필요합니다.", "아래 항목은 담당자 한 분이 1~2주 안에 처리하실 수 있는 범위로 추렸습니다."], section="Request") a, b, c = s.cards3(y=s.band_y) a.chip("정보 · 병원만 아는 사실", "info").title("알려 주시면 반영합니다", size=HEAD).bullets([ "가슴성형 견적 기본 포함 항목과 결제 방식.", "가슴성형 후 출근 기준과 걷기 시작 시점.", "타원 수술 재수술 상담 가능 여부.", "코·윤곽 재수술 대기 기간, 코 실밥 제거일.", ], size=BODY) b.chip("확인 · 검토와 승인", "warning").title("확인해 주시면 켜집니다", size=HEAD).bullets([ "검토 대기 5편의 담당 원장 확정과 검토일.", "검토 완료 10편의 기록을 병원 기록과 대조.", "서포터즈 지원 관계 문구 확정.", "작성자 실명·편집 책임자 표기 승인.", ], size=BODY) c.chip("액션 · 홈페이지 쪽", "critical").title("병원이 직접 고쳐야 합니다", size=HEAD).bullets([ "ItemList JSON-LD 오류 수정, MedicalClinic 추가.", "GBP 전화번호 등록, 웹사이트 교정.", "프리저베 구조를 다른 시술 페이지로 확산.", "서포터즈 정식 도메인 확정과 링크 연결.", ], size=BODY) # 14 Integrity s = d.slide(eyebrow="What We Don't Sell · 정직성 규약", title="근거 없는 것은 ", title_accent="팔지 않는다", section="Integrity") a, b, c = s.cards3(y=s.band_y) a.icon_square().title("만들지 않는 것", size=HEAD).bullets([ "llms.txt. Google 공식 효과 0, 요청 로그 없음.", "네이버 블로그 증량. AI 크롤링 금지 도메인.", ], size=BODY) b.icon_square().title("약속하지 않는 것", size=HEAD).bullets([ "\"스키마 넣으면 인용됩니다\". 인과 증거 없음.", "순위·노출 보장. 답변은 10~34% 흔들림.", ], size=BODY) c.icon_square().title("측정 규율", size=HEAD).bullets([ "단발 조회 보고 금지. 반복 측정만.", "답변엔진 UI 자동 조회 금지. 약관 위반.", "틀린 판정은 정정 슬라이드로 남긴다.", ], size=BODY) # 15 Method s = d.slide(eyebrow="Method · AEO / GEO Rubric v1.0", title="36개 항목, 100점, ", title_accent="근거 등급을 명시", section="Method") a, b, c = s.cards3(y=s.band_y) a.icon_square().title("채점 방식", size=HEAD).bullets([ "항목별 0~3 레벨 × 배점. 미검증은 분모 제외.", "A 20 · B 20 · C 25 · D 20 · E 10 · F 5.", "등급 A 81+ · B 61~80 · C 41~60 · D 40 이하.", ], size=BODY) b.icon_square().title("배점 근거", size=HEAD).bullets([ "차단되면 인용될 수 없다. A·B에 무게.", "콘텐츠 전술 효과는 논쟁 중. C는 원칙만.", "병의원은 강남언니가 장악. D1 배점 6.", ], size=BODY) c.icon_square().title("항목마다 붙는 태그", size=HEAD).bullets([ "근거 등급: 실측 / 학술 / 벤더 / 추정.", "통제 가능성: 직접 / 영향 / 불가.", "채점 방식: 자동 / 반자동 / 수동.", ], size=BODY) # 16 Closing d.closing(eyebrow="Next Step", title_lines=["표면은 확보됐습니다.", "홈페이지가 따라가면 됩니다."], lede="P0 세 가지: 스키마, NAP 통일, 측정 인프라. 서포터즈 요청은 담당자 한 분이 처리할 수 있습니다.", cta_primary="P0 착수하기 →", cta_secondary="서포터즈 사이트 보기", contact="o2oteam@o2o.kr") d.save(OUT) print("saved", OUT)