- 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>
70 lines
3.3 KiB
Python
70 lines
3.3 KiB
Python
"""
|
|
/discovery/:id 리포트 페이지를 단일 HTML 파일로 내보낸다.
|
|
|
|
npm run build && npx vite preview --port 3012
|
|
python3 docs/reports/landing/export_html.py taeha docs/reports/taeha/Taeha_AI_Answer_Readiness_Report.html
|
|
|
|
- 헤드리스 Chrome으로 렌더된 DOM을 덤프하고 빌드 CSS를 인라인한다
|
|
- Navbar·Footer·스크립트 제거, motion 애니메이션 고정
|
|
- ScoreRing 오프셋과 카테고리 진행 바 폭을 점수 텍스트에서 다시 계산한다
|
|
"""
|
|
import glob, re, subprocess, sys
|
|
|
|
CHROME = "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome"
|
|
BASE = "http://localhost:3012"
|
|
|
|
slug, out = sys.argv[1], sys.argv[2]
|
|
title = sys.argv[3] if len(sys.argv) > 3 else f"{slug} AI Answer Readiness Report"
|
|
|
|
dom = subprocess.run(
|
|
[CHROME, "--headless=new", "--disable-gpu", "--window-size=1440,900", "--virtual-time-budget=20000",
|
|
"--dump-dom", f"{BASE}/discovery/{slug}"],
|
|
capture_output=True, text=True).stdout
|
|
css = open(glob.glob("dist/assets/*.css")[0], encoding="utf-8").read()
|
|
|
|
m = re.search(r'<div id="root">(.*)</div>\s*</body>', dom, re.S)
|
|
body = m.group(1)
|
|
body = re.sub(r"<script.*?</script>", "", body, flags=re.S)
|
|
body = re.sub(r"<nav\b.*?</nav>", "", body, flags=re.S, count=1)
|
|
body = re.sub(r"<footer\b.*?</footer>", "", body, flags=re.S)
|
|
|
|
# ScoreRing: dashoffset = circumference * (1 - score/100)
|
|
def fix_ring(mm):
|
|
circ = float(mm.group(2)); score = int(mm.group(5))
|
|
return f"{mm.group(1)}{circ * (1 - min(score, 100) / 100):.4f}{mm.group(4)}{score}{mm.group(6)}"
|
|
body, n_ring = re.subn(
|
|
r'(stroke-dasharray="([\d.]+)"\s+stroke-dashoffset=")([\d.]+)("></circle></svg><div class="absolute inset-0 flex items-center justify-center"><span[^>]*>)(\d+)(</span>)',
|
|
fix_ring, body)
|
|
|
|
# 카테고리 바: <div class="h-full rounded-full" style="background: X; width: 0%;"> … <span class="font-semibold">NN%</span>
|
|
def fix_bar(mm):
|
|
return f'{mm.group(1)}width: {mm.group(3)}%;{mm.group(4)}{mm.group(3)}%{mm.group(5)}'
|
|
body, n_bar = re.subn(
|
|
r'(<div class="h-full rounded-full" style="[^"]*?)width:\s*[\d.]+%;?([^"]*"[^>]*></div></div><div class="flex justify-between mt-2 text-sm"><span class="font-semibold">)(\d+)(%</span>)',
|
|
lambda mm: f'{mm.group(1)}width: {mm.group(3)}%;{mm.group(2)}{mm.group(3)}{mm.group(4)}', body)
|
|
|
|
html = f"""<!DOCTYPE html>
|
|
<html lang="ko">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>{title}</title>
|
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=Playfair+Display:ital,wght@0,400;0,500;0,600;0,700;0,900;1,400&display=swap" rel="stylesheet">
|
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.9/dist/web/variable/pretendardvariable-dynamic-subset.min.css">
|
|
<style>{css}</style>
|
|
<style>
|
|
[style*="opacity"], [style*="transform"] {{ opacity: 1 !important; transform: none !important; }}
|
|
html, body {{ background: #fff; }}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div class="min-h-screen bg-white">
|
|
{body}
|
|
</div>
|
|
</body>
|
|
</html>"""
|
|
open(out, "w", encoding="utf-8").write(html)
|
|
print(f"saved {out} ({len(html):,} bytes) rings={n_ring} bars={n_bar}")
|