feat: 뷰성형외과 서포터즈 샘플 사이트 v3 + 설계 v0.1 + 공식 사이트 대비 AEO 감사
- supporters/: Astro 사이트 소스 (pages·components·posts·factSheet/authors/videos 데이터, 뉴스룸 916건 빌더, 발행 게이트 check.mjs, vercel.json) - supporters/docs/: v1~v3 스크린샷 기록 - docs/INFINITH_Viewclinic_Supporters_Site_Design_v0.1.md: 서포터즈 사이트 설계 문서 - docs/NEXT_SESSION_SUPPORTERS_AUTOBUILD.md: 다음 세션 자동 빌드 프롬프트 - docs/reports/Viewclinic_Supporters_vs_Official_AEO_Audit.xlsx + scripts/build_supporters_audit_xlsx.py: 서포터즈 vs 공식 사이트 AEO/GEO 감사 비교 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
243
docs/INFINITH_Viewclinic_Supporters_Site_Design_v0.1.md
Normal file
@ -0,0 +1,243 @@
|
||||
# 뷰성형외과 서포터즈 웹 설계 제안 v0.1
|
||||
|
||||
**작성:** 2026-09-04 · INFINITH AI Discovery 후속 · **상태:** 초안(haewon 검토 전)
|
||||
**전제 진단:** /discovery/viewclinic 50점/C (A98 · B25 · C28 · D68 · E0 · F33%), QB 120문항 논브랜드 언급률 GPT 11% · PPLX 3%
|
||||
|
||||
---
|
||||
|
||||
## 0. 한 줄 요약
|
||||
|
||||
병원 홈페이지·유튜브·인스타 자산을 **복제하지 않고 "환자 질문에 대한 답"으로 변환해 발행하는 별도 매체**를 만듭니다. 기업은행 티스토리와 같은 개념이되, 우리가 통제하는 도메인에 두어 AI 크롤러 방문과 유입을 직접 계측합니다. 편집 캘린더는 이미 만들어 둔 QB 120문항입니다.
|
||||
|
||||
---
|
||||
|
||||
## 1. 오늘 확정한 것과 미결 사항
|
||||
|
||||
| 항목 | 결정 | 설계에 미치는 영향 |
|
||||
|---|---|---|
|
||||
| 사이트 주소 | **미결.** 별도 도메인 vs 병원 서브도메인 2안(§3). 남의 플랫폼은 제외 확정 | 중복 판정 거리, 초기 신뢰도, 계측 가능 여부 |
|
||||
| 사용 가능 자산 | 유튜브 전사+임베드 / 홈페이지·인스타 이미지 캡처 / 환자 후기·경험담 | 전후사진은 제외. 후기는 §7 규칙으로 제한 |
|
||||
| 작성자 표기 | 실명 서포터즈 + 원장 감수 | Person 스키마 + reviewedBy Physician 구조 |
|
||||
| 목표 표면 | AI 답변엔진과 네이버 동등 | 허브(자체 도메인 원문) + 네이버 블로그 재작성 2트랙 |
|
||||
|
||||
---
|
||||
|
||||
## 2. 목적과 성공 지표
|
||||
|
||||
이 사이트는 브랜딩 매체가 아니라 **AI 인용 표면 + 계측 장치**입니다. 성과는 기존 QB 120문항을 같은 방식으로 재측정해 증명합니다.
|
||||
|
||||
| 지표 | 현재 | 8주 목표 | 측정 방법 |
|
||||
|---|---|---|---|
|
||||
| 논브랜드 84문항 뷰 언급률 (GPT) | 11% | 20% 이상 | `scripts/run_question_bank_openai.py` 재실행 |
|
||||
| 논브랜드 84문항 뷰 언급률 (PPLX) | 3% | 10% 이상 | 동일 러너, sonar |
|
||||
| 뷰 인용 시 출처 도메인 | blog.viewclinic.com(정지 블로그)·강남언니 | 서포터즈 도메인 등장 | JSONL 인용 URL 집계 |
|
||||
| AI 크롤러 방문 | 계측 불가(E 0%) | GPTBot·OAI-SearchBot·PerplexityBot·ClaudeBot·Google-Extended 일별 히트 | 서버 로그 UA + IP 대역 검증 |
|
||||
| AI 유입 세션 | 계측 불가 | 주간 집계 | GA4 AI 채널그룹(chatgpt.com·perplexity.ai·gemini 리퍼러) |
|
||||
| 네이버 유입 | 없음 | 서치어드바이저 노출·클릭 | 서치어드바이저 + 블로그 통계 |
|
||||
|
||||
언급률은 신뢰구간과 함께만 보고합니다. "스키마를 넣으면 인용된다"고 약속하지 않습니다(INFINITH 정직성 규약 유지).
|
||||
|
||||
---
|
||||
|
||||
## 3. 사이트 주소 2안 비교표
|
||||
|
||||
남의 플랫폼(티스토리 등)은 robots·스키마·서버로그를 통제할 수 없어 AI 크롤러 계측이 불가능하므로 후보에서 제외했습니다(2026-09-04 haewon 결정).
|
||||
|
||||
| 기준 | A. 별도 도메인 (예: viewsupporters.kr) | B. 병원 서브도메인 (supporters.viewclinic.com) |
|
||||
|---|---|---|
|
||||
| 검색엔진이 보는 정체 | 병원을 다루는 제3의 사이트 | 병원과 같은 사이트 |
|
||||
| 중복·클론 판정 거리 | 가장 멂 | 가까움. 내용 유사 시 canonical 통합으로 한쪽만 노출 |
|
||||
| AI가 보는 "언급 도메인 수" 효과 | +1 도메인 | 없음(같은 도메인) |
|
||||
| 초기 검색 신뢰도 | 0에서 시작 | 홈페이지 권위 상속 |
|
||||
| robots·스키마·서버로그 통제 | 전부 가능 | 전부 가능(병원 DNS 협조 필요) |
|
||||
| AI 크롤러 접근 | 우리가 전면 허용 | 우리가 전면 허용 |
|
||||
| 의료광고 판정 관점 | 운영 주체·비용 부담에 따라 판단 | 병원 광고로 명확 |
|
||||
| 홈페이지 문제의 전염 | 없음. 독립 채점 | WordPress 5.5.3 등 본 사이트 감점 요인이 같은 도메인 평가에 섞임 |
|
||||
| 개설 속도 | 1~2주 | 1~2주 + 병원 IT 협의(DNS 위임) |
|
||||
| 비용 | 도메인 + Vercel(월 2만원 내외) | 동일. 도메인 비용 없음 |
|
||||
| INFINITH 권고 | **1순위** | 2순위 |
|
||||
|
||||
**권고 근거:** 이 프로젝트가 증명할 것은 "AI가 뷰성형외과를 인용하는가"이고, AI는 자사 사이트보다 다른 도메인의 언급을 더 신뢰합니다. 별도 도메인은 중복 위험도 구조적으로 제거합니다. 초기 권위 0 문제는 병원 홈페이지·유튜브·강남언니·GBP에서 서포터즈 사이트로 sameAs와 링크를 걸어 보완합니다.
|
||||
|
||||
**B안이 맞는 경우:** 병원이 "공식 채널"로 운영하길 원하고 의료광고 심의를 어차피 받기로 했다면, 권위 상속 이점이 있는 B안이 낫습니다. 이 경우 §4.2 규칙 1·2(문장 재사용 금지, 정보 유형 분리)를 더 엄격히 적용해야 홈페이지와의 canonical 충돌을 피할 수 있습니다.
|
||||
|
||||
---
|
||||
|
||||
## 4. 중복·클론 판정을 피하는 원칙
|
||||
|
||||
### 4.1 검색엔진별로 위험의 성격이 다릅니다
|
||||
|
||||
| 엔진 | 실제 동작 | 우리에게 생기는 위험 |
|
||||
|---|---|---|
|
||||
| 구글 | 중복 자체에 페널티는 없음. 같은 내용은 canonical로 하나만 남기고 통합 | (1) 스크랩 사이트로 분류돼 색인 제외 (2) 홈페이지와 서포터즈가 같은 키워드로 경쟁해 서로 순위를 깎음 |
|
||||
| 네이버 | 유사문서 판독 시스템이 뒤에 발행된 유사 글을 검색에서 제외. 출처 신뢰(C-Rank) 반영 | 홈페이지·네이버 블로그·서포터즈 중 원본 판정을 못 받은 쪽이 사라짐 |
|
||||
| AI 답변엔진 | 크롤링 가능한 텍스트를 근거로 인용. 여러 도메인이 일관되게 말하면 신뢰 상승 | 위험보다 기회. 단, 사실이 도메인마다 다르면(NAP 혼재) 오답 |
|
||||
|
||||
### 4.2 운영 규칙 6가지
|
||||
|
||||
1. **원문 재사용률 상한.** 홈페이지 문장을 그대로 옮기지 않습니다. 사실(의료진 수, 개원 연도, 진료시간)은 가져오되 문장은 새로 씁니다. 발행 전 원문 대비 연속 일치 문자열을 검사합니다(§9 파이프라인의 자동 게이트).
|
||||
2. **매체 정체성을 다르게 둡니다.** 홈페이지는 "병원이 병원을 소개"하고, 서포터즈는 "환자 관점의 질문과 답"입니다. 같은 시술을 다뤄도 홈페이지에 없는 정보 유형(준비 체크리스트, 회복 일정표, 비교표, FAQ)을 중심에 둡니다.
|
||||
3. **유튜브는 매체 변환이라 중복이 아닙니다.** 영상 발화를 글로 재구성하고 원본 영상을 임베드합니다. 영상 설명란 텍스트는 복사하지 않습니다.
|
||||
4. **이미지 캡처는 재문맥화합니다.** 원본 alt·캡션을 쓰지 않고 글 문맥에 맞는 alt와 캡션을 새로 씁니다. 출처(뷰성형외과 인스타그램 @계정, 게시일)를 캡션에 표기합니다.
|
||||
5. **발행 순서로 원본을 확정합니다.** 허브(자체 도메인)에 먼저 발행하고, 네이버 블로그는 24시간 이후 요약 재작성본을 올리며 허브 링크를 답니다. 네이버 글은 허브 글의 60% 이하 분량으로 다른 문장을 씁니다.
|
||||
6. **사실은 한 곳에서 관리합니다.** 주소·전화·진료시간·의료진 수는 팩트 시트 하나에서 불러와 홈페이지 표기와 한 글자까지 맞춥니다(진단 B4 NAP 혼재 문제를 서포터즈에서 재생산하지 않습니다).
|
||||
|
||||
---
|
||||
|
||||
## 5. 콘텐츠 엔진: QB 120문항이 편집 캘린더입니다
|
||||
|
||||
### 5.1 카테고리별 글 포맷
|
||||
|
||||
| QB 카테고리 | 문항 수 | 현재 ✕/△ | 글 포맷 | 주 원천 |
|
||||
|---|---|---|---|---|
|
||||
| A 브랜드 직접 | 12 | 대부분 △·✕ | "뷰성형외과는 어떤 곳인가" 정의문 시리즈(연혁·규모·의료진·방송 이력) | 홈페이지 사실 + 유튜브 채널 소개 영상 |
|
||||
| B 위치·운영 | 10 | 절반 ✕ | 방문 가이드(주차·층별 안내·예약 경로·공휴일) | 인스타 시설 사진 + 홈페이지 |
|
||||
| C 지역 추천·비교 | 15 | 거의 ✕ | 시술별 "선택 기준" 가이드 + 병원 특성 비교표 | 유튜브 원장 설명 영상 |
|
||||
| D 시술별 정보탐색 | 58 | 대부분 ✕ | 시술 FAQ 페이지(정의·적응증·시간·회복·주의) 1시술 1페이지 | 유튜브 시술 설명 영상 전사 |
|
||||
| E 신뢰·안전 | 9 | ✕ | 마취과 상주·CCTV·응급 체계 등 안전 설명 | 홈페이지 시설 + 강남언니 병원정보 |
|
||||
| F 가격·이벤트 | 8 | ✕ | 가격 구성 요소 설명(범위 표기는 병원 승인 후) | 홈페이지 이벤트 페이지 |
|
||||
| G 후기·평판 | 8 | △ | 강남언니·GBP 집계 수치 요약 + 링크. 경험담 인용 없음(§7) | 강남언니 hospitals/189, GBP |
|
||||
|
||||
### 5.2 세 가지 원천의 변환 파이프라인
|
||||
|
||||
```
|
||||
[유튜브 1,064편] → 자막/전사 → 질문 단위로 분할 → Q&A 글 초안 → 영상 타임스탬프 임베드
|
||||
[홈페이지·인스타 캡처] → 사용 허락 목록 → 캡처 → 새 alt·캡션 → 글의 삽화로 배치
|
||||
[홈페이지 사실] → 팩트 시트(JSON 1개) → 모든 글의 병원 정보 블록에 주입
|
||||
```
|
||||
|
||||
QB 120문항 하나가 글 하나의 H1이 됩니다. D 카테고리 58문항은 시술별로 묶어 1시술 1페이지 안의 FAQ 항목으로 배치합니다.
|
||||
|
||||
### 5.3 물량과 순서
|
||||
|
||||
| 주차 | 발행 | 내용 |
|
||||
|---|---|---|
|
||||
| W1~2 | 사이트 개설 + 팩트 시트 + 정의문 3편 | A 카테고리. 사이트 자체 소개 페이지(서포터즈 운영 주체·감수 체계 명시) |
|
||||
| W3~4 | 시술 페이지 6편 | D 카테고리 중 논브랜드 실측에서 뷰가 0회였던 시술 우선 |
|
||||
| W5~6 | 방문 가이드 3편 + 안전 3편 + 비교 가이드 3편 | B·E·C |
|
||||
| W7~8 | 평판 요약 1편 + 보강 | G. 재측정 |
|
||||
|
||||
8주간 약 20편입니다. 주 2~3편이면 서포터즈 2~3명과 원장 감수 주 1회로 운영 가능합니다.
|
||||
|
||||
---
|
||||
|
||||
## 6. 페이지 템플릿 (AEO 구조)
|
||||
|
||||
모든 글이 같은 뼈대를 가집니다. 진단 C 카테고리(28%)에서 지적된 항목을 그대로 뒤집은 구조입니다.
|
||||
|
||||
```
|
||||
H1: 질문형 한 문장 (예: "뷰성형외과 가슴성형은 어떤 방식으로 진행되나요?")
|
||||
[직접 답변 블록] 40~60단어. 첫 단락에서 질문에 바로 답함
|
||||
[정의문] "뷰성형외과는 2005년 개원한 신논현역 인근 성형외과로 ..." (팩트 시트 주입)
|
||||
[본문] H2 3~5개. 표는 HTML table. 수치는 단위 포함
|
||||
[영상] 원본 유튜브 임베드 + 해당 발화 타임스탬프
|
||||
[FAQ] 5~8개. QB 문항 그대로 질문으로 사용
|
||||
[감수 표기] "이 글은 서포터즈 OOO이 작성하고 최순우 원장이 의학적 내용을 감수했습니다. 감수일 2026-xx-xx"
|
||||
[출처] 참고한 영상·페이지 링크
|
||||
[병원 정보 블록] NAP·진료시간. 팩트 시트에서 자동 생성
|
||||
[대가 관계 고지] "서포터즈 활동은 뷰성형외과의 지원을 받습니다" (§7)
|
||||
```
|
||||
|
||||
**스키마(JSON-LD):**
|
||||
|
||||
| 타입 | 용도 | 진단 항목 대응 |
|
||||
|---|---|---|
|
||||
| `MedicalWebPage` + `Article` | 글 본체. `dateModified` 필수 | C4, P2 신선도 |
|
||||
| `FAQPage` | FAQ 블록 | C2 FAQ 0개 |
|
||||
| `Person` (author) | 서포터즈 실명. `sameAs`로 본인 SNS 연결 | E-E-A-T |
|
||||
| `Physician` (reviewedBy) | 최순우 원장. 강남언니 프로필 `sameAs` | B3 |
|
||||
| `MedicalClinic` (about) | 뷰성형외과. 홈페이지·GBP·강남언니·유튜브 `sameAs` | B2 스키마 없음 |
|
||||
| `VideoObject` | 임베드 영상 | 유튜브 자산 연결 |
|
||||
|
||||
주의: 홈페이지의 ItemList JSON-LD가 문법 오류로 무효였습니다. 서포터즈는 빌드 단계에서 스키마 유효성 검사를 통과하지 못하면 배포가 실패하도록 합니다.
|
||||
|
||||
---
|
||||
|
||||
## 7. 법무·컴플라이언스 게이트 (발행 전 필수 확인)
|
||||
|
||||
이 섹션은 법률 자문이 아니라 **법무 확인이 필요한 항목의 목록**입니다. 착수 전 병원 측 법무 또는 자문 변호사 확인을 권합니다.
|
||||
|
||||
| 항목 | 근거 | 설계 반영 |
|
||||
|---|---|---|
|
||||
| 치료경험담 광고 금지 | 의료법 제56조 제2항 | 환자 후기는 **인용하지 않습니다.** 강남언니 후기 수·평점, GBP 평점 같은 집계 수치와 링크만 표기 |
|
||||
| 비교·비방 광고 금지 | 의료법 제56조 제2항 | C 카테고리 비교표는 "시술 방식 간 비교"로 한정. 타 병원명 비교 금지 |
|
||||
| 치료 효과 보장·과장 금지 | 의료법 제56조 제2항 | 직접 답변 블록에 "개인차" 문구 표준화. 원장 감수 게이트 |
|
||||
| 전후사진 | 의료법 시행령 요건 | 이번 범위에서 **제외** |
|
||||
| 사전심의 대상 여부 | 의료법 제57조. 매체 이용자 규모 기준 | 신규 사이트는 초기 기준 미달 가능성이 높으나 확인 필요. 네이버 블로그 트랙은 별도 확인 |
|
||||
| 경제적 대가 관계 표시 | 공정위 추천·보증 심사지침 | 서포터즈 글 하단에 지원 관계 고지 문구 고정 |
|
||||
| 저작권 | 병원 콘텐츠 사용 | 유튜브·홈페이지·인스타 자산 사용 허락서 1장(범위·기간·매체 명시) |
|
||||
| 초상권 | 의료진·직원 사진 | 캡처 대상 사진의 인물 동의 확인 목록 |
|
||||
| 서포터즈 개인정보 | 실명·사진 공개 | 서포터즈 동의서 |
|
||||
|
||||
---
|
||||
|
||||
## 8. 네이버 트랙 (동등 목표 반영)
|
||||
|
||||
네이버 블로그·플레이스·카페는 robots.txt로 AI 크롤러를 차단합니다. 그래서 네이버용 자산은 따로 두되, 허브의 파생물로만 운영합니다.
|
||||
|
||||
| 단계 | 내용 |
|
||||
|---|---|
|
||||
| 원본 확정 | 허브에 먼저 발행. 서치어드바이저에 허브 도메인 등록·사이트맵 제출 |
|
||||
| 재작성 | 24시간 후 네이버 블로그에 요약본(허브 분량의 60% 이하, 다른 문장) + 허브 링크 |
|
||||
| 유사문서 회피 | 이미지도 크롭·순서를 바꾸고, 표는 네이버 에디터 표로 다시 작성 |
|
||||
| 플레이스 연동 | 네이버 플레이스 소식에 허브 글 링크 게시(병원 계정 필요) |
|
||||
| 측정 | 서치어드바이저 노출·클릭, 블로그 유입 경로 |
|
||||
|
||||
네이버 블로그를 병원 명의로 할지 서포터즈 명의로 할지는 §7 사전심의 확인 결과에 따라 정합니다.
|
||||
|
||||
---
|
||||
|
||||
## 9. 기술 아키텍처
|
||||
|
||||
기존 INFINITH 인프라(Supabase·Vercel)를 그대로 씁니다.
|
||||
|
||||
```
|
||||
[콘텐츠 DB: Supabase]
|
||||
supporter_posts (초안/검수/발행 상태, QB 문항 ID, 원천 영상 ID)
|
||||
fact_sheet (NAP·의료진·진료시간 단일 원본)
|
||||
media_assets (캡처 이미지, 출처, 허락 여부, alt)
|
||||
│
|
||||
[생성 파이프라인: Edge Function]
|
||||
1. 유튜브 자막 수집 (YouTube Data API captions / 없으면 Whisper)
|
||||
2. QB 문항 매칭 → Q&A 초안 생성 (기존 Perplexity/Claude 프롬프트 체계 재사용)
|
||||
3. 중복 검사 게이트: 홈페이지 원문 대비 연속 일치 문자열, 금칙어(효과 보장·경험담 표현)
|
||||
4. 서포터즈 편집 → 원장 감수(상태 전이) → 발행
|
||||
│
|
||||
[사이트: Astro 정적 빌드 → Vercel]
|
||||
robots.txt 전면 허용(GPTBot·OAI-SearchBot·PerplexityBot·ClaudeBot·Google-Extended 명시)
|
||||
sitemap.xml 자동, JSON-LD 빌드 시 검증, dateModified 자동
|
||||
│
|
||||
[계측]
|
||||
Vercel 로그 드레인 → Supabase crawler_hits (UA + 공개 IP 대역 검증)
|
||||
GA4 AI 채널그룹 + GSC + Bing 웹마스터 + 네이버 서치어드바이저
|
||||
QB 120 재측정 러너(기존 스크립트)로 4주·8주 스팟체크
|
||||
```
|
||||
|
||||
정적 빌드를 고르는 이유는 세 가지입니다. HTML 무게가 가벼워 크롤러가 전문을 읽고, WordPress 같은 취약 버전 문제가 없고, 스키마 검증을 빌드 단계에 걸 수 있습니다.
|
||||
|
||||
---
|
||||
|
||||
## 10. 리스크와 대응
|
||||
|
||||
| 리스크 | 대응 |
|
||||
|---|---|
|
||||
| 병원이 시술 정보 문장을 홈페이지와 똑같이 쓰길 원함 | 4.2 규칙 1을 계약서에 명시. 팩트만 공유하고 문장은 서포터즈가 씀 |
|
||||
| 원장 감수 병목 | 주 1회 감수 슬롯 고정. 감수 없이 발행 불가하되 사실 확인형 글(방문 가이드)은 실무자 감수로 대체 |
|
||||
| 8주 안에 언급률이 안 움직임 | 색인·크롤러 방문·인용 URL 등 선행 지표를 먼저 보고. 언급률은 신뢰구간으로 보고 |
|
||||
| 후기 사용 요구 | §7 규칙 고정. 집계 수치와 링크로만 |
|
||||
| 네이버 유사문서로 허브가 밀림 | 발행 순서 규칙(허브 우선)과 재작성 분량 상한 |
|
||||
|
||||
---
|
||||
|
||||
## 11. haewon 결정 필요 항목
|
||||
|
||||
1. 사이트 주소 2안 중 선택 (§3)
|
||||
2. 서포터즈 인원과 모집 방식(병원 내부 직원인지, 외부 모집인지)
|
||||
3. 법무 확인 주체(병원 자문 변호사인지, INFINITH가 별도 자문을 붙일지)
|
||||
4. 이 사이트를 POC 8주 범위 안(W3~6 콘텐츠 구조 항목)에 넣을지, 별도 계약으로 뗄지
|
||||
5. 가격 정보(F 카테고리) 노출 범위: 구성 요소 설명만 vs 범위 금액 표기
|
||||
|
||||
## 12. 다음 산출물
|
||||
|
||||
- 이 문서 승인 후: 도메인 2안 비교와 콘텐츠 계획을 POC 덱 부록 또는 별도 제안 덱(infinith-deck 규격)으로 제작
|
||||
- 기술 착수 시: `fact_sheet` 스키마, 페이지 템플릿 1개, 중복 검사 게이트 스크립트를 먼저 만들어 정의문 1편으로 파일럿
|
||||
159
docs/NEXT_SESSION_SUPPORTERS_AUTOBUILD.md
Normal file
@ -0,0 +1,159 @@
|
||||
# 다음 세션 프롬프트 · "병원 URL 하나로 서포터즈 사이트까지 자동 빌드"
|
||||
|
||||
작성 2026-09-04 · 작성자 Claude (haewon 세션 핸드오버) · 상태: 착수 전
|
||||
|
||||
---
|
||||
|
||||
## 0. 세션 시작 시 그대로 붙여 넣을 프롬프트
|
||||
|
||||
```
|
||||
INFINITH 저장소(브랜치 feature/discovery-url-cta)에서 "AI Discovery 자동 빌드" 기능을 개발한다.
|
||||
목표: /discovery 랜딩의 URL 입력 CTA에 병원 홈페이지 URL을 넣으면, 진단 리포트(/discovery/:id)까지 만들던 기존 파이프라인을 확장해
|
||||
서포터즈 사이트(supporters/ Astro 프로젝트와 같은 구조)까지 자동 생성·미리보기 배포하는 것이다.
|
||||
먼저 docs/NEXT_SESSION_SUPPORTERS_AUTOBUILD.md 를 읽고, 그 문서의 §5 단계 순서대로 진행한다.
|
||||
핵심 제약: 뷰성형외과 수작업 빌드에서 확정된 원칙(전후사진·후기 인용 금지, 홈페이지 문장 복사 금지, 감수 예정 표기, noindex 기본, 발행 게이트 통과 필수)을 코드로 강제한다.
|
||||
결정이 필요한 것은 문서 §7을 보고 haewon에게 묻는다. 존댓말, 엠대시 금지.
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 1. 지금까지 만든 것 (위치)
|
||||
|
||||
| 자산 | 위치 | 상태 |
|
||||
|---|---|---|
|
||||
| AI Discovery 랜딩 (뷰성형외과 제안용) | `src/pages/DiscoveryLandingPage.tsx` + `src/components/discovery/` 9종 (Hero·WhyNow·AbmrLoop·HowItWorks·SampleReport·HonestyStrip·Roadmap·DiscoveryCta·DiscoveryLeadModal) | 프로덕션 https://infinith-demo.vercel.app/discovery (main). 단일 HTML 시안 `docs/reports/ai_discovery_landing.html` |
|
||||
| URL 입력 CTA | `DiscoveryLeadModal.tsx` → Supabase `discovery_leads` insert (URL·연락처 필수, 상호 선택). 실패 시 o2oteam@o2o.kr mailto 폴백 | 브랜치 `feature/discovery-url-cta`, 프로덕션 미배포. 마이그레이션 `supabase/migrations/20260831_discovery_leads.sql` **미적용** (supabase login 후 db push 필요) |
|
||||
| 진단 리포트 페이지 | `src/pages/DiscoveryReportPage.tsx`, 결과 레지스트리 `src/data/discoveryResults.ts`, 뷰성형외과 결과 `src/data/discovery_viewclinic.ts` (50/C) | 새 병원 = 결과 파일 + 레지스트리 1줄 |
|
||||
| 기준표 | `src/data/aeoGeoRubric.ts` (36항목, A20/B20/C25/D20/E10/F5), 문서 `docs/AEO_GEO_RUBRIC.md`, 채점 `src/lib/discoveryScore.ts` | v1.0. 미검증은 분모 제외 |
|
||||
| 자동 채점기 | `scripts/audit_aeo_geo.py <main_url> <service_url> --id --name --skip-ts --json` (auto 21~22항목) | stdlib+curl. 노드 순회가 역순(stack.pop)이라 sameAs는 Organization 타입 노드가 먼저 잡힘 → 서포터즈 스키마는 `@type: ['MedicalClinic','Organization']` |
|
||||
| 기존 백엔드 파이프라인 | `supabase/functions/` discover-channels → collect-channel-data → generate-report → generate-content-plan, scrape-website, enrich-channels, `_shared/` (extractSocialLinks·verifyHandles·dataQuality·retry) | 실제 연동: YouTube Data API, Apify, Naver Search API, Firecrawl, Perplexity. 문서 `docs/AI_DISCOVERY_BUILD2_HANDOVER.md`, `docs/PIPELINE_IMPROVEMENT_PLAN.md` |
|
||||
| 질문 뱅크 | `docs/reports/Viewclinic_AI_Discovery_Question_Bank.md` (120문항, 7카테고리), 러너 `scripts/run_question_bank_openai.py`, 통합 `scripts/merge_qb_results.py`, 엑셀 `scripts/export_qb_excel.py` | 뷰성형외과 2엔진 실측 완료 |
|
||||
| **서포터즈 샘플 사이트 (수작업 v3)** | `supporters/` (Astro 7 정적, 별도 Vercel 프로젝트 `view-supporters-sample`, https://view-supporters-sample.vercel.app, noindex) | 이 세션의 산출물. 자동화의 정답지(golden sample) |
|
||||
| 감사 비교 엑셀 | `docs/reports/Viewclinic_Supporters_vs_Official_AEO_Audit.xlsx`, 빌더 `scripts/build_supporters_audit_xlsx.py` | 공식 50/C · 샘플 78/B · 색인 허용 86/A. 시트 4 "E 측정 인프라 연결 가이드" |
|
||||
| 설계 문서 | `docs/INFINITH_Viewclinic_Supporters_Site_Design_v0.1.md` | 중복 회피 6규칙, 법무 게이트, 네이버 2트랙 |
|
||||
| 기업은행 블로그 감사 | `docs/reports/IBK_Blog_AEO_GEO_Audit_2026-09-04.md` | 54/C, Perplexity 3/3 인용. 글 구조(세 줄 요약·질문형 H3·표·FAQ)의 근거 |
|
||||
|
||||
### supporters/ 구조 (자동 생성기가 만들어야 할 결과물)
|
||||
```
|
||||
supporters/
|
||||
astro.config.mjs site = SITE_URL env, build.format 'file', trailingSlash never
|
||||
vercel.json cleanUrls, X-Robots-Tag noindex, build env PUBLIC_INDEXABLE=false
|
||||
scripts/check.mjs 발행 게이트: JSON-LD 파싱·Article/FAQPage 존재·img alt·H1=1·금칙어·홈페이지 문장 40자 연속 일치
|
||||
scripts/home_text.txt 홈페이지 본문(공백 제거) — 중복 검사 기준
|
||||
scripts/build_news.py 뉴스룸 데이터 빌더 (게시판 크롤 + 네이버 뉴스 API → src/data/news.json, 부정 기사 제외 키워드)
|
||||
src/data/factSheet.json 병원 사실 단일 원본 (NAP·진료시간·층·진료과·안전·이력·표면 집계)
|
||||
src/data/authors.json 서포터즈(가명·샘플) + 감수 원장(실제 약력·사진·sameAs)
|
||||
src/data/videos.json 채널 전체 조회수 집계 → 롱폼 Top10(정보형, 쇼츠 판별은 /shorts/{id} 200 응답)·정보형 쇼츠+서포터즈 답·전체
|
||||
src/data/news.json 1,137건 (보도자료/기고·인터뷰/언급, 주제 13종)
|
||||
src/content/posts/*.md 글 8편. frontmatter: title(질문형)·category·qbIds·summary[3]·author·reviewer·reviewStatus·videos[]·gallery[]·faq[]·sources[]·thumbnail
|
||||
src/lib.ts clinicSchema/personSchema/physicianSchema, INDEXABLE 플래그
|
||||
src/layouts/Base.astro 헤더(병원 로고+서포터즈)·이전 페이지 버튼(홈 제외)·플로팅 위로·푸터·JSON-LD·canonical(index.html 제거)
|
||||
src/components/ Summary(세 줄 요약, 연하늘 #F2F6FF 단색)·VideoLite(썸네일+클릭 iframe)·Gallery·RelatedVideos·Faq·ReviewBox·Sources·Disclosure·FactBlock·PostCard
|
||||
src/pages/ index(라이트 파스텔 히어로·다크 글래스 KPI·Doctors Explain·시설 갤러리)·posts/[id]·posts/index·videos·newsroom·clinic·about·authors/[id]·robots.txt.ts·sitemap.xml.ts
|
||||
public/img/{clinic,doctors,safety,exam,breast,care}/ 홈페이지에서 내려받은 92장 + hero-collage.jpg + view-logo.png
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. 이 세션에서 확정된 결정 (자동화가 지켜야 할 규칙)
|
||||
|
||||
1. 디자인은 INFINITH 토큰(`infinith-frontend-design` 스킬): 네이비 #0A1128, 퍼플 #6C5CE7, 그라디언트 #4F1DA1→#021341, Playfair(영문 제목·숫자)+Pretendard, 다크/라이트 리듬, 다크 위 흰 카드, 대각선 그림자, 파스텔 상태색. **텍스트 많은 블록은 그라디언트 금지, 연하늘 단색.**
|
||||
2. 글 구조 = 기업은행 블로그 검증 구조: 세 줄 요약 → 본문(질문형 H2, HTML 표) → 원본 영상 → 갤러리 → 이어서 볼 영상 → FAQ → 작성·감수 박스 → 참고 자료 → 병원 정보 블록 → 대가 고지.
|
||||
3. 넣지 않는 것: 전후 사진, 환자 후기·경험담 인용(**공정위 2026-07-12 뒷광고 제재 병원**), 타 병원 비교, 효과 보장, 가격(경영진 결정 전), 후기·리뷰·토크·변신 성격 영상, 비교 광고 소지 이미지(마취과 상주 43% vs 23.1%).
|
||||
4. 리뷰 페이지는 만들지 않는다(haewon 결정 2026-09-04). Google Places 키 불필요.
|
||||
5. 영상: 롱폼 Top10 기본 탭, 정보형만. 쇼츠는 정보형만 + 서포터즈 한 문단 답. 채널 전체 순위는 참고 탭.
|
||||
6. 뉴스룸: 병원 배포 기사 링크 트리. 제3자 규제·사건 보도 제외(키워드 목록은 build_news.py NEG). 본문 인용 없음.
|
||||
7. 서포터즈 실명·원장 감수는 확정 전 → 가명·"감수 예정" 표기, reviewedBy 스키마는 감수 완료 글에만.
|
||||
8. 기본 noindex + robots Disallow. 발행은 `PUBLIC_INDEXABLE=true` 환경변수 하나로 전환(robots 전면 허용 + AI 봇 명시 + sitemap 선언).
|
||||
9. 병원 자산 사용은 파트너십 전제(허락 받은 것으로 간주). 단 이미지는 재문맥화(새 alt·캡션·출처·캡처일).
|
||||
10. 경영진 미결: 도메인(별도 vs 서브도메인), 서포터즈 모집, 법무 확인 주체, POC 8주 범위 포함 여부, 가격 노출.
|
||||
|
||||
---
|
||||
|
||||
## 3. 목표 기능의 파이프라인 (제안)
|
||||
|
||||
```
|
||||
[입력] 병원 홈페이지 URL (+ 상호 선택) ← /discovery 랜딩 CTA (DiscoveryLeadModal)
|
||||
│
|
||||
Phase 0 discover-channels (기존) → 유튜브·인스타·페북·블로그·강남언니·GBP 핸들
|
||||
Phase 1 audit_aeo_geo (기존 py → Edge Function 이식 또는 워커) → discovery_<id> 결과 (50/C 등)
|
||||
Phase 2 자산 수집 (신규 collect-supporter-assets)
|
||||
├ 홈페이지: Firecrawl 페이지 텍스트(병원소개·의료진·오시는길·안전·시술 메인) + 이미지 URL 목록(필터: logo/icon/btn/popup/전후/before-after 제외) + 로고
|
||||
├ 유튜브: uploads 전량 videos.list(statistics·contentDetails·status) → 조회수 순위, 쇼츠 판별(/shorts/{id} 200), 자막(youtube_transcript_api, IP 차단 대비 프록시/캐시/큐)
|
||||
├ 강남언니: 병원 페이지 공개 집계(평점·후기수·의료진수·진료시간)
|
||||
├ 뉴스: 홈페이지 언론보도 게시판(있으면) + 네이버 뉴스 API 6질의 → 분류·부정 제외
|
||||
└ 의료진: 홈페이지 의료진 페이지 → 이름·직함·약력·사진 → authors.json physicians
|
||||
Phase 3 팩트 시트 생성 (factSheet.json) ← 사실 추출 + 표기 단일화(주소 1형태, 전화, 진료시간 출처 표기)
|
||||
Phase 4 질문 선정 ← QB 템플릿(카테고리 7종)을 업종·시술로 인스턴스화 → 첫 배치 8~10문항 (A1·B1·D4·E1·G1 비율)
|
||||
Phase 5 글 생성 (Claude) ← 문항 + 관련 영상 자막 + 홈페이지 사실 → frontmatter 규격 md
|
||||
프롬프트 규칙: 세 줄 요약, 질문형 H2, 표 1개 이상, FAQ 5~8, 출처 accessed 날짜, 금칙어, 개인차 문장, 홈페이지 문장 복사 금지
|
||||
Phase 6 사이트 조립 ← supporters/ 템플릿 복제 + 데이터 주입 + 이미지 다운로드·리사이즈 + 로고
|
||||
Phase 7 발행 게이트 (check.mjs) 통과 못 하면 중단 → 실패 항목을 리포트에 표시
|
||||
Phase 8 미리보기 배포 ← Vercel 프로젝트 `supporters-<clinicId>` 생성·배포(noindex) → URL을 discovery 리포트와 discovery_leads 행에 기록
|
||||
Phase 9 감사 재실행 (색인 허용 빌드 로컬) → 공식 vs 서포터즈 비교표 (build_supporters_audit_xlsx.py 로직 재사용) → 리포트 페이지에 표시
|
||||
[출력] /discovery/<id> 에 "서포터즈 미리보기" 카드 + 비교 점수 + 남은 과제
|
||||
```
|
||||
|
||||
권장 실행 형태: 오래 걸리는 Phase 2·5·8은 Edge Function 시간 제한(150초)을 넘기므로, **Supabase 큐(테이블 + cron) + 별도 워커(Node, Vercel 서버리스 아님)** 또는 GitHub Actions 워커로 돌리고 진행 상태를 `supporter_builds` 테이블에 기록해 랜딩이 폴링한다.
|
||||
|
||||
---
|
||||
|
||||
## 4. 재사용할 코드 (이식 대상)
|
||||
|
||||
| 기능 | 지금 있는 곳 | 이식 방향 |
|
||||
|---|---|---|
|
||||
| 채널 발견·검증 | `supabase/functions/discover-channels`, `_shared/extractSocialLinks.ts`, `verifyHandles.ts` | 그대로 사용 |
|
||||
| 자동 채점 | `scripts/audit_aeo_geo.py` | Node/TS 이식(`supabase/functions/discover-aeo-geo`) 또는 워커에서 python 호출 |
|
||||
| 유튜브 통계·쇼츠 판별 | 이 세션 스크래치 스니펫 (uploads.json → videos.list 50개 배치 → /shorts/{id} 200 판별) | 워커 모듈 `collect-youtube-ranking.ts` |
|
||||
| 뉴스 수집·분류 | `supporters/scripts/build_news.py` (OUTLET 맵, NEG, TOPICS, REL/Q1/Q2 정규식, 게시판 파서) | 워커 모듈 |
|
||||
| 이미지 필터·리사이즈·컨택트시트 | 이 세션 스니펫 (PIL, 1600px, 제외 정규식 `icon|logo|btn|popup|floating|banner|before|after`) | 워커 모듈 |
|
||||
| 발행 게이트 | `supporters/scripts/check.mjs` | 그대로. home_text.txt는 Phase 2 텍스트로 생성 |
|
||||
| 비교 엑셀 | `scripts/build_supporters_audit_xlsx.py` | 점수 비교 로직만 TS로 옮겨 리포트 카드에 표시 |
|
||||
| 글 템플릿·스키마 | `supporters/src/pages/posts/[id].astro`, `src/lib.ts` | 템플릿 저장소로 분리(`templates/supporters/`) |
|
||||
|
||||
---
|
||||
|
||||
## 5. 작업 순서 (권장)
|
||||
|
||||
1. **템플릿화**: `supporters/`에서 뷰성형외과 고유값을 전부 `factSheet.json`·`authors.json`·`videos.json`·`news.json`·`posts/`·`public/img/`로 몰아넣고, 나머지를 `templates/supporters-astro/`로 복제. 빈 데이터로도 빌드되게 empty-state 처리.
|
||||
2. **워커 뼈대**: `workers/supporters-build/` (Node 22). 입력 `{clinicId, url}` → 단계별 함수, 각 단계 결과를 `supporter_builds` 테이블(단계·상태·로그·산출 경로)에 기록.
|
||||
3. **Phase 2 수집기** 3종(홈페이지·유튜브·뉴스)을 뷰성형외과로 회귀 테스트: 결과가 수작업 v3와 같은 팩트 시트·Top10·뉴스 건수(±5%)가 나오면 통과.
|
||||
4. **Phase 5 글 생성 프롬프트**: 뷰성형외과 글 8편을 정답지로 두고, 같은 문항·자막으로 생성한 결과가 게이트를 통과하고 세 줄 요약·표·FAQ 규격을 지키는지 확인. 금칙어·경험담 표현은 게이트에서 막는다.
|
||||
5. **Phase 8 배포기**: Vercel API로 프로젝트 생성·환경변수·배포. 도메인은 `supporters-<id>.vercel.app`.
|
||||
6. **랜딩 연결**: DiscoveryLeadModal 제출 → `supporter_builds` 행 생성 → 진행 상태 UI(단계 9개) → 완료 시 리포트 페이지에 미리보기 카드. `discovery_leads` 마이그레이션 먼저 적용.
|
||||
7. **두 번째 병원으로 검증**: `data/plastic surgery clinics url.json`에서 한 곳 골라 끝까지 돌린다. 수작업 개입 없이 게이트 통과가 목표.
|
||||
|
||||
---
|
||||
|
||||
## 6. 이번 세션에서 밟은 함정 (반드시 회피)
|
||||
|
||||
- youtube_transcript_api는 25편 후 `IpBlocked`. 자막은 큐 + 지연 + 프록시 또는 YouTube Data API captions(OAuth 필요) 대안 설계. 자막 없는 영상은 제목·설명만으로 글을 쓰지 말고 "근거 부족"으로 건너뛴다.
|
||||
- Apify 계정 미납(`Too many outstanding invoices`) → 인스타 수집 불가. 인스타는 선택 단계로.
|
||||
- YouTube search API `order=viewCount`는 3건만 반환 → uploads 전량 + videos.list로 집계.
|
||||
- 쇼츠는 3분까지 가능 → 길이 60초 기준만으로 판별 금지, `/shorts/{id}` 200 응답으로 판별.
|
||||
- Firecrawl 결과에 팝업·상담폼 텍스트가 섞임 → NOISE 필터 필요. 홈페이지 게시판 썸네일 alt가 비어 있으면 문서 페이지에서 제목 재수집.
|
||||
- 채점기 canonical 검사: Astro `Astro.url.pathname`이 `/index.html`로 잡힘 → 정규화 필수. 주소는 지번 병기 시 2종으로 잡힘 → 팩트 블록은 도로명 1형태만.
|
||||
- zsh에서 `[id].astro` 경로는 따옴표 필수(glob).
|
||||
- Vercel `build.format: 'file'`은 `cleanUrls: true` 없으면 /posts/x 404.
|
||||
- `npm run build | grep`은 파이프라인 때문에 게이트 실패를 삼킨다 → `if npm run build > log; then deploy; fi`.
|
||||
- Google Places 키 없음. 필요 없음(리뷰 페이지 폐기).
|
||||
- `.env`의 GOOGLE_PLACES_API_KEY는 비어 있음. YOUTUBE·NAVER·FIRECRAWL·PERPLEXITY·OPENAI 키는 있음.
|
||||
|
||||
---
|
||||
|
||||
## 7. haewon에게 물어볼 것 (착수 전)
|
||||
|
||||
1. 워커 실행 위치: Supabase 큐 + Node 워커(서버 필요) vs GitHub Actions vs Vercel 백그라운드 함수 중 어디로 할지.
|
||||
2. 첫 배치 글 수(8편 유지?)와 생성 모델(Claude API, 회사 키 유무).
|
||||
3. 자동 생성 글의 감수 워크플로: 병원에 초안 링크를 보내는 이메일/노션 연동을 넣을지.
|
||||
4. 두 번째 검증 병원 선정(`data/plastic surgery clinics url.json`).
|
||||
5. 미리보기 배포를 Vercel 프로젝트 자동 생성으로 할지, 하나의 프로젝트에 `/<id>/` 경로로 묶을지.
|
||||
|
||||
---
|
||||
|
||||
## 8. 완료 기준
|
||||
|
||||
- 랜딩에서 URL 제출 → 사람 개입 없이 9단계 완주 → 미리보기 URL이 리포트에 표시된다.
|
||||
- 뷰성형외과 URL로 돌렸을 때 팩트 시트·Top10·뉴스룸이 수작업 v3와 일치(±5%)하고, 게이트 0오류, 색인 허용 빌드 감사 80점 이상.
|
||||
- 두 번째 병원에서도 게이트 0오류로 완주.
|
||||
- 금칙 사항(전후·후기 인용·비교·효과 보장)이 게이트에서 실제로 차단되는 테스트 케이스가 있다.
|
||||
BIN
docs/reports/Viewclinic_Supporters_vs_Official_AEO_Audit.xlsx
Normal file
187
scripts/build_supporters_audit_xlsx.py
Normal file
@ -0,0 +1,187 @@
|
||||
#!/usr/bin/env python3
|
||||
"""공식 홈페이지 vs 서포터즈 사이트 AEO/GEO 비교 엑셀.
|
||||
사용: python3 scripts/build_supporters_audit_xlsx.py <scratch/audit dir> <out.xlsx>
|
||||
입력: official.json(공식 결과+항목명), supporters_prod.json(샘플 noindex 실측), supporters_idx.json(색인 허용 빌드 실측), overrides.json(수동 판정)"""
|
||||
import json,sys,os
|
||||
from openpyxl import Workbook
|
||||
from openpyxl.styles import Font,PatternFill,Alignment,Border,Side
|
||||
from openpyxl.utils import get_column_letter
|
||||
A=sys.argv[1]; OUT=sys.argv[2]
|
||||
off=json.load(open(f"{A}/official.json")); names=off["names"]
|
||||
CATS={"A":("AI 크롤러 접근성",20),"B":("엔티티 식별·구조화",20),"C":("답변 인용 가능 콘텐츠",25),"D":("AI 인용 표면 확보",20),"E":("AI 가시성 측정 인프라",10),"F":("기술 위생",5)}
|
||||
def load(fn):
|
||||
d=json.load(open(f"{A}/{fn}")); res=d["results"] if isinstance(d,dict) and "results" in d else d
|
||||
return {r["criterionId"]:r for r in res}
|
||||
prod=load("supporters_prod.json"); idx=load("supporters_idx.json")
|
||||
ov=json.load(open(f"{A}/overrides.json")) if os.path.exists(f"{A}/overrides.json") else {}
|
||||
official={r["id"]:r for r in off["official"]}
|
||||
def lvl(r): return r["level"] if r else "unverified"
|
||||
# 색인 허용 열 = 로컬 색인 빌드 실측 + 로컬에서 판정 불가한 항목(A4·B6·A7)은 프로덕션 값 + 수동 판정 override
|
||||
final={}
|
||||
for cid in names:
|
||||
r=idx.get(cid); src="로컬 색인 허용 빌드 실측"
|
||||
if cid in ("A4","B6","A7"): r=prod.get(cid); src="프로덕션 실측 (로컬에서 판정 불가)"
|
||||
ev=r["evidence"] if r else ""; L=lvl(r)
|
||||
if cid in ov: L=ov[cid]["level"]; ev=ov[cid]["evidence"]; src="수동 판정 (서포터즈 실측)"
|
||||
final[cid]={"level":L,"evidence":ev,"src":src}
|
||||
W={c:names[c][1] for c in names}
|
||||
def score(levels):
|
||||
cat={}
|
||||
for c in CATS:
|
||||
e=p=n=0
|
||||
for cid in names:
|
||||
if cid[0]!=c: continue
|
||||
L=levels.get(cid,"unverified")
|
||||
if L=="unverified": continue
|
||||
n+=1; p+=W[cid]; e+=W[cid]*L/3
|
||||
cat[c]=(round(e,1),p,(round(e/p*100) if p else 0),n)
|
||||
E=sum(v[0] for v in cat.values()); P=sum(v[1] for v in cat.values()); N=sum(v[3] for v in cat.values())
|
||||
s=round(E/P*100) if P else 0; g="A" if s>=81 else "B" if s>=61 else "C" if s>=41 else "D"
|
||||
return cat,round(E,1),P,s,g,N
|
||||
lv_off={c:official[c]["level"] for c in names}; lv_prod={c:lvl(prod.get(c)) for c in names}; lv_fin={c:final[c]["level"] for c in names}
|
||||
for c in ov:
|
||||
if lv_prod.get(c)=="unverified": lv_prod[c]=ov[c]["level"]
|
||||
S_off=score(lv_off); S_prod=score(lv_prod); S_fin=score(lv_fin)
|
||||
|
||||
NAVY="0A1128"; VIOLET="4F1DA1"; LIGHT="F4F6FB"; GOOD="F3F0FF"; WARN="FFF6ED"; CRIT="FFF0F0"
|
||||
hf=Font(name="Pretendard",bold=True,color="FFFFFF",size=14); bf=Font(name="Pretendard",size=14); bb=Font(name="Pretendard",size=14,bold=True)
|
||||
fill=lambda h:PatternFill("solid",fgColor=h); thin=Side(style="thin",color="E2E8F0"); bd=Border(left=thin,right=thin,top=thin,bottom=thin)
|
||||
wrap=Alignment(wrap_text=True,vertical="top"); center=Alignment(horizontal="center",vertical="center",wrap_text=True)
|
||||
wb=Workbook()
|
||||
# ── 1. 요약
|
||||
ws=wb.active; ws.title="요약"
|
||||
ws["A1"]="뷰성형외과 공식 홈페이지 vs 뷰 서포터즈 사이트 · AEO/GEO 기준표 v1.0 비교"; ws["A1"].font=Font(name="Pretendard",bold=True,size=18,color=NAVY)
|
||||
ws["A2"]="실측일 2026-09-04 · 도구 scripts/audit_aeo_geo.py(자동 21항목) + 수동 판정 · 미검증 항목은 0점이 아니라 분모 제외 · 등급 D≤40 / C 41~60 / B 61~80 / A 81+"; ws["A2"].font=Font(name="Pretendard",size=14,color="64748B")
|
||||
hdr=["구분","공식 홈페이지 (viewclinic.com)","서포터즈 샘플 (현재·noindex)","서포터즈 색인 허용 빌드","비고"]
|
||||
for i,h in enumerate(hdr,1):
|
||||
c=ws.cell(row=4,column=i,value=h); c.font=hf; c.fill=fill(NAVY); c.alignment=center; c.border=bd
|
||||
rows=[("총점 (100점 환산)",S_off[3],S_prod[3],S_fin[3],"검증 항목 가중 합 ÷ 검증 항목 가중치 합"),
|
||||
("등급",S_off[4],S_prod[4],S_fin[4],""),
|
||||
("획득 / 가능 가중치",f"{S_off[1]} / {S_off[2]}",f"{S_prod[1]} / {S_prod[2]}",f"{S_fin[1]} / {S_fin[2]}",""),
|
||||
("검증 항목 수 (36 중)",S_off[5],S_prod[5],S_fin[5],"D·E 대부분은 오너 계정·수동 실측 필요")]
|
||||
r=5
|
||||
for row in rows:
|
||||
for i,v in enumerate(row,1):
|
||||
c=ws.cell(row=r,column=i,value=v); c.font=bb if i==1 else bf; c.border=bd; c.alignment=center if i>1 else wrap
|
||||
r+=1
|
||||
r+=1
|
||||
for i,h in enumerate(["카테고리 (가중치)","공식 %","샘플 noindex %","색인 허용 %","차이 (색인 허용 − 공식)"],1):
|
||||
c=ws.cell(row=r,column=i,value=h); c.font=hf; c.fill=fill(VIOLET); c.alignment=center; c.border=bd
|
||||
r+=1
|
||||
for cat,(nm,w) in CATS.items():
|
||||
a=S_off[0][cat]; b=S_prod[0][cat]; f=S_fin[0][cat]
|
||||
vals=[f"{cat}. {nm} ({w})",f"{a[2]}% ({a[3]}/{sum(1 for x in names if x[0]==cat)} 검증)",f"{b[2]}% ({b[3]} 검증)",f"{f[2]}% ({f[3]} 검증)",f"{f[2]-a[2]:+d}p"]
|
||||
for i,v in enumerate(vals,1):
|
||||
c=ws.cell(row=r,column=i,value=v); c.font=bf; c.border=bd; c.alignment=center if i>1 else wrap
|
||||
if i==5: c.fill=fill(GOOD if f[2]-a[2]>0 else CRIT if f[2]-a[2]<0 else LIGHT)
|
||||
r+=1
|
||||
r+=1
|
||||
notes=["읽는 법",
|
||||
"· 샘플 noindex 열: 지금 배포된 그대로. robots.txt가 전면 차단이라 A1이 0점, sitemap 미선언으로 A5 1점. 의도된 상태입니다(발행 전).",
|
||||
"· 색인 허용 빌드 열: PUBLIC_INDEXABLE=true로 빌드한 같은 코드. 발행 시 robots 전면 허용 + AI 봇 명시 + sitemap 선언. A4·B6·A7은 로컬에서 판정할 수 없어 프로덕션 실측값을 씁니다.",
|
||||
"· D(표면)·E(측정)는 사이트가 아니라 병원 엔티티·오너 계정에 걸린 항목입니다. 서포터즈 사이트가 직접 바꿀 수 있는 항목은 A·B·C·F이며, E는 서포터즈 도메인의 서버 로그·GA4를 붙이면 채워집니다.",
|
||||
"· 공식 홈페이지 수치는 2026-08-31 재채점 기준(50점/C)이며 discovery_viewclinic.ts와 같습니다.",
|
||||
"· 점수는 '인용된다'는 약속이 아닙니다. 인용될 수 없는 이유를 제거했는지의 체크리스트입니다(INFINITH 정직성 규약)."]
|
||||
for n in notes:
|
||||
c=ws.cell(row=r,column=1,value=n); c.font=bb if n=="읽는 법" else bf; c.alignment=wrap; ws.merge_cells(start_row=r,start_column=1,end_row=r,end_column=5); ws.row_dimensions[r].height=44 if n!="읽는 법" else 24; r+=1
|
||||
for i,w in enumerate([44,36,36,36,56],1): ws.column_dimensions[get_column_letter(i)].width=w
|
||||
# ── 2. 항목별 비교
|
||||
ws2=wb.create_sheet("항목별 비교 (36항목)")
|
||||
hdr=["ID","카테고리","항목","가중치","공식 홈페이지 레벨","공식 근거","서포터즈 샘플 레벨 (noindex)","샘플 근거","서포터즈 색인 허용 레벨","색인 허용 근거","판정 출처","차이 (색인 허용 − 공식)"]
|
||||
for i,h in enumerate(hdr,1):
|
||||
c=ws2.cell(row=1,column=i,value=h); c.font=hf; c.fill=fill(NAVY); c.alignment=center; c.border=bd
|
||||
r=2
|
||||
def L2(v): return "미검증" if v=="unverified" else v
|
||||
for cid in names:
|
||||
o=official[cid]; p=prod.get(cid); f=final[cid]
|
||||
pL=lv_prod[cid]; pE=(p["evidence"] if p else "")
|
||||
if cid in ov and (not p or lvl(p)=="unverified"): pE=ov[cid]["evidence"]
|
||||
diff="" if (o["level"]=="unverified" or f["level"]=="unverified") else f["level"]-o["level"]
|
||||
vals=[cid,f"{cid[0]}. {CATS[cid[0]][0]}",names[cid][0],W[cid],L2(o["level"]),o["evidence"],L2(pL),pE,L2(f["level"]),f["evidence"],f["src"],(f"{diff:+d}" if diff!="" else "")]
|
||||
for i,v in enumerate(vals,1):
|
||||
c=ws2.cell(row=r,column=i,value=v); c.font=bf; c.border=bd; c.alignment=wrap if i in (3,6,8,10,11) else center
|
||||
if i in (5,7,9) and v!="미검증": c.fill=fill(GOOD if v==3 else LIGHT if v==2 else WARN if v==1 else CRIT)
|
||||
if i==12 and diff!="": c.fill=fill(GOOD if diff>0 else CRIT if diff<0 else LIGHT)
|
||||
r+=1
|
||||
for i,w in enumerate([8,24,38,9,11,60,12,60,12,60,24,12],1): ws2.column_dimensions[get_column_letter(i)].width=w
|
||||
ws2.freeze_panes="D2"
|
||||
# ── 3. 남은 과제
|
||||
ws3=wb.create_sheet("서포터즈 남은 과제")
|
||||
todo=[("A4","www 변형 미수렴","vercel.app 임시 도메인은 www 변형이 없음. 정식 도메인(별도 도메인 또는 서브도메인) 확정 후 http/www 4변형 301 통일","도메인 결정 후"),
|
||||
("B4","주소 표기 2종","팩트 시트 단일 원본으로 통일 완료(2026-09-04 수정). 다음 실측에서 3점 예상","완료"),
|
||||
("B5","sameAs 인식 1개","MedicalClinic 노드에 sameAs 5개가 있으나 채점기가 Organization 타입 노드를 먼저 읽음. 스키마 노드 순서·타입 보강","코드 수정"),
|
||||
("B7","hreflang 없음","단일 언어 매체라 해당 없음. 영문판을 만들면 연결","해당 없음"),
|
||||
("C4","구체 수치","시술 페이지에 절개 길이·수술 시간은 있으나 가격·회복 일수 표 없음. 가격 노출 범위는 경영진 결정 대기","결정 대기"),
|
||||
("C5","표 1개","시술 글마다 비교표 1~2개로 확대","콘텐츠"),
|
||||
("D1~D5","표면 항목","병원 엔티티 항목. 서포터즈 사이트가 아니라 병원 계정(강남언니·GBP·플레이스)에서 개선","병원"),
|
||||
("E1~E4","측정 인프라","서포터즈 도메인에 Vercel 로그 드레인 + GA4 AI 채널그룹 + GSC·Bing 등록. 발행과 동시에 설치","발행 시"),
|
||||
("F2","CMS 판정 불가","정적 빌드라 CMS·버전 노출 없음. 채점기 기준으로는 미검증이지만 실질 위험은 없음","해당 없음"),
|
||||
("전제","noindex 해제","법무 확인·도메인 결정·서포터즈 실명·원장 감수 완료 후 PUBLIC_INDEXABLE=true로 발행","경영진")]
|
||||
for i,h in enumerate(["항목","현재","조치","상태"],1):
|
||||
c=ws3.cell(row=1,column=i,value=h); c.font=hf; c.fill=fill(VIOLET); c.alignment=center; c.border=bd
|
||||
for r,row in enumerate(todo,2):
|
||||
for i,v in enumerate(row,1):
|
||||
c=ws3.cell(row=r,column=i,value=v); c.font=bf; c.border=bd; c.alignment=wrap
|
||||
for i,w in enumerate([12,28,100,16],1): ws3.column_dimensions[get_column_letter(i)].width=w
|
||||
|
||||
# ── 4. E 측정 연결 가이드
|
||||
ws4=wb.create_sheet("E 측정 인프라 연결 가이드")
|
||||
ws4["A1"]="E. AI 가시성 측정 인프라 (가중치 10) · 무엇을 어디서 연결해야 채점되는가"; ws4["A1"].font=Font(name="Pretendard",bold=True,size=18,color=NAVY)
|
||||
ws4["A2"]="원칙: 답변엔진 UI를 자동 조회하지 않는다(OpenAI·Perplexity 약관). 측정은 우리가 소유한 신호(서버 로그·GA4·GSC)와 정식 API 반복 측정으로만 한다. 현재 공식 홈페이지·서포터즈 모두 E 4항목이 미검증(분모 제외) 상태다."; ws4["A2"].font=Font(name="Pretendard",size=14,color="64748B"); ws4["A2"].alignment=wrap; ws4.merge_cells("A2:I2"); ws4.row_dimensions[2].height=48
|
||||
hdr=["항목 (가중치)","레벨 정의 0 → 3","필요한 데이터","공식 홈페이지 (viewclinic.com, nginx 직접 호스팅)","서포터즈 사이트 (Vercel 정적)","연결 방법 (단계)","접근 권한 · 담당","주기 · 산출물","현재 → 목표"]
|
||||
for i,h in enumerate(hdr,1):
|
||||
c=ws4.cell(row=4,column=i,value=h); c.font=hf; c.fill=fill(NAVY); c.alignment=center; c.border=bd
|
||||
E_ROWS=[
|
||||
("E1 AI 크롤러 로그 · IP 검증 (3)",
|
||||
"0 로그 접근 불가 → 1 로그 있음·미분석 → 2 UA 기준 집계 → 3 IP 검증 히트 집계",
|
||||
"웹서버 접근 로그(요청 시각·URL·User-Agent·원격 IP·상태코드). AI 봇 UA: GPTBot, OAI-SearchBot, ChatGPT-User, PerplexityBot, ClaudeBot, Claude-SearchBot, Google-Extended, Applebot-Extended, Bingbot, Amazonbot, CCBot, Bytespider",
|
||||
"nginx access.log. 병원 호스팅 업체(또는 내부 IT)에서 로그 파일 또는 로그 전송(syslog/S3) 설정 필요. Cloudflare 미사용이라 AI Crawl Control 대시보드 없음",
|
||||
"Vercel 프로젝트 → Settings → Log Drains(Pro 플랜 이상) 또는 Observability → Logs. 드레인 대상: Supabase Edge Function(수신 엔드포인트) → crawler_hits 테이블. 무료 플랜이면 Vercel 대시보드 로그 수동 내보내기로 시작",
|
||||
"1) 로그 수집 경로 확보 2) UA로 1차 필터 3) 원격 IP를 공개 IP 대역과 대조해 위조 제거: OpenAI gptbot.json·searchbot.json, Google googlebot.json·special-crawlers.json, Perplexity perplexitybot.json, Bing bingbot.json, Anthropic 공개 IP 문서, 나머지는 역방향 DNS 4) 봇별·URL별 일별 히트 집계 5) 대시보드(/discovery 리포트 E 카드)에 표시. 참조 구현: ADO2 009. AEO-GEO/06_skill-snapshot/scripts/bot_ip_verify.py",
|
||||
"공식: 병원 호스팅 관리자 계정 (병원 IT 또는 대행사). 서포터즈: INFINITH Vercel 팀(o2odev) + Supabase 서비스 키",
|
||||
"일별 자동 집계, 주간 리포트. 산출물: crawler_hits 테이블, 봇별 방문 추이 차트, 첫 방문일(TTFC)",
|
||||
"공식 미검증 → 2(UA 집계)까지 로그만 받으면 즉시, 3은 IP 검증 스크립트 연결 후. 서포터즈 미검증 → 발행과 동시에 3"),
|
||||
("E2 GA4 AI 유입 채널 (3)",
|
||||
"0 GA4 없음 → 1 GA4 있음·AI 채널 없음 → 2 기본 AI Assistant 채널 → 3 커스텀 채널(Perplexity 포함)",
|
||||
"GA4 속성의 세션 소스/매체·리퍼러. 리퍼러 도메인: chatgpt.com, chat.openai.com, perplexity.ai, gemini.google.com, bard.google.com, copilot.microsoft.com, bing.com/chat, claude.ai, you.com, phind.com, 그리고 UTM(utm_source=chatgpt 등)",
|
||||
"홈페이지에 GA4 태그가 있는지부터 확인(2026-08 실측 시 오너 접근 불가로 미검증). 있으면 관리 → 데이터 표시 → 채널 그룹에서 커스텀 채널 추가. 없으면 GTM으로 GA4 설치 후 동일",
|
||||
"Astro 레이아웃에 GA4(gtag.js) 삽입 + 같은 커스텀 채널 그룹. 정적 사이트라 서버사이드 태깅 불필요. 서포터즈 GA4 속성은 병원 GA4 계정 안에 별도 속성으로 두어 병원이 소유",
|
||||
"1) GA4 속성 생성 또는 접근 확보 2) 관리 → 채널 그룹 → 새 채널 그룹 생성 3) 채널 'AI Assistant' 규칙: 세션 소스가 정규식 (chatgpt|openai|perplexity|gemini|bard|copilot|bing\\.com/chat|claude|you\\.com|phind) 와 일치 4) 채널 우선순위를 Referral 위로 5) 탐색 보고서에 AI 채널 세션·랜딩 페이지·전환 추가 6) 서포터즈→병원 상담 예약 링크에 utm_source=viewsupporters 부착해 귀속",
|
||||
"공식: 병원 GA4 관리자(편집자 권한). 서포터즈: INFINITH가 속성 생성 후 병원 계정에 관리자 권한 부여",
|
||||
"실시간 수집, 월간 리포트. 산출물: AI 채널 세션 수·전환 수(하한값), 랜딩 페이지 순위. 주의: AI 유입의 35~70%가 Direct로 유실되므로 하한값으로만 보고",
|
||||
"공식 미검증 → 3 (GA4 접근 확보 후 30분 작업). 서포터즈 미검증 → 발행 시 3"),
|
||||
("E3 GSC AI Overviews 노출 (2)",
|
||||
"0 GSC 미등록 → 1 등록만 → 2 데이터 확인 → 3 월간 추적",
|
||||
"Google Search Console 실적 보고서의 검색 유형/검색 결과 모양 필터(AI 개요·AI 모드 노출·클릭). Bing Webmaster Tools(A6)와 함께 등록",
|
||||
"search.google.com/search-console에 viewclinic.com 도메인 속성 등록. DNS TXT 인증은 도메인 관리자(병원)가 필요. 이미 등록돼 있을 수 있으니 오너 확인 먼저",
|
||||
"서포터즈 도메인 확정 후 도메인 속성 등록(vercel.app 임시 도메인은 등록 불가 아님이나 발행 도메인으로 다시 등록해야 하므로 도메인 확정 후 진행). Vercel DNS면 TXT 추가 1분",
|
||||
"1) GSC 도메인 속성 추가 → DNS TXT 인증 2) sitemap.xml 제출 3) 실적 보고서에서 '검색 결과 모양' 또는 '검색 유형' 필터로 AI 개요 노출 확인 4) 월 1회 스냅샷 저장(노출·클릭·평균 게재순위) 5) Bing 웹마스터 도구에 GSC 가져오기로 동시 등록(A6 해소) 6) IndexNow 키 설정",
|
||||
"공식: 도메인 DNS 관리자(병원 또는 가비아 등 등록 대행). 서포터즈: INFINITH(Vercel DNS)",
|
||||
"월 1회 수동 스냅샷. 산출물: AI 개요 노출 페이지 목록, 월별 추이표",
|
||||
"공식 미검증 → 2는 등록 직후, 3은 3개월 누적 후. 서포터즈 미검증 → 도메인 확정 후 등록"),
|
||||
("E4 기준선 스팟체크 (2)",
|
||||
"0 없음 → 1 단발 조회만 → 2 반복 측정 1회 → 3 분기 반복 측정",
|
||||
"질문 세트(QB 120문항 또는 핵심 10문항) × 엔진(OpenAI Responses API+web_search 서울 고정, Perplexity sonar) × 반복 5~7회 → 언급 여부·인용 URL·순위. 결과는 언급률 + 신뢰구간으로만 보고",
|
||||
"이미 1회 실시(2026-09-01, 120문항 × 2엔진). 현재 레벨 2에 해당하나 리포트 원본이 INFINITH 측에 있어 병원 기준 미검증 처리. 분기 반복으로 3",
|
||||
"동일 문항으로 발행 4주·8주 후 재측정. 서포터즈 도메인이 인용 URL에 등장하는지가 핵심 지표",
|
||||
"1) scripts/run_question_bank_openai.py 로 OpenAI·Perplexity 반복 실행(JSONL 재개 가능) 2) scripts/merge_qb_results.py 로 통합표 3) scripts/export_qb_excel.py 로 엑셀 4) 브랜드/논브랜드 언급률·95% 신뢰구간 산출 5) 인용 도메인 빈도표에 viewsupporters 도메인 추가 6) 분기마다 반복해 추이 저장",
|
||||
"INFINITH (OPENAI_API_KEY·PERPLEXITY_API_KEY 회사 계정). 병원 접근 불필요",
|
||||
"분기 1회(발행 초기에는 4주·8주). 산출물: Viewclinic_QB_Results_*.xlsx, 언급률 추이, 인용 URL 목록",
|
||||
"공식 미검증 → 2 즉시 인정 가능(리포트 첨부), 3은 다음 분기 재측정 후. 서포터즈 동일"),
|
||||
]
|
||||
for r,row in enumerate(E_ROWS,5):
|
||||
for i,v in enumerate(row,1):
|
||||
c=ws4.cell(row=r,column=i,value=v); c.font=bb if i==1 else bf; c.border=bd; c.alignment=wrap
|
||||
ws4.row_dimensions[r].height=330
|
||||
for i,w in enumerate([26,34,48,50,50,70,36,40,36],1): ws4.column_dimensions[get_column_letter(i)].width=w
|
||||
ws4.freeze_panes="B5"
|
||||
r=len(E_ROWS)+6
|
||||
extra=[("공통 전제","서버 로그·GA4·GSC는 '오너가 소유한 신호'다. 병원이 계정 권한을 주지 않으면 공식 홈페이지 E는 계속 미검증이다. 서포터즈 사이트는 INFINITH가 인프라를 소유하므로 발행과 동시에 E 4항목을 모두 채울 수 있고, 이것이 서포터즈 사이트가 측정 장치 역할을 한다는 뜻이다."),
|
||||
("IP 대역 출처","OpenAI: openai.com/gptbot.json, openai.com/searchbot.json, openai.com/chatgpt-user.json · Google: developers.google.com/search/apis/ipranges/googlebot.json, special-crawlers.json, user-triggered-fetchers.json · Perplexity: perplexity.com/perplexitybot.json · Bing: bing.com/toolbox/bingbot.json · Anthropic: 공식 문서의 공개 IP 목록 · 나머지 봇: 역방향 DNS + 정방향 재확인"),
|
||||
("데이터 보관","crawler_hits(봇·IP·URL·시각·검증여부), ga4_ai_sessions(일·채널·랜딩·전환), gsc_ai_snapshot(월·페이지·노출·클릭), qb_runs(분기·문항·엔진·언급·인용URL). 모두 Supabase에 두고 /discovery/:id 리포트 E 카드가 읽는다."),
|
||||
("보고 규칙","단발 조회 금지. 언급률은 신뢰구간과 함께. 크롤러 히트는 IP 검증분만. GA4 AI 유입은 하한값 표기. 스키마·콘텐츠 변경과 지표 변화의 인과를 단정하지 않는다.")]
|
||||
for k,v in extra:
|
||||
c=ws4.cell(row=r,column=1,value=k); c.font=bb; c.border=bd; c.alignment=wrap
|
||||
c=ws4.cell(row=r,column=2,value=v); c.font=bf; c.border=bd; c.alignment=wrap; ws4.merge_cells(start_row=r,start_column=2,end_row=r,end_column=9); ws4.row_dimensions[r].height=64; r+=1
|
||||
|
||||
wb.save(OUT); print("saved",OUT, "official",S_off[3],S_off[4],"prod",S_prod[3],S_prod[4],"idx",S_fin[3],S_fin[4])
|
||||
2
supporters/.env.example
Normal file
@ -0,0 +1,2 @@
|
||||
PUBLIC_INDEXABLE=false
|
||||
SITE_URL=https://view-supporters-sample.vercel.app
|
||||
25
supporters/.gitignore
vendored
Normal file
@ -0,0 +1,25 @@
|
||||
# build output
|
||||
dist/
|
||||
# generated types
|
||||
.astro/
|
||||
|
||||
# dependencies
|
||||
node_modules/
|
||||
|
||||
# logs
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
|
||||
|
||||
# environment variables
|
||||
.env
|
||||
.env.production
|
||||
|
||||
# macOS-specific files
|
||||
.DS_Store
|
||||
|
||||
# jetbrains setting folder
|
||||
.idea/
|
||||
.vercel
|
||||
4
supporters/.vscode/extensions.json
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
{
|
||||
"recommendations": ["astro-build.astro-vscode"],
|
||||
"unwantedRecommendations": []
|
||||
}
|
||||
11
supporters/.vscode/launch.json
vendored
Normal file
@ -0,0 +1,11 @@
|
||||
{
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"command": "./node_modules/.bin/astro dev",
|
||||
"name": "Development server",
|
||||
"request": "launch",
|
||||
"type": "node-terminal"
|
||||
}
|
||||
]
|
||||
}
|
||||
22
supporters/AGENTS.md
Normal file
@ -0,0 +1,22 @@
|
||||
## Development
|
||||
|
||||
When starting the dev server, use background mode:
|
||||
|
||||
```
|
||||
astro dev --background
|
||||
```
|
||||
|
||||
Manage the background server with `astro dev stop`, `astro dev status`, and `astro dev logs`.
|
||||
|
||||
## Documentation
|
||||
|
||||
Full documentation: https://docs.astro.build
|
||||
|
||||
Consult these guides before working on related tasks:
|
||||
|
||||
- [Adding pages, dynamic routes, or middleware](https://docs.astro.build/en/guides/routing/)
|
||||
- [Working with Astro components](https://docs.astro.build/en/basics/astro-components/)
|
||||
- [Using React, Vue, Svelte, or other framework components](https://docs.astro.build/en/guides/framework-components/)
|
||||
- [Adding or managing content](https://docs.astro.build/en/guides/content-collections/)
|
||||
- [Adding styles or using Tailwind](https://docs.astro.build/en/guides/styling/)
|
||||
- [Supporting multiple languages](https://docs.astro.build/en/guides/internationalization/)
|
||||
1
supporters/CLAUDE.md
Symbolic link
@ -0,0 +1 @@
|
||||
AGENTS.md
|
||||
43
supporters/README.md
Normal file
@ -0,0 +1,43 @@
|
||||
# Astro Starter Kit: Minimal
|
||||
|
||||
```sh
|
||||
npm create astro@latest -- --template minimal
|
||||
```
|
||||
|
||||
> 🧑🚀 **Seasoned astronaut?** Delete this file. Have fun!
|
||||
|
||||
## 🚀 Project Structure
|
||||
|
||||
Inside of your Astro project, you'll see the following folders and files:
|
||||
|
||||
```text
|
||||
/
|
||||
├── public/
|
||||
├── src/
|
||||
│ └── pages/
|
||||
│ └── index.astro
|
||||
└── package.json
|
||||
```
|
||||
|
||||
Astro looks for `.astro` or `.md` files in the `src/pages/` directory. Each page is exposed as a route based on its file name.
|
||||
|
||||
There's nothing special about `src/components/`, but that's where we like to put any Astro/React/Vue/Svelte/Preact components.
|
||||
|
||||
Any static assets, like images, can be placed in the `public/` directory.
|
||||
|
||||
## 🧞 Commands
|
||||
|
||||
All commands are run from the root of the project, from a terminal:
|
||||
|
||||
| Command | Action |
|
||||
| :------------------------ | :----------------------------------------------- |
|
||||
| `npm install` | Installs dependencies |
|
||||
| `npm run dev` | Starts local dev server at `localhost:4321` |
|
||||
| `npm run build` | Build your production site to `./dist/` |
|
||||
| `npm run preview` | Preview your build locally, before deploying |
|
||||
| `npm run astro ...` | Run CLI commands like `astro add`, `astro check` |
|
||||
| `npm run astro -- --help` | Get help using the Astro CLI |
|
||||
|
||||
## 👀 Want to learn more?
|
||||
|
||||
Feel free to check [our documentation](https://docs.astro.build) or jump into our [Discord server](https://astro.build/chat).
|
||||
12
supporters/astro.config.mjs
Normal file
@ -0,0 +1,12 @@
|
||||
// @ts-check
|
||||
import { defineConfig } from 'astro/config';
|
||||
|
||||
// 샘플 배포 주소. 도메인 확정 후 SITE_URL 환경변수로 교체한다.
|
||||
const site = process.env.SITE_URL || 'https://view-supporters-sample.vercel.app';
|
||||
|
||||
export default defineConfig({
|
||||
site,
|
||||
trailingSlash: 'never',
|
||||
build: { format: 'file' },
|
||||
compressHTML: true,
|
||||
});
|
||||
BIN
supporters/docs/v1-screenshots/v1_clinic.png
Normal file
|
After Width: | Height: | Size: 516 KiB |
BIN
supporters/docs/v1-screenshots/v1_home.png
Normal file
|
After Width: | Height: | Size: 386 KiB |
BIN
supporters/docs/v1-screenshots/v1_list.png
Normal file
|
After Width: | Height: | Size: 336 KiB |
BIN
supporters/docs/v1-screenshots/v1_mobile.png
Normal file
|
After Width: | Height: | Size: 1.2 MiB |
BIN
supporters/docs/v1-screenshots/v1_post.png
Normal file
|
After Width: | Height: | Size: 574 KiB |
BIN
supporters/docs/v2-screenshots/v2_about.png
Normal file
|
After Width: | Height: | Size: 710 KiB |
BIN
supporters/docs/v2-screenshots/v2_clinic.png
Normal file
|
After Width: | Height: | Size: 2.7 MiB |
BIN
supporters/docs/v2-screenshots/v2_home.png
Normal file
|
After Width: | Height: | Size: 7.8 MiB |
BIN
supporters/docs/v2-screenshots/v2_list.png
Normal file
|
After Width: | Height: | Size: 3.5 MiB |
BIN
supporters/docs/v2-screenshots/v2_mobile_home.png
Normal file
|
After Width: | Height: | Size: 4.3 MiB |
BIN
supporters/docs/v2-screenshots/v2_mobile_post_motiva.png
Normal file
|
After Width: | Height: | Size: 3.1 MiB |
BIN
supporters/docs/v2-screenshots/v2_post_motiva.png
Normal file
|
After Width: | Height: | Size: 4.0 MiB |
BIN
supporters/docs/v2-screenshots/v2_post_safety.png
Normal file
|
After Width: | Height: | Size: 4.4 MiB |
BIN
supporters/docs/v2-screenshots/v2_post_visit.png
Normal file
|
After Width: | Height: | Size: 4.0 MiB |
BIN
supporters/docs/v3-screenshots/v3_header_logo.png
Normal file
|
After Width: | Height: | Size: 1.1 MiB |
BIN
supporters/docs/v3-screenshots/v3_home_full.png
Normal file
|
After Width: | Height: | Size: 7.9 MiB |
BIN
supporters/docs/v3-screenshots/v3_home_hero.png
Normal file
|
After Width: | Height: | Size: 2.6 MiB |
BIN
supporters/docs/v3-screenshots/v3_newsroom_full.png
Normal file
|
After Width: | Height: | Size: 12 MiB |
BIN
supporters/docs/v3-screenshots/v3_newsroom_top.png
Normal file
|
After Width: | Height: | Size: 483 KiB |
BIN
supporters/docs/v3-screenshots/v3_post_backbtn.png
Normal file
|
After Width: | Height: | Size: 833 KiB |
BIN
supporters/docs/v3-screenshots/v3_post_summary.png
Normal file
|
After Width: | Height: | Size: 616 KiB |
BIN
supporters/docs/v3-screenshots/v3_post_totop.png
Normal file
|
After Width: | Height: | Size: 296 KiB |
BIN
supporters/docs/v3-screenshots/v3_videos_list.png
Normal file
|
After Width: | Height: | Size: 615 KiB |
BIN
supporters/docs/v3-screenshots/v3_videos_long.png
Normal file
|
After Width: | Height: | Size: 1.1 MiB |
BIN
supporters/docs/v3-screenshots/v3_videos_shorts.png
Normal file
|
After Width: | Height: | Size: 649 KiB |
BIN
supporters/docs/v3-screenshots/v3_videos_top.png
Normal file
|
After Width: | Height: | Size: 649 KiB |
4096
supporters/package-lock.json
generated
Normal file
21
supporters/package.json
Normal file
@ -0,0 +1,21 @@
|
||||
{
|
||||
"name": "supporters",
|
||||
"type": "module",
|
||||
"version": "0.0.1",
|
||||
"engines": {
|
||||
"node": ">=22.12.0"
|
||||
},
|
||||
"scripts": {
|
||||
"dev": "astro dev",
|
||||
"build": "astro build && node scripts/check.mjs",
|
||||
"preview": "astro preview",
|
||||
"astro": "astro",
|
||||
"check": "node scripts/check.mjs"
|
||||
},
|
||||
"dependencies": {
|
||||
"astro": "^7.3.1"
|
||||
},
|
||||
"allowScripts": {
|
||||
"esbuild": true
|
||||
}
|
||||
}
|
||||
BIN
supporters/public/favicon.ico
Normal file
|
After Width: | Height: | Size: 655 B |
9
supporters/public/favicon.svg
Normal file
@ -0,0 +1,9 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 128 128">
|
||||
<path d="M50.4 78.5a75.1 75.1 0 0 0-28.5 6.9l24.2-65.7c.7-2 1.9-3.2 3.4-3.2h29c1.5 0 2.7 1.2 3.4 3.2l24.2 65.7s-11.6-7-28.5-7L67 45.5c-.4-1.7-1.6-2.8-2.9-2.8-1.3 0-2.5 1.1-2.9 2.7L50.4 78.5Zm-1.1 28.2Zm-4.2-20.2c-2 6.6-.6 15.8 4.2 20.2a17.5 17.5 0 0 1 .2-.7 5.5 5.5 0 0 1 5.7-4.5c2.8.1 4.3 1.5 4.7 4.7.2 1.1.2 2.3.2 3.5v.4c0 2.7.7 5.2 2.2 7.4a13 13 0 0 0 5.7 4.9v-.3l-.2-.3c-1.8-5.6-.5-9.5 4.4-12.8l1.5-1a73 73 0 0 0 3.2-2.2 16 16 0 0 0 6.8-11.4c.3-2 .1-4-.6-6l-.8.6-1.6 1a37 37 0 0 1-22.4 2.7c-5-.7-9.7-2-13.2-6.2Z" />
|
||||
<style>
|
||||
path { fill: #000; }
|
||||
@media (prefers-color-scheme: dark) {
|
||||
path { fill: #FFF; }
|
||||
}
|
||||
</style>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 749 B |
BIN
supporters/public/img/breast/award.jpg
Normal file
|
After Width: | Height: | Size: 89 KiB |
BIN
supporters/public/img/breast/consultation.jpg
Normal file
|
After Width: | Height: | Size: 126 KiB |
BIN
supporters/public/img/breast/dream-team.jpg
Normal file
|
After Width: | Height: | Size: 299 KiB |
BIN
supporters/public/img/breast/global-kol.jpg
Normal file
|
After Width: | Height: | Size: 88 KiB |
BIN
supporters/public/img/breast/main-1.jpg
Normal file
|
After Width: | Height: | Size: 47 KiB |
BIN
supporters/public/img/breast/main-2.jpg
Normal file
|
After Width: | Height: | Size: 34 KiB |
BIN
supporters/public/img/breast/medicine.jpg
Normal file
|
After Width: | Height: | Size: 52 KiB |
BIN
supporters/public/img/breast/motiva-no1.png
Normal file
|
After Width: | Height: | Size: 54 KiB |
BIN
supporters/public/img/breast/motiva-slide-1.png
Normal file
|
After Width: | Height: | Size: 138 KiB |
BIN
supporters/public/img/breast/motiva-slide-2.png
Normal file
|
After Width: | Height: | Size: 392 KiB |
BIN
supporters/public/img/breast/motiva-slide-3.png
Normal file
|
After Width: | Height: | Size: 161 KiB |
BIN
supporters/public/img/breast/motiva-slide-4.png
Normal file
|
After Width: | Height: | Size: 458 KiB |
BIN
supporters/public/img/breast/motiva-slide-5.png
Normal file
|
After Width: | Height: | Size: 226 KiB |
BIN
supporters/public/img/breast/motiva-slide-6.png
Normal file
|
After Width: | Height: | Size: 130 KiB |
BIN
supporters/public/img/breast/step-01.jpg
Normal file
|
After Width: | Height: | Size: 99 KiB |
BIN
supporters/public/img/breast/step-03.jpg
Normal file
|
After Width: | Height: | Size: 66 KiB |
BIN
supporters/public/img/breast/step-04.jpg
Normal file
|
After Width: | Height: | Size: 37 KiB |
BIN
supporters/public/img/breast/step-05.jpg
Normal file
|
After Width: | Height: | Size: 42 KiB |
BIN
supporters/public/img/breast/step-06.jpg
Normal file
|
After Width: | Height: | Size: 50 KiB |
BIN
supporters/public/img/breast/step.png
Normal file
|
After Width: | Height: | Size: 292 KiB |
BIN
supporters/public/img/breast/venus.jpg
Normal file
|
After Width: | Height: | Size: 230 KiB |
BIN
supporters/public/img/care/swiper-1.png
Normal file
|
After Width: | Height: | Size: 32 KiB |
BIN
supporters/public/img/care/swiper-2.png
Normal file
|
After Width: | Height: | Size: 64 KiB |
BIN
supporters/public/img/care/swiper-3.png
Normal file
|
After Width: | Height: | Size: 130 KiB |
BIN
supporters/public/img/care/swiper-4.png
Normal file
|
After Width: | Height: | Size: 35 KiB |
BIN
supporters/public/img/care/swiper-5.png
Normal file
|
After Width: | Height: | Size: 122 KiB |
BIN
supporters/public/img/care/swiper-6.png
Normal file
|
After Width: | Height: | Size: 124 KiB |
BIN
supporters/public/img/care/swiper-7.png
Normal file
|
After Width: | Height: | Size: 119 KiB |
BIN
supporters/public/img/care/swiper-8.png
Normal file
|
After Width: | Height: | Size: 127 KiB |
BIN
supporters/public/img/care/swiper-9.png
Normal file
|
After Width: | Height: | Size: 63 KiB |
BIN
supporters/public/img/clinic-lounge.jpg
Normal file
|
After Width: | Height: | Size: 61 KiB |
BIN
supporters/public/img/clinic/building-clean.jpg
Normal file
|
After Width: | Height: | Size: 52 KiB |
BIN
supporters/public/img/clinic/consult-1.jpg
Normal file
|
After Width: | Height: | Size: 271 KiB |
BIN
supporters/public/img/clinic/consult-2.jpg
Normal file
|
After Width: | Height: | Size: 243 KiB |
BIN
supporters/public/img/clinic/consult-3.jpg
Normal file
|
After Width: | Height: | Size: 259 KiB |
BIN
supporters/public/img/clinic/dental-15f.jpg
Normal file
|
After Width: | Height: | Size: 468 KiB |
BIN
supporters/public/img/clinic/exam-1.jpg
Normal file
|
After Width: | Height: | Size: 465 KiB |
BIN
supporters/public/img/clinic/exam-2.jpg
Normal file
|
After Width: | Height: | Size: 358 KiB |
BIN
supporters/public/img/clinic/exam-3.jpg
Normal file
|
After Width: | Height: | Size: 268 KiB |
BIN
supporters/public/img/clinic/hero-collage.jpg
Normal file
|
After Width: | Height: | Size: 56 KiB |
BIN
supporters/public/img/clinic/hero-view.jpg
Normal file
|
After Width: | Height: | Size: 55 KiB |
BIN
supporters/public/img/clinic/hours-building.jpg
Normal file
|
After Width: | Height: | Size: 93 KiB |
BIN
supporters/public/img/clinic/map-1.jpg
Normal file
|
After Width: | Height: | Size: 107 KiB |
BIN
supporters/public/img/clinic/news-1.jpg
Normal file
|
After Width: | Height: | Size: 77 KiB |
BIN
supporters/public/img/clinic/news-2.jpg
Normal file
|
After Width: | Height: | Size: 87 KiB |
BIN
supporters/public/img/clinic/news-3.jpg
Normal file
|
After Width: | Height: | Size: 72 KiB |
BIN
supporters/public/img/clinic/news-4.jpg
Normal file
|
After Width: | Height: | Size: 82 KiB |
BIN
supporters/public/img/clinic/news-5.jpg
Normal file
|
After Width: | Height: | Size: 89 KiB |
BIN
supporters/public/img/clinic/news-6.jpg
Normal file
|
After Width: | Height: | Size: 85 KiB |
BIN
supporters/public/img/clinic/news-7.jpg
Normal file
|
After Width: | Height: | Size: 59 KiB |
BIN
supporters/public/img/clinic/parking-map.jpg
Normal file
|
After Width: | Height: | Size: 121 KiB |
BIN
supporters/public/img/clinic/specialties-3.jpg
Normal file
|
After Width: | Height: | Size: 117 KiB |
BIN
supporters/public/img/clinic/specialties-4.jpg
Normal file
|
After Width: | Height: | Size: 100 KiB |
BIN
supporters/public/img/clinic/surgery-1.jpg
Normal file
|
After Width: | Height: | Size: 156 KiB |
BIN
supporters/public/img/clinic/surgery-2.jpg
Normal file
|
After Width: | Height: | Size: 248 KiB |
BIN
supporters/public/img/clinic/surgery-3.jpg
Normal file
|
After Width: | Height: | Size: 131 KiB |
BIN
supporters/public/img/clinic/vip-1.jpg
Normal file
|
After Width: | Height: | Size: 353 KiB |
BIN
supporters/public/img/clinic/vip-lounge.jpg
Normal file
|
After Width: | Height: | Size: 121 KiB |