- Question Bank 120문항(사전판정 ◯5/△35/✕80) + OpenAI·Perplexity API 실측: 브랜드 언급 97~100%, 논브랜드 GPT 11%/PPLX 3%, 시술 정보 52문항 0건 - 러너 run_question_bank_openai.py(재개 가능)·merge_qb_results.py·export_qb_excel.py, 결과 MD 3종 + xlsx 5시트 + raw JSONL - AEO_GEO_RUBRIC.md v1.1 과제 5건: 서브도메인 실측, D3 표면 범위 확대, 엔진별 판정, 브랜드/논브랜드 축, 점수 표기 정합성 - 점수 교정: scoreDiscovery 계산값 50/C(44.1/89, 검증 32/36)로 진단 리포트 덱·POC 제안서 일괄 교정(구판 48, 커밋 메시지 49는 착오) - 진단 리포트 덱 13→14장: Validation 슬라이드 추가, A5·B4·C6 재채점 반영, 연락처 o2oteam@o2o.kr 교정 - POC 제안서 10→14장: 부록 4장(QB 설계·실측 KPI·브랜드vs논브랜드·블로그 인용 증거), 랜딩 스크린샷 50점 화면 재캡처 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0155BJMHJyhqqAPGYjGKYhPS
292 lines
18 KiB
Python
292 lines
18 KiB
Python
"""
|
||
Viewclinic AI Discovery POC Proposal 덱 빌드 (INFINITH Deck 규격, 14페이지)
|
||
|
||
제안 4장 + 진단 리포트 요약 5장 + 부록 4장(User Question Bank 120문항 실측) + 클로징 1장.
|
||
부록 실측 데이터: docs/reports/Viewclinic_QB_Results_Combined.md (2026-09-01, OpenAI·Perplexity API)
|
||
본문 텍스트는 기본값 대비 +2pt (rows/bullets 18, grid 16.25, quote 19.25).
|
||
|
||
python3 docs/reports/build_viewclinic_poc_deck.py docs/reports/Viewclinic_POC_Proposal.pptx
|
||
"""
|
||
import os
|
||
import sys
|
||
sys.path.insert(0, "/Users/haewonkam/.claude/skills/infinith-deck/scripts")
|
||
import ifd
|
||
from ifd import Deck
|
||
|
||
BODY = 18.0 # rows 본문·불릿 (기본 16 + 2)
|
||
GRID = 16.25 # 스코어카드 격자 셀 (기본 14.25 + 2)
|
||
QUOTE = 19.25 # 인용 본문 (기본 17.25 + 2)
|
||
|
||
|
||
def quote_up(s, text, who, x=1.25, y=6.63, w=17.5, h=1.83):
|
||
"""ifd.Slide.quote 와 동일 레이아웃, 본문만 +2pt."""
|
||
ifd._rect(s._raw, x, y, w, h, fill=ifd.LIGHT_BG if not s.dark else ifd.DARK_CARD, line=None, radius=0)
|
||
ifd._rect(s._raw, x, y, 0.04, h, fill=ifd.VIOLET, line=None, radius=0)
|
||
ifd._check(text, w - 0.5, QUOTE, 2, "quote")
|
||
ifd._write(ifd._tb(s._raw, x + 0.28, y + 0.25, w - 0.5, h - 0.9), text, QUOTE, ifd.SANS,
|
||
color=ifd.WHITE if s.dark else ifd.INK, line_spacing=1.2)
|
||
ifd._write(ifd._tb(s._raw, x + 0.28, y + h - 0.6, w - 0.5, 0.44), who, 18.0, ifd.SANS,
|
||
color=ifd.MUTED_DK if s.dark else ifd.MUTED)
|
||
|
||
|
||
OUT = sys.argv[1] if len(sys.argv) > 1 else "Viewclinic_POC_Proposal.pptx"
|
||
d = Deck()
|
||
|
||
# 01 Cover
|
||
d.cover(
|
||
eyebrow="PROOF OF CONCEPT · POC PROPOSAL",
|
||
title_lines=["AI Discovery,", "AEO / GEO."],
|
||
subtitle="뷰성형외과 × INFINITH · AI 답변엔진 노출 구축 검증 제안 · 2026. 09. 01",
|
||
meta_right="AI Discovery Proposal · 2026",
|
||
)
|
||
|
||
# 02 Why now (dark)
|
||
s = d.slide(eyebrow="Why Now · AEO / GEO",
|
||
title="고객은 이미 AI에게 ", title_accent="병원을 묻습니다",
|
||
lede=["검색은 링크 목록에서 하나의 답변으로 바뀌었습니다.",
|
||
"답변에 인용되지 못한 병원은 비교 대상에도 오르지 못합니다."],
|
||
dark=True, section="Why Now")
|
||
L, R = s.cards2(y=s.band_y)
|
||
L.chip("시장의 변화", "info").rows([
|
||
("검색이 답변으로 바뀌었다", "ChatGPT·Perplexity·네이버 AI 브리핑이 병원을 직접 추천. 클릭 없이 답변만 소비되는 비중이 커지는 중."),
|
||
("AI는 인용으로만 노출한다", "답변엔진은 크롤링 가능한 오픈웹 근거를 골라 인용. 광고 입찰이 없어 근거를 먼저 갖춘 병원이 선점."),
|
||
("네이버 자산은 기여 0", "네이버 블로그·플레이스는 robots.txt로 AI 크롤러를 전면 차단(2026-08 실측). 기존 네이버 마케팅이 AI 답변에 닿지 않음."),
|
||
], b_size=BODY)
|
||
R.chip("뷰성형외과 실측 · 50 / 100 · C", "critical").rows([
|
||
("읽을 수는 있다", "robots.txt가 AI 검색봇 12종을 전부 허용. 크롤러 접근성 98%로 기술적 전제는 이미 확보."),
|
||
("인용할 근거가 없다", "MedicalClinic 스키마 없음, FAQ 0개, 정의문 0개. 답변 인용 가능 콘텐츠 점수 28%."),
|
||
("성과를 잴 수도 없다", "AI 유입 측정 인프라 0%. AI가 보내는 환자를 세는 장치 자체가 아직 없음."),
|
||
], b_size=BODY)
|
||
|
||
# 03 Offering — 랜딩 히어로 스크린샷 + 3스텝
|
||
from pptx.util import Inches
|
||
|
||
SHOT = os.path.join(os.path.dirname(os.path.abspath(__file__)), "shots")
|
||
s = d.slide(eyebrow="Our Offering · AI Discovery",
|
||
title="진단하고, 구축하고, ", title_accent="숫자로 증명합니다",
|
||
lede="AI Discovery는 36개 항목 실측 진단, 우선순위 구축, 반복 측정 보고까지 한 파이프라인으로 제공합니다.",
|
||
section="Offering")
|
||
img_h = 9.9 - s.band_y
|
||
img_w = img_h * (1440 / 1402)
|
||
ifd._rect(s._raw, 1.25 - 0.05, s.band_y - 0.05, img_w + 0.10, img_h + 0.10,
|
||
fill="FFFFFF", line=ifd.CARD_LINE, radius=0.06)
|
||
s._raw.shapes.add_picture(f"{SHOT}/land_hero.png", Inches(1.25), Inches(s.band_y), height=Inches(img_h))
|
||
rx = 1.25 + img_w + 0.40
|
||
R = ifd.Card(s, rx, s.band_y, 18.75 - rx, img_h, s.dark)
|
||
R.rows([
|
||
("STEP 1 · Audit", "AEO/GEO 기준표 v1.0으로 36개 항목 실측. 뷰성형외과 1차 진단 완료, 50점 등급 C."),
|
||
("STEP 2 · Build", "스키마 삽입, NAP 통일, 정의문·FAQ 구축. AI가 인용할 근거를 홈페이지에 만듭니다."),
|
||
("STEP 3 · Prove", "GA4·서버로그·반복 스팟체크 설치. 언급률과 신뢰구간으로만 보고합니다."),
|
||
], b_size=BODY)
|
||
|
||
# 04 What you get (KPI + quote)
|
||
s = d.slide(eyebrow="What You Get · 8 Weeks",
|
||
title="8주 후, 이렇게 ", title_accent="달라집니다",
|
||
lede="목표는 두 가지입니다. 인용될 기술적 전제를 완성하고, AI가 보내는 환자를 처음으로 계측합니다.",
|
||
section="Outcome")
|
||
k1, k2, k3, k4 = s.cards4(y=s.band_y, h=2.55)
|
||
k1.kpi("50 → 61+", "AI ANSWER READINESS", "등급 C에서 B로")
|
||
k2.kpi("100%", "AI 유입 측정 인프라", "현재 0%. GA4·서버로그 설치")
|
||
k3.kpi("36 / 36", "검증 항목 커버리지", "미검증 4개를 계정 연동으로 해소")
|
||
k4.kpi("19,316", "강남언니 후기", "기존 자산을 AI 인용 표면으로 연결")
|
||
quote_up(s, "스키마를 넣으면 인용된다고 약속하지 않습니다. 인용될 수 없는 이유를 전부 제거하고, 결과를 반복 측정으로 증명합니다.",
|
||
"INFINITH 정직성 규약")
|
||
|
||
# ── 진단 리포트 요약 5장 ──────────────────────────────────────────────────────
|
||
|
||
# 05 Report 1 · Executive summary
|
||
s = d.slide(eyebrow="Report 1 / 5 · Executive Summary",
|
||
title="읽을 수는 있지만, ", title_accent="인용할 근거가 없습니다",
|
||
lede=["36개 항목 중 32개를 실측했고, 오너 계정이 필요한 4개는 점수 분모에서 제외했습니다.",
|
||
"접근성(A) 98%와 표면(D) 68%는 양호. 엔티티(B) 25%와 콘텐츠(C) 28%가 총점을 끌어내립니다."],
|
||
section="Report · Summary")
|
||
img_h = 9.9 - s.band_y
|
||
img_w = img_h * (1440 / 950)
|
||
ix = 1.25 + (17.5 - img_w) / 2
|
||
ifd._rect(s._raw, ix - 0.05, s.band_y - 0.05, img_w + 0.10, img_h + 0.10,
|
||
fill="FFFFFF", line=ifd.CARD_LINE, radius=0.06)
|
||
s._raw.shapes.add_picture(f"{SHOT}/land_report.png", Inches(ix), Inches(s.band_y), height=Inches(img_h))
|
||
|
||
# 06 Report 2 · What AI sees (dark)
|
||
s = d.slide(eyebrow="Report 2 / 5 · What AI Sees",
|
||
title="문제는 차단이 아니라 ", title_accent="내용입니다", dark=True, section="Report · Findings")
|
||
L, R = s.cards2(y=s.band_y)
|
||
L.chip("정상", "good").rows([
|
||
("크롤러 접근 정상", "robots.txt가 OAI-SearchBot·PerplexityBot 등 12종을 허용. Cloudflare 차단 없음."),
|
||
("서버 렌더링 본문", "WordPress가 본문을 서버에서 렌더. JS 없이 10,757자 추출."),
|
||
("강남언니·YouTube 활발", "강남언니 후기 19,316건, 의료진 26명. YouTube 채널이 영상을 꾸준히 발행."),
|
||
], b_size=BODY)
|
||
R.chip("치명", "critical").rows([
|
||
("누구인지 확정 불가", "MedicalClinic 스키마 없음, sameAs 비어 있음. 시술 목록 JSON-LD는 문법 오류로 무효."),
|
||
("인용할 문장 없음", "H1 5개가 전부 영문 라벨. FAQ 0개, HTML 표 0개, 정의문 0개."),
|
||
("표면 간 NAP 불일치", "GBP에 전화번호 없음. 웹사이트가 viewplasticsurgery.com. 주소 표기 3종."),
|
||
], b_size=BODY)
|
||
|
||
# 07 Report 3 · Scorecard
|
||
s = d.slide(eyebrow="Report 3 / 5 · Scorecard · 6 Categories",
|
||
title="카테고리별 점수와 ", title_accent="검증 범위를 정리했습니다",
|
||
lede="점수는 검증된 항목만으로 정규화했습니다. 미검증 항목을 확인하면 총점이 바뀔 수 있습니다.",
|
||
section="Report · 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", "7.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, "critical"], [None, None, None, None, "good"],
|
||
[None, None, None, None, "critical"], [None, None, None, None, "warning"]],
|
||
row_h=0.62, size=GRID,
|
||
)
|
||
Rail.title("읽는 법").bullets([
|
||
"획득 분모는 검증 항목의 배점 합. E는 미검증 3개라 분모가 2점뿐.",
|
||
"총점 44.1 / 89 = 50점, 등급 C.",
|
||
"미검증 4개는 계정 접근으로 1일 안에 채웁니다.",
|
||
], size=BODY)
|
||
|
||
# 08 Report 4 · Action plan
|
||
s = d.slide(eyebrow="Report 4 / 5 · Action Plan",
|
||
title="즉시 고칠 것과 ", title_accent="4주 안에 만들 것을 나눴습니다", section="Report · Action Plan")
|
||
a, b, c = s.cards3(y=s.band_y)
|
||
a.chip("P0 · 1일~1주", "critical").title("엔티티·표면·측정").bullets([
|
||
"깨진 JSON-LD 수정, MedicalClinic 스키마, sameAs 연결.",
|
||
"GBP 전화번호 등록, 웹사이트를 viewclinic.com으로 교정.",
|
||
"4개 표면 NAP를 한 글자까지 통일. 두 도메인 hreflang 연결.",
|
||
"GA4 AI 채널그룹, 서버로그 IP 검증, GSC·Bing 등록.",
|
||
], size=BODY)
|
||
b.chip("P1 · 4주 내", "warning").title("콘텐츠 구조").bullets([
|
||
"핵심 페이지 첫 단락에 정의문. 시술별 FAQ 5~8개.",
|
||
"페이지당 H1 1개 서술형. 비교표를 HTML table로.",
|
||
"WordPress 업그레이드, alt 누락 111개 보완, HTML 무게 절감.",
|
||
], size=BODY)
|
||
c.chip("P2 · 지속", "good").title("신선도·시딩").bullets([
|
||
"dateModified 마크업 활성화, 월 1회 사이트 갱신.",
|
||
"유튜브 영상을 사이트 글로 전사해 신선도를 전달.",
|
||
"티스토리·언론 기고로 naver.com 외 언급 도메인 확보.",
|
||
"분기 1회 반복 측정. 언급률과 신뢰구간으로만 보고.",
|
||
], size=BODY)
|
||
|
||
# 09 Report 5 · Roadmap (dark)
|
||
s = d.slide(eyebrow="Report 5 / 5 · Roadmap · 8 Weeks",
|
||
title="8주 안에 ", title_accent="C에서 B로 올립니다", dark=True, section="Report · Roadmap")
|
||
s.stats3([
|
||
("W1–2", "스키마·NAP·측정 인프라 설치.\n기준선 스팟체크 1회."),
|
||
("W3–6", "정의문·FAQ·H1 재설계·HTML 표.\nWordPress 업그레이드."),
|
||
("W7–8", "재진단. 미검증 4개 반영해\n총점 재산정."),
|
||
], y=s.band_y + 0.6)
|
||
|
||
# ── 부록 4장 · User Question Bank 120문항 실측 ───────────────────────────────
|
||
|
||
# 10 Appendix 1 · Question Bank 설계
|
||
s = d.slide(eyebrow="Appendix 1 / 4 · User Question Bank",
|
||
title="환자의 질문 120개를 ", title_accent="먼저 만들었습니다",
|
||
lede=["잠재 환자가 AI에게 실제로 묻는 문장을 퍼널 4단계(인지·탐색·비교·결정)로 설계했습니다.",
|
||
"질문마다 진단 실측 기준의 예상 판정을 붙여, 구축 전후 비교의 기준선으로 씁니다."],
|
||
section="Appendix · Question Bank")
|
||
W, Rail = s.cards2(y=s.band_y, split=0.66)
|
||
W.grid(
|
||
headers=["카테고리", "문항", "대표 질문"],
|
||
col_w=[3.2, 1.0, 6.7],
|
||
rows=[
|
||
["브랜드 직접", "12", "뷰성형외과 어때? 믿을만한 병원이야?"],
|
||
["위치·운영", "10", "뷰성형외과 진료시간 어떻게 돼?"],
|
||
["지역 추천·비교", "15", "강남에서 가슴성형 잘하는 병원 어디야?"],
|
||
["시술 정보 7분야", "58", "가슴 보형물 종류 뭐가 있어?"],
|
||
["신뢰·안전", "9", "성형외과 고를 때 뭘 확인해야 해?"],
|
||
["가격·이벤트", "8", "뷰성형외과 지금 하는 이벤트 있어?"],
|
||
["후기·평판", "8", "뷰성형외과 실제 후기 어때?"],
|
||
],
|
||
row_h=0.56, size=GRID,
|
||
)
|
||
Rail.title("설계 원칙").bullets([
|
||
"검색 키워드가 아니라 채팅형 AI에 입력하는 구어체 문장.",
|
||
"브랜드 질문 36개, 논브랜드 질문 84개. 승부처는 병원 이름을 모르는 환자의 84개.",
|
||
"사전판정: 인용 기대 5, 불안정 35, 인용 불가 80.",
|
||
], size=BODY)
|
||
|
||
# 11 Appendix 2 · 실측 방법과 요약 (dark)
|
||
s = d.slide(eyebrow="Appendix 2 / 4 · Measurement",
|
||
title="두 답변엔진에 120문항을 ", title_accent="전부 실측했습니다",
|
||
lede="OpenAI와 Perplexity 정식 API로 서울 위치를 고정하고, 질문 원문 그대로 1회씩 질의했습니다.",
|
||
dark=True, section="Appendix · Measurement")
|
||
k1, k2, k3, k4 = s.cards4(y=s.band_y, h=2.55)
|
||
k1.kpi("97%", "CHATGPT · 브랜드 질문", "36문항 중 35문항 언급")
|
||
k2.kpi("11%", "CHATGPT · 논브랜드", "84문항 중 10문항 언급")
|
||
k3.kpi("100%", "PERPLEXITY · 브랜드 질문", "36문항 중 36문항 언급")
|
||
k4.kpi("3%", "PERPLEXITY · 논브랜드", "84문항 중 3문항 언급")
|
||
quote_up(s, "소비자 UI 자동 조회는 약관 위반이라 쓰지 않았습니다. 정식 API 측정과 반복 스팟체크로만 보고합니다.",
|
||
"INFINITH 정직성 규약")
|
||
|
||
# 12 Appendix 3 · 브랜드 vs 논브랜드
|
||
s = d.slide(eyebrow="Appendix 3 / 4 · Findings",
|
||
title="이름을 모르는 환자에게는 ", title_accent="존재하지 않습니다",
|
||
lede=["병원명을 아는 환자의 질문에는 두 엔진 모두 답합니다.",
|
||
"병원을 찾는 질문에서는 경쟁 병원이 추천 자리를 가져갑니다."],
|
||
section="Appendix · Findings")
|
||
L, R = s.cards2(y=s.band_y)
|
||
L.chip("브랜드 질문 · 언급 97~100%", "good").rows([
|
||
("이름을 대면 답한다", "뷰성형외과를 지목한 36문항은 위치·평점·안전 정보까지 서술. 인지도 문제는 아님."),
|
||
("근거는 남의 표면", "인용 출처는 강남언니·GetCareKorea·뉴스. 자사 홈페이지가 근거로 쓰인 답변은 예외적."),
|
||
("오답 위험 실증", "주차 질문에 Perplexity는 35대 무료로 정답, ChatGPT는 시설 없음으로 추측. 구조화 부재의 비용."),
|
||
], b_size=BODY)
|
||
R.chip("논브랜드 질문 · 언급 3~11%", "critical").rows([
|
||
("추천 목록에 없다", "추천·비교·시술 정보 84문항에서 Perplexity 3문항, ChatGPT 10문항만 뷰를 언급."),
|
||
("경쟁 병원이 선점", "Perplexity 추천은 디에이 4회, 우리 4회, 바노바기·JK 각 3회. 아이디병원은 1순위 2회."),
|
||
("시술 정보 질문 전멸", "가격·회복기간·보형물 비교 질문에서 인용 0. FAQ 0개, HTML 표 0개의 직접 결과."),
|
||
], b_size=BODY)
|
||
|
||
# 13 Appendix 4 · 핵심 증거
|
||
s = d.slide(eyebrow="Appendix 4 / 4 · Key Evidence",
|
||
title="멈춘 블로그가 아직 ", title_accent="인용되고 있습니다",
|
||
lede="ChatGPT가 논브랜드 질문에서 뷰성형외과를 인용한 드문 사례의 출처는 2024년 5월에 멈춘 자사 블로그였습니다.",
|
||
section="Appendix · Evidence")
|
||
a, b, c = s.cards3(y=s.band_y)
|
||
a.chip("발견", "info").title("실측 사례 C-02").bullets([
|
||
"강남에서 가슴성형 잘하는 병원을 묻자 뷰성형외과가 2순위로 등장.",
|
||
"인용 출처는 blog.viewclinic.com. 2024-05 이후 갱신이 멈춘 블로그.",
|
||
], size=BODY)
|
||
b.chip("해석", "violet").title("텍스트 자산은 인용된다").bullets([
|
||
"유튜브 영상 1,064개보다 오래된 블로그 글이 인용에 기여.",
|
||
"답변엔진은 크롤링 가능한 텍스트만 근거로 쓴다는 직접 증거.",
|
||
], size=BODY)
|
||
c.chip("함의", "good").title("P1 콘텐츠의 근거").bullets([
|
||
"정의문·FAQ·비교표가 생기면 논브랜드 언급률이 움직입니다.",
|
||
"8주 재측정에서 같은 120문항으로 변화를 확인합니다.",
|
||
], size=BODY)
|
||
|
||
# 14 Closing — 제안서 포맷 (웹 CTA 버튼 없음, 다음 단계 3줄)
|
||
raw = d._new(False)
|
||
ifd._write(ifd._tb(raw, ifd.MARGIN, 2.12, ifd.CONTENT_W, 0.47), "NEXT STEP · POC PROPOSAL",
|
||
ifd.T_EYEBROW, ifd.SANS, bold=True, color=ifd.VIOLET, spc=300)
|
||
_title = ["진단은 끝났습니다.", "8주로 증명하겠습니다."]
|
||
_th = 96 / 72 * 1.2 * 2
|
||
ifd._write(ifd._tb(raw, ifd.MARGIN, 2.97, ifd.CONTENT_W, _th), _title, 96.0, ifd.SANS,
|
||
bold=True, color=ifd.INK, line_spacing=0.98)
|
||
_y = 2.97 + _th + 0.35
|
||
ifd._write(ifd._tb(raw, ifd.MARGIN, _y, ifd.CONTENT_W, 0.62),
|
||
"8주 구축과 재진단으로 C를 B로 올리고, 전 과정을 숫자로 보고합니다.",
|
||
27.75, ifd.SANS, color=ifd.BODY)
|
||
_y += 1.10
|
||
for num, label, desc in [
|
||
("01", "검토", "본 제안서와 36개 항목 진단 리포트 전문을 검토합니다."),
|
||
("02", "계정 연동", "GA4·서버로그·GSC·Bing 권한 공유 시 미검증 4개 항목을 1일 안에 확정합니다."),
|
||
("03", "킥오프", "W1 스키마·NAP·측정 인프라 설치부터 착수합니다."),
|
||
]:
|
||
ifd._write(ifd._tb(raw, ifd.MARGIN, _y, 0.75, 0.45), num, 19.5, ifd.SANS, bold=True, color=ifd.VIOLET)
|
||
ifd._write(ifd._tb(raw, ifd.MARGIN + 0.85, _y, 2.1, 0.45), label, 19.5, ifd.SANS, bold=True, color=ifd.INK)
|
||
ifd._write(ifd._tb(raw, ifd.MARGIN + 3.05, _y, 14.4, 0.45), desc, 19.5, ifd.SANS, color=ifd.BODY)
|
||
_y += 0.60
|
||
ifd._write(ifd._tb(raw, ifd.MARGIN, ifd.Y_FOOTER - 0.1, 6.0, 0.6), "INFINITH", 30.0, ifd.SERIF,
|
||
bold=True, color=ifd.INK, spc=300)
|
||
ifd._write(ifd._tb(raw, 11.0, ifd.Y_FOOTER, 7.75, 0.49), "o2oteam@o2o.kr", 20.25, ifd.SANS,
|
||
color=ifd.MUTED, align=ifd.PP_ALIGN.RIGHT)
|
||
|
||
d.save(OUT)
|
||
print("saved", OUT)
|