o2o-infinith-demo/docs/reports/taeha/build_taeha_deck.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

214 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.

"""
법무법인 태하 AI Answer Readiness Report 덱 빌드 (INFINITH Deck 규격)
python3 docs/reports/taeha/build_taeha_deck.py docs/reports/taeha/Taeha_AI_Answer_Readiness_Report.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 "Taeha_AI_Answer_Readiness_Report.pptx"
d = Deck()
# 01 Cover
d.cover(
eyebrow="AI DISCOVERY · AEO / GEO AUDIT v1.0 · LEGAL",
title_lines=["AI Answer", "Readiness Report."],
subtitle="법무법인 태하 · taehadrug.com · 네이버 마약 변호사 파워링크 1위 · 2026. 08. 28",
meta_right="AI Discovery Report · 2026",
)
# 02 Executive summary
s = d.slide(eyebrow="Executive Summary",
title="잘 만든 사이트를 ", title_accent="ChatGPT가 못 읽는다",
lede=["36개 항목 중 31개를 실측했고, 오너 계정이 필요한 5개는 점수 분모에서 제외했습니다.",
"구조화·콘텐츠·위생은 상위권. 메인 도메인의 WAF 차단과 도메인 6개 분산이 총점을 끌어내립니다."],
section="Summary")
k1, k2, k3, k4 = s.cards4(y=s.band_y, h=3.4)
k1.kpi("68 / 100", "AI ANSWER READINESS", "등급 B · 60 / 88점")
k2.kpi("87%", "F · 기술 위생", "alt 누락 0 · HTML 274KB")
k3.kpi("65%", "C · 인용 가능 콘텐츠", "FAQ 14문항 · 전관 경력 텍스트")
k4.kpi("403", "메인 도메인 → GPTBot", "taehalaw.com WAF가 AI 검색봇 차단")
# 03 What AI sees (dark)
s = d.slide(eyebrow="What AI Sees · 진단의 결론",
title="엔티티가 6개로 ", title_accent="쪼개져 있다", dark=True, section="Findings")
L, R = s.cards2(y=s.band_y)
L.chip("정상", "good").rows([
("마약 사이트 구조화 우수", "JSON-LD 3블록 전부 유효. LegalService·Attorney·FAQPage·Breadcrumb가 @graph로 연결."),
("전관 경력이 텍스트로 있다", "지청장·판사·부장검사 역임이 H3로 노출. AI가 읽을 수 있는 권위 신호."),
("FAQ 14문항, 실제 질문형", "\"초범인데도 실형이 나올 수 있나요?\" 같은 의뢰인 언어 그대로. FAQPage 스키마 병행."),
])
R.chip("치명", "critical").rows([
("메인 도메인이 ChatGPT를 차단", "taehalaw.com 가비아 WAF가 GPTBot·OAI-SearchBot을 403으로. 브라우저·Perplexity는 200."),
("도메인 6개, 전화번호 4종", "마약·형사·이혼·군형법·학폭 사이트가 따로. 콜트래킹 번호와 안심번호가 전부 다름."),
("canonical이 http://, 경로 불일치", "메인 canonical http://taehadrug.com/. 성공사례 canonical이 실제 URL과 다름."),
])
# 04 Scorecard
s = d.slide(eyebrow="Scorecard · 6 Categories",
title="카테고리별 점수와 ", title_accent="검증 범위",
lede="점수는 검증된 항목만으로 정규화했습니다. 미검증 항목을 확인하면 총점이 바뀔 수 있습니다.",
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", "15.0 / 17", "6 / 7", "양호"],
["B · 엔티티 식별·구조화", "20", "13.7 / 20", "7 / 7", "양호"],
["C · 답변 인용 가능 콘텐츠", "25", "16.3 / 25", "8 / 8", "양호"],
["D · AI 인용 표면 확보", "20", "10.7 / 19", "5 / 6", "보완"],
["E · AI 가시성 측정 인프라", "10", "0 / 2", "1 / 4", "미설치"],
["F · 기술 위생", "5", "4.3 / 5", "4 / 4", "우수"],
],
tones=[[None, None, None, None, "good"], [None, None, None, None, "good"],
[None, None, None, None, "good"], [None, None, None, None, "warning"],
[None, None, None, None, "critical"], [None, None, None, None, "info"]],
row_h=0.62,
)
Rail.title("읽는 법").bullets([
"획득 분모는 검증 항목의 배점 합. E는 미검증 3개라 분모가 2점뿐.",
"총점 60 / 88 = 68점, 등급 B. 뷰성형외과 48점과 20점 차이.",
"A가 88%인 이유: 마약 사이트는 열려 있지만 메인 도메인 WAF 차단으로 A2 감점.",
])
# 05 A 접근성 (dark)
s = d.slide(eyebrow="A · Access & Crawlability · 88%",
title="마약 사이트는 열려 있고, ", title_accent="메인은 막혀 있다", dark=True, section="A · Access")
L, R = s.cards2(y=s.band_y)
L.chip("taehadrug.com · 정상", "good").rows([
("A1 검색용 AI 봇 허용", "robots.txt User-agent * Allow /. 12종 UA로 실제 GET, 전부 200."),
("A4 · A5 정합·sitemap", "http·www 변형 전부 301 수렴. sitemap 326개 URL, lastmod 2026-08-06."),
("A7 속도·무게", "TTFB 0.31s, 초기 HTML 274KB. 기준 300KB 이내."),
])
R.chip("taehalaw.com · 차단", "critical").rows([
("A2 WAF가 AI 검색봇 차단", "GPTBot → errdoc.gabia.io/403.html, OAI-SearchBot → modsecurity.html로 302."),
("결과", "ChatGPT 검색은 \"법무법인 태하\" 메인 사이트를 읽지 못한다. Perplexity는 읽는다."),
("A6 Bing 색인 (미검증)", "ChatGPT 검색은 Bing 인덱스에 의존. Webmaster 등록 여부는 계정 확인 필요."),
])
# 06 B 엔티티
s = d.slide(eyebrow="B · Entity & Structured Data · 68%",
title="스키마는 좋은데 ", title_accent="도메인끼리 서로 모른다", section="B · Entity")
L, R = s.cards2(y=s.band_y)
L.chip("우수", "info").rows([
("B1 JSON-LD 전부 유효", "Organization·WebSite·LegalService×4·WebPage·Breadcrumb·FAQPage가 @graph로 연결."),
("B5 sameAs 6개", "네이버 블로그·Instagram·YouTube·카카오채널·네이버 지도·Google 지도."),
("B2 · B3 LegalService·Attorney", "address·geo·areaServed 있음. Attorney 2명 name·jobTitle·knowsAbout."),
])
R.chip("보완", "warning").rows([
("B6 canonical 오류", "http:// 지정. 성공사례 canonical /success-story/ vs 실제 /success/success_story/."),
("B3 변호사 6명 중 2명만 구조화", "전관 경력(지청장·판사·부장검사)이 스키마에 없음. openingHours 없음."),
("B4 전화 2종, B7 단일 언어", "taehadrug 1533-2403 / taehalaw 1533-1403. hreflang 없음."),
])
# 07 C 콘텐츠 (dark)
s = d.slide(eyebrow="C · Answer-Ready Content · 65%",
title="FAQ와 사례는 있고, ", title_accent="정의문과 표가 없다", dark=True, section="C · Content")
L, R = s.cards2(y=s.band_y)
L.chip("우수", "good").rows([
("C2 FAQ 14문항 + FAQPage", "\"마약 양이 적어도 구속될 수 있어요?\" 등 의뢰인 질문 그대로 H3."),
("C7 전관 경력 텍스트", "지청장·판사·부장검사 역임이 H3로 노출. Attorney 스키마 병행."),
("C8 · C6 사례 자기완결·갱신", "성공사례 H2가 [결과] 사건 유형 형태. sitemap 3주 전 갱신."),
])
R.chip("보완", "warning").rows([
("C1 정의문 없음", "H1 \"마약 사건엔 태하가 답입니다\"는 슬로건. 누구·어디·무엇 정의문 없음."),
("C5 HTML 표 없음", "\"마약사건 처벌현황\"이 표가 아닌 이미지·블록. 형량 비교표가 있어야 인용된다."),
("C4 · C3 수치·제목", "수임료 범위·처리 기간 수치 없음. 첫 H2가 \"#필로폰 #MDMA\" 해시태그."),
])
# 08 D 표면 / E 측정
s = d.slide(eyebrow="D · Surfaces / E · Measurement",
title="광고는 1위, ", title_accent="브랜드 검색은 경쟁사 차지", section="D · Surfaces")
L, R = s.cards2(y=s.band_y)
L.chip("D · 표면 56%", "warning").rows([
("D4 전화번호 4종", "1533-2403 / 1533-1403 / 0507-1495-4404 / 050-71496-4404. NAP가 안 맞는다."),
("D2 GBP 본점 평점 미표시", "수원분사무소는 5.0. 브랜드 검색에 법무법인YK·로엘 스폰서가 상단 노출."),
("D1 · D5 로톡·플레이스", "로톡은 변호사 개인 프로필만 확인. 플레이스 리뷰 220건, 분사무소 5곳."),
])
R.chip("E · 측정 0%", "critical").rows([
("E1 크롤러 로그", "Apache 직접 호스팅. AI 봇 히트 추출과 IP 검증 파이프라인 없음."),
("E2 GA4 AI 유입 채널", "광고에 utm_source=naver는 쓰고 있음. AI 채널그룹은 미확인, 계정 접근 필요."),
("E4 기준선 스팟체크", "미실시. \"마약 변호사\", \"필로폰 초범 집행유예\" 질의 10개 × 5회가 전제."),
])
# 09 Action plan
s = d.slide(eyebrow="Action Plan",
title="1일 안에 고칠 것과 ", title_accent="4주 안에 만들 것", section="Action Plan")
a, b, c = s.cards3(y=s.band_y)
a.chip("P0 · 1일~1주", "critical").title("접근·엔티티·NAP").bullets([
"가비아 WAF에서 GPTBot·OAI-SearchBot·Claude-SearchBot 화이트리스트.",
"canonical https 교정, 6개 도메인 sameAs 상호 연결.",
"대표번호 1개로 홈페이지 6개·플레이스·Google 지도 통일.",
"메인 taehalaw.com에 LegalService + Attorney 전원 스키마.",
])
b.chip("P1 · 4주 내", "warning").title("콘텐츠 구조·측정").bullets([
"각 도메인 첫 단락에 정의문. 메인 H1을 서술형으로.",
"성공사례 결과 분포·처리 기간을 HTML 표로.",
"GA4 AI 채널그룹, 서버로그 IP 검증, GSC·Bing 등록, 기준선 측정.",
])
c.chip("P2 · 지속", "good").title("신선도·시딩").bullets([
"성공사례에 datePublished·dateModified 마크업.",
"YouTube @with_lawyer 영상을 사이트 글로 전사.",
"티스토리·언론 기고로 naver.com 외 언급 도메인 확보.",
"분기 1회 반복 측정. 언급률과 신뢰구간으로만 보고.",
])
# 10 Roadmap (dark)
s = d.slide(eyebrow="Roadmap · 8 Weeks",
title="8주 안에 ", title_accent="B에서 A로", dark=True, section="Roadmap")
s.stats3([
("W1–2", "WAF 해제·canonical·sameAs·NAP 통일.\n기준선 스팟체크 1회."),
("W3–6", "정의문·HTML 표·메인 H1 재설계.\n측정 인프라 설치."),
("W7–8", "재진단. 미검증 5개 반영해\n총점 재산정."),
], y=s.band_y + 0.6)
# 11 What we don't sell
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("만들지 않는 것").bullets([
"llms.txt. Google 공식 효과 0, 채택률 0.13%, 서버 로그에 요청 자체가 없음.",
"도메인 통합 강요. 분야별 랜딩은 광고 전환에 유리하다. sameAs·NAP로 묶는 쪽이 싸다.",
])
b.icon_square().title("약속하지 않는 것").bullets([
"\"스키마 넣으면 인용됩니다\". 태하는 스키마가 좋은데도 ChatGPT가 메인을 못 읽는다.",
"순위·노출 보장. LLM 답변은 동일 질문에도 10~34% 흔들림.",
])
c.icon_square().title("측정 규율").bullets([
"단발 조회로 성과 보고 금지. 반복 측정과 신뢰구간만.",
"답변엔진 UI 자동 조회 금지. OpenAI·Perplexity 약관 위반.",
"AI 유입은 항상 하한값. 35~70%가 Direct로 유실.",
])
# 12 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("채점 방식").bullets([
"항목별 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 이하.",
])
b.icon_square().title("법률 업종 적용").bullets([
"업종 스키마: LegalService · 전문가 스키마: Attorney.",
"버티컬 표면: 로톡·로앤굿 (병의원의 강남언니 자리).",
"E-E-A-T: 전관 경력·전문분야 등록을 텍스트로.",
])
c.icon_square().title("항목마다 붙는 태그").bullets([
"근거 등급: 실측 / 학술 / 벤더 / 추정.",
"통제 가능성: 직접 / 영향 / 불가.",
"채점 방식: 자동 / 반자동 / 수동. 자동 항목은 Edge Function으로 이식.",
])
# 13 Closing
d.closing(eyebrow="Next Step",
title_lines=["WAF 한 줄이 ", "총점을 가르고 있습니다."],
lede="메인 도메인에서 AI 검색봇을 허용하는 데 1일. 그 다음 canonical·sameAs·전화번호 통일까지 1주.",
cta_primary="P0 착수하기 →", cta_secondary="리포트 페이지 보기",
contact="infinith.kr · hello@infinith.kr")
d.save(OUT)
print("saved", OUT)