Compare commits
No commits in common. "main" and "feature/supporters-autobuild" have entirely different histories.
main
...
feature/su
10
.gitignore
vendored
@ -11,13 +11,3 @@ doc/
|
||||
|
||||
# Auto-generated: Supabase DB snapshots (vite-plugin-supabase-sync)
|
||||
src/data/db/
|
||||
|
||||
# Word 잠금 파일
|
||||
~$*
|
||||
|
||||
# 파이썬 캐시
|
||||
__pycache__/
|
||||
|
||||
# 개인 업무 문서 (주간보고 등). 저장소에 올리지 않는다
|
||||
docs/*주간보고*
|
||||
docs/*weekly_report*
|
||||
|
||||
@ -1,65 +0,0 @@
|
||||
# AI 답변 감성 판정 기준표 v0.1
|
||||
|
||||
작성 2026-09-08 · 상태: 초안, haewon 보정 대기 · 적용 스크립트 `scripts/sentiment_qb.py`
|
||||
|
||||
## 1. 무엇을 재는가
|
||||
|
||||
질문 뱅크(120문항)를 답변엔진에 물어 받은 답변 안에서 **대상 병원이 어떤 어조로 언급되는지**를 판정한다. 브랜드 범용 도구(FrostAI 등)의 "감성 점수"에 대응하는 지표지만, 병원은 부정 신호의 대부분이 규제·사건 보도이므로 그 항목을 따로 센다.
|
||||
|
||||
측정 원칙은 질문 뱅크와 같다. 소비자 UI 자동 조회는 하지 않고 공식 API 답변만 쓴다. 답변 원문은 저장하되 리포트에는 근거 문장 한 줄만 인용한다.
|
||||
|
||||
## 2. 판정 단위와 값
|
||||
|
||||
답변 1건 = 판정 1건. 판정기는 아래 JSON을 낸다.
|
||||
|
||||
| 필드 | 값 | 설명 |
|
||||
|---|---|---|
|
||||
| mentioned | true / false | 대상 병원(별칭 포함)이 답변에 나오는가. 추출기의 `view_mentioned`와 교차 확인 |
|
||||
| sentiment | positive / neutral / negative / mixed / not_mentioned | 병원에 대한 어조. 언급이 없으면 not_mentioned |
|
||||
| aspects | 배열 | 아래 §3 측면 태그 중 해당하는 것 |
|
||||
| negative_type | none / regulatory / safety / service / price / reputation / other | 부정이면 어떤 종류인가 |
|
||||
| evidence | 문장 1개 | 판정 근거가 된 답변 원문 문장 (요약 금지, 그대로 인용) |
|
||||
| recommendation | recommended / listed / cautioned / not_applicable | 병원을 추천했는가, 목록에 나열만 했는가, 주의를 붙였는가 |
|
||||
| confidence | 0~1 | 판정기 자신감 |
|
||||
|
||||
### 어조 판정 규칙
|
||||
|
||||
- **positive**: 병원의 강점을 서술하거나 추천 근거로 든다. 예: "안전 시스템이 갖춰진 병원", "가슴성형 전문으로 평가받는다".
|
||||
- **neutral**: 사실 나열, 목록 포함, 위치·진료과목 안내처럼 평가 없는 언급.
|
||||
- **negative**: 부정 사실을 서술하거나 주의를 권한다. 예: "공정위 시정명령을 받았다", "후기 광고 논란이 있었다", "부작용 사례가 보고됐다".
|
||||
- **mixed**: 긍정과 부정이 한 답변에 함께 있다. 점수 계산에서는 negative 쪽으로 센다(보수적).
|
||||
- 다른 병원에 대한 평가는 세지 않는다. 대상 병원 문장만 본다.
|
||||
- "확인이 필요하다", "직접 상담을 권한다" 같은 일반 주의 문구는 negative가 아니라 neutral이다. 병원 고유의 문제를 지목할 때만 negative다.
|
||||
|
||||
## 3. 측면 태그
|
||||
|
||||
| 태그 | 포함 |
|
||||
|---|---|
|
||||
| doctors | 의료진, 전문의, 경력, 원장 |
|
||||
| safety | 안전 시스템, 마취, 부작용, 사고, 회복 관리 |
|
||||
| price | 가격, 비용, 견적, 할인 |
|
||||
| reviews | 후기, 평판, 만족도, 리뷰 수 |
|
||||
| regulatory | 공정위, 시정명령, 의료법 위반, 광고 제재, 소송 |
|
||||
| procedure | 시술·수술 종류, 장비, 기술, 전문 분야 |
|
||||
| access | 위치, 예약, 외국어 응대, 편의 |
|
||||
| media | 방송 출연, 언론 보도, 수상 |
|
||||
|
||||
## 4. 점수
|
||||
|
||||
- **감성 점수(0~100)** = (positive × 100 + neutral × 50 + mixed × 25 + negative × 0) ÷ 언급된 답변 수. Frost의 감성 점수와 같은 척도로 비교할 수 있게 했다.
|
||||
- **부정 비율** = negative + mixed ÷ 언급된 답변 수.
|
||||
- **규제 언급률** = negative_type이 regulatory인 답변 ÷ 언급된 답변 수. 병원 전용 보조 지표.
|
||||
- **추천율** = recommendation이 recommended인 답변 ÷ 언급된 답변 수.
|
||||
- 엔진별로 따로 내고, 브랜드 질문(A·B)과 논브랜드 질문을 분리한다. 논브랜드에서 언급되며 긍정인 것이 가장 가치 있는 신호다.
|
||||
|
||||
## 5. 보정 절차 (공신력)
|
||||
|
||||
1. 판정 결과에서 30건을 무작위로 뽑아 `*_calibration.xlsx`에 담는다. 판정기 값은 숨기고 haewon이 sentiment와 negative_type을 직접 적는다.
|
||||
2. 일치율을 계산한다. sentiment 일치 80% 이상이면 기준표를 확정한다. 미만이면 불일치 사례를 §2 규칙에 예시로 추가하고 다시 돌린다.
|
||||
3. 확정 후에는 같은 기준표·같은 모델로만 월간 실행한다. 모델을 바꾸면 보정을 다시 한다.
|
||||
|
||||
## 6. haewon이 정할 것
|
||||
|
||||
- **부정의 범위**: "후기 광고 논란"처럼 사실이지만 병원에 불리한 언급을 negative로 볼지, 규제 사실 서술은 neutral로 볼지. 초안은 negative다.
|
||||
- **mixed 처리**: 점수에서 25점으로 셀지, 제외할지. 초안은 25점.
|
||||
- **다른 병원 추천 문맥**: 대상 병원이 언급되지 않고 경쟁 병원만 추천된 답변을 별도 지표(경쟁 노출)로 셀지.
|
||||
@ -1,180 +0,0 @@
|
||||
# AI 답변 감성 판정 기준표 v0.3
|
||||
|
||||
작성 2026-09-08 · 개정 2026-09-09 · 상태: 1차 보정 반영, 2차 보정 대기 · 적용 스크립트 `scripts/sentiment_qb.py`
|
||||
|
||||
v0.1 대비 바뀐 것은 둘이다.
|
||||
|
||||
1. **어조 규칙 R1~R5** (§2). 1차 보정에서 haewon 라벨 29건 대비 일치율이 75%로 기준선 80%에 미달해, 불일치 7건에서 뽑았다.
|
||||
2. **결정론적 언급 탐지기** (§2-1). 상호가 URL 안에만 있거나 "리뷰"의 "뷰"를 병원 언급으로 세던 오탐을 없앴다.
|
||||
|
||||
R4-a와 R6은 시험 투입했다가 되돌렸다. 이유는 §5에 있다.
|
||||
|
||||
## 1. 무엇을 재는가
|
||||
|
||||
질문 뱅크(120문항)를 답변엔진에 물어 받은 답변 안에서 **대상 병원이 어떤 어조로 언급되는지**를 판정한다. 브랜드 범용 도구(FrostAI 등)의 "감성 점수"에 대응하는 지표지만, 병원은 부정 신호의 대부분이 규제·사건 보도이므로 그 항목을 따로 센다.
|
||||
|
||||
측정 원칙은 질문 뱅크와 같다. 소비자 UI 자동 조회는 하지 않고 공식 API 답변만 쓴다. 답변 원문은 저장하되 리포트에는 근거 문장 한 줄만 인용한다.
|
||||
|
||||
## 2-1. 언급 탐지 (v0.3 추가)
|
||||
|
||||
`sentiment` 를 매기기 전에 대상 병원이 답변에 실제로 나오는지를 **정규식으로 먼저 판정한다**(`mention_kind()`). 판정기가 언급이라고 해도 탐지기가 없다고 하면 `not_mentioned` 로 덮어쓴다. 언급 수가 모든 비율의 분모라서, 여기가 틀리면 점수 전체가 틀린다.
|
||||
|
||||
| 값 | 조건 |
|
||||
|---|---|
|
||||
| strong | 정식 상호가 답변 본문에 있다. 한글은 음절 사이 공백을 허용한다("뷰 성형외과"도 잡는다). 영문 정식 명칭·도메인도 포함한다 |
|
||||
| weak | 정식 상호 없이 영문 약칭만 있다(예: 병원 목록 안의 "View") |
|
||||
| none | 위 어느 것도 없다 |
|
||||
|
||||
두 가지를 반드시 처리한다.
|
||||
|
||||
- **URL을 먼저 지운다.** `news.nate.com/view/...`, `clinic/view.do`, `?bmode=view` 의 "view"를 병원 언급으로 세면 안 된다. 1차 판정에서 C-13·C-14·E-06 세 건이 이 오탐이었다.
|
||||
- **다른 낱말의 일부인 약칭을 뺀다.** "리뷰"의 "뷰"가 대표적이다. G-04(openai)는 "리뷰 사이트에서도"만 있고 병원 언급이 없는데 언급으로 세고 있었다.
|
||||
|
||||
이 수정으로 언급 수가 84에서 83으로 바뀌었다.
|
||||
|
||||
## 2. 판정 단위와 값
|
||||
|
||||
답변 1건 = 판정 1건. 판정기는 아래 JSON을 낸다.
|
||||
|
||||
| 필드 | 값 | 설명 |
|
||||
|---|---|---|
|
||||
| mentioned | true / false | 대상 병원(별칭 포함)이 답변에 나오는가. 추출기의 `view_mentioned`와 교차 확인 |
|
||||
| sentiment | positive / neutral / negative / mixed / not_mentioned | 병원에 대한 어조. 언급이 없으면 not_mentioned |
|
||||
| aspects | 배열 | 아래 §3 측면 태그 중 해당하는 것 |
|
||||
| negative_type | none / regulatory / safety / service / price / reputation / other | 부정이면 어떤 종류인가 |
|
||||
| evidence | 문장 1개 | 판정 근거가 된 답변 원문 문장 (요약 금지, 그대로 인용) |
|
||||
| recommendation | recommended / listed / cautioned / not_applicable | 병원을 추천했는가, 목록에 나열만 했는가, 주의를 붙였는가 |
|
||||
| confidence | 0~1 | 판정기 자신감 |
|
||||
|
||||
### 어조 판정 규칙
|
||||
|
||||
- **positive**: 병원의 강점을 서술하거나 추천 근거로 든다. 예: "안전 시스템이 갖춰진 병원", "가슴성형 전문으로 평가받는다".
|
||||
- **neutral**: 사실 나열, 목록 포함, 위치·진료과목 안내처럼 평가 없는 언급.
|
||||
- **negative**: 부정 사실을 서술하거나 주의를 권한다. 예: "공정위 시정명령을 받았다", "후기 광고 논란이 있었다", "부작용 사례가 보고됐다".
|
||||
- **mixed**: 긍정과 부정이 한 답변에 함께 있다. 점수 계산에서는 negative 쪽으로 센다(보수적).
|
||||
- 다른 병원에 대한 평가는 세지 않는다. 대상 병원 문장만 본다.
|
||||
- "확인이 필요하다", "직접 상담을 권한다" 같은 일반 주의 문구는 negative가 아니라 neutral이다. 병원 고유의 문제를 지목할 때만 negative다.
|
||||
|
||||
### 우선순위 규칙 (v0.2 추가, 1차 보정에서 도출)
|
||||
|
||||
아래 규칙은 위 어조 정의보다 우선한다. 괄호 안은 근거가 된 보정 사례다.
|
||||
|
||||
**R1. 결론 단락이 개별 항목보다 우선한다.** 답변에 종합 평가·결론 단락이 있으면 그 단락의 어조로 판정한다. 본문에 나열된 개별 항목의 어조로 판정하지 않는다.
|
||||
- A-01(perplexity): 본문에 강남언니 평점 9.4~9.5가 있으나 결론이 "후기 기반 평판은 좋은 편이지만 추가 확인이 필요"라는 확인 권고다. → **neutral** (mixed 아님)
|
||||
- D4-01(openai): 본문에 "모두에게 동일한 설명을 하는 듯한 인상"이라는 후기 지적 한 줄이 있으나 종합 평이 "안전성과 전문성을 갖춘 병원으로 평가받고 있습니다"다. → **positive** (mixed 아님)
|
||||
- D2-01(perplexity): 결론이 "수술 결과의 일관된 객관성까지 판단하긴 어렵고, 커뮤니티에는 부정적 의견도 일부 보입니다"다. → **negative** (mixed 아님)
|
||||
|
||||
**R2. mixed는 긍정과 부정이 대등하게 병존할 때만 쓴다.** 부정 성분이 문장 하나의 부수 언급이면 mixed로 세지 않는다. 긍정과 부정이 각각 독립된 근거로 서술되고 결론이 어느 쪽으로도 기울지 않을 때만 mixed다.
|
||||
|
||||
**R3. "후기 수가 많다", "인지도가 있다"는 규모 서술이며 긍정 평가가 아니다.** 이 서술만 있고 병원 고유의 문제 지목이 함께 있으면 mixed가 아니라 negative다. (D2-01)
|
||||
|
||||
**R4. 평가 축이 있는 목록에 포함되면 positive다.** 질문이 조건이나 우수성을 묻고(평점이 높은 곳, 마취과 전문의가 상주하는 곳, 잘하는 곳) 그 목록에 대상 병원이 들어가면 positive다. 조건 없는 단순 나열(위치·진료과목 안내, 지역 병원 열거)만 neutral이다.
|
||||
- C-12(openai): "강남언니에서 평점 높은 성형외과" 목록에 뷰성형외과가 포함됐다. → **positive**
|
||||
- C-08(openai): "마취과 전문의가 상주하는 강남 성형외과" 목록에 포함되고 "마취과 전문의 365일 상주", "3D-CT, 체계적 사후 관리"가 함께 서술됐다. → **positive**
|
||||
|
||||
**R5. 강점·역량 서술은 출처가 병원 홍보라도 positive로 센다.** 답변이 "환자의 안전과 만족을 최우선", "최신 기술과 장비"처럼 강점을 서술하면, 그 서술이 병원 광고를 인용한 것이라도 답변의 어조는 positive다.
|
||||
- A-02(openai): "환자의 안전과 만족을 최우선으로 하며, 최신 기술과 장비를 사용하여 서비스를 제공한다고 광고합니다" → **positive**
|
||||
|
||||
**R6. (경계 사례, 미확정) 사실 조회 질문의 항목 나열은 neutral이다.** 질문이 "무슨 수술을 주로 하나", "어디에 있나"처럼 사실을 묻고 답변이 진료 항목 카탈로그이면, 개별 항목에 붙은 수식어("풍부한 임상 경험")는 긍정으로 세지 않는다.
|
||||
- A-03(openai): 진료 분야 나열이고 항목마다 홍보성 수식이 붙어 있다. haewon은 **neutral**, 판정기는 positive였다.
|
||||
- **주의**: R6과 R5의 경계가 규칙으로 깔끔히 갈리지 않는다. A-02(positive)와 A-03(neutral) 모두 답변에 병원 홍보 문구가 인용돼 있는데 라벨이 갈렸다. 2차 보정에서 haewon이 이 경계를 확정해야 한다(§6).
|
||||
|
||||
## 3. 측면 태그
|
||||
|
||||
| 태그 | 포함 |
|
||||
|---|---|
|
||||
| doctors | 의료진, 전문의, 경력, 원장 |
|
||||
| safety | 안전 시스템, 마취, 부작용, 사고, 회복 관리 |
|
||||
| price | 가격, 비용, 견적, 할인 |
|
||||
| reviews | 후기, 평판, 만족도, 리뷰 수 |
|
||||
| regulatory | 공정위, 시정명령, 의료법 위반, 광고 제재, 소송 |
|
||||
| procedure | 시술·수술 종류, 장비, 기술, 전문 분야 |
|
||||
| access | 위치, 예약, 외국어 응대, 편의 |
|
||||
| media | 방송 출연, 언론 보도, 수상 |
|
||||
|
||||
## 4. 점수
|
||||
|
||||
- **감성 점수(0~100)** = (positive × 100 + neutral × 50 + mixed × 25 + negative × 0) ÷ 언급된 답변 수. Frost의 감성 점수와 같은 척도로 비교할 수 있게 했다.
|
||||
- **부정 비율** = negative + mixed ÷ 언급된 답변 수.
|
||||
- **규제 언급률** = negative_type이 regulatory인 답변 ÷ 언급된 답변 수. 병원 전용 보조 지표.
|
||||
- **추천율** = recommendation이 recommended인 답변 ÷ 언급된 답변 수.
|
||||
- 엔진별로 따로 내고, 브랜드 질문(A·B)과 논브랜드 질문을 분리한다. 논브랜드에서 언급되며 긍정인 것이 가장 가치 있는 신호다.
|
||||
|
||||
## 5. 보정 절차 (공신력)
|
||||
|
||||
1. 판정 결과에서 30건을 무작위로 뽑아 `*_calibration.xlsx`에 담는다. 판정기 값은 숨기고 haewon이 sentiment와 negative_type을 직접 적는다.
|
||||
2. 일치율을 계산한다. sentiment 일치 80% 이상이면 기준표를 확정한다. 미만이면 불일치 사례를 §2 규칙에 예시로 추가하고 다시 돌린다.
|
||||
3. 확정 후에는 같은 기준표·같은 모델로만 월간 실행한다. 모델을 바꾸면 보정을 다시 한다.
|
||||
|
||||
### 1차 보정 결과 (2026-09-09)
|
||||
|
||||
| 항목 | 값 |
|
||||
|---|---|
|
||||
| 대상 | 뷰성형외과 240건 판정분에서 무작위 30건 |
|
||||
| haewon 라벨 | 29건 (G-08 openai 1건은 판단 보류로 공란) |
|
||||
| sentiment 일치 | **22/29 = 75%** (기준선 80% 미달) |
|
||||
| negative_type 일치 | **측정 불가** (haewon G열 미기입, negative 라벨 2건 포함) |
|
||||
|
||||
불일치 7건의 갈래는 셋이다. 판정기가 mixed를 남발한 것 3건(A-01·D4-01·D2-01), 평가 축이 있는 목록 포함과 강점 서술을 중립으로 낮춘 것 3건(A-02·C-12·C-08), 항목 나열의 수식어를 긍정으로 올린 것 1건(A-03).
|
||||
|
||||
### 이 보정의 한계
|
||||
|
||||
규칙 R1~R6은 위 30건의 불일치에서 뽑았다. 같은 30건으로 다시 재면 일치율이 오르는 것은 당연하므로, 재판정 후의 일치율을 기준표 확정 근거로 쓰지 않는다. **확정하려면 새로 뽑은 30건으로 2차 보정을 해야 한다.** 1차 보정에 쓴 30건은 2차 표본에서 제외한다.
|
||||
|
||||
negative_type은 라벨이 없어 이번에 재지 못했다. 2차 보정에서는 negative·mixed 라벨에 종류를 반드시 함께 적는다.
|
||||
|
||||
### 재판정 결과 (2026-09-09, R1~R5 적용)
|
||||
|
||||
240건을 gpt-4.1로 다시 판정했다. 실제 비용 $0.79(입력 324,627 · 출력 17,723 토큰).
|
||||
|
||||
| sentiment | v0.1 | v0.2 | 증감 |
|
||||
|---|---|---|---|
|
||||
| positive | 14 | 20 | +6 |
|
||||
| neutral | 57 | 58 | +1 |
|
||||
| mixed | 10 | **0** | -10 |
|
||||
| negative | 3 | 6 | +3 |
|
||||
| not_mentioned | 156 | 156 | 0 |
|
||||
|
||||
**감성 점수 53.6 → 58.3** (언급 84건, 변동 없음). 리포트 `Viewclinic_QB_Sentiment.md` 를 v0.2로 다시 만들었다.
|
||||
|
||||
haewon 라벨 29건 대비 일치율은 75% → 93%(27/29)다. 앞서 적은 대로 **이 값은 확정 근거가 아니다.** 규칙을 이 표본의 불일치에서 뽑았기 때문이다.
|
||||
|
||||
### R4-a·R6 시험 투입과 철회 (같은 날)
|
||||
|
||||
R4 과적용(C-12 perplexity)과 A-03을 고치려고 규칙 두 개를 더 넣고 240건을 다시 판정했다. 결과는 나빠졌다.
|
||||
|
||||
| 회차 | 규칙 | haewon 라벨 대비 (참고값) |
|
||||
|---|---|---|
|
||||
| v0.1 | 기본 정의만 | 75% (22/29) |
|
||||
| v0.2 | R1~R5 | 93% (27/29) |
|
||||
| v0.3 시험 | R1~R5 + R4-a + R6 | **89% (26/29)** |
|
||||
|
||||
- **R4-a**(목록에 있어도 결론에서 대상 병원이 빠지면 positive 아님)는 C-12 perplexity를 고쳤으나 **C-12 openai를 반대로 틀었다.**
|
||||
- **R6**(결론 없는 카탈로그는 neutral)은 목표한 A-03을 고치지 못했고 **A-02 perplexity를 새로 틀었다.**
|
||||
|
||||
규칙을 하나 더할 때마다 하나 고치고 하나 깨진다. n=29에서 75%·93%·89%는 표준오차(약 ±6%p) 안이다. **규칙은 R1~R5에서 멈춘다.** R4-a와 R6은 철회했고, 남은 불일치 2건(C-12 perplexity, A-03)은 사람 판단의 경계로 보고 2차 보정에서 새 표본으로 다시 본다.
|
||||
|
||||
시험분은 폐기했고, 최종 데이터는 v0.2 판정(R1~R5 프롬프트)에 §2-1 탐지기를 다시 적용해 만들었다. 탐지기는 결정론적 후처리라 API 재호출이 필요 없다. 이 회차의 총 API 비용은 $1.70이다(v0.2 $0.79 + R4-a·R6 시험 $0.91).
|
||||
|
||||
### 최종 수치 (v0.3)
|
||||
|
||||
| 항목 | v0.1 | v0.3 |
|
||||
|---|---|---|
|
||||
| 언급 | 84 | **83** |
|
||||
| 감성 점수 | 53.6 | **58.4** |
|
||||
| positive / neutral / mixed / negative | 14 / 57 / 10 / 3 | 20 / 57 / 0 / 6 |
|
||||
| 약한 언급 | 3(오탐 포함) | **1**(G-08 openai) |
|
||||
|
||||
약한 언급 1건을 빼면 언급 82건 · 감성 점수 57.9다. 리포트에 두 값을 함께 적었다.
|
||||
|
||||
**mixed가 240건 전체에서 0건이다.** haewon 라벨에도 mixed가 없었으므로 방향은 맞지만, R2가 과하게 좁혔을 수 있다. 2차 보정 표본에 mixed 후보가 하나도 없으면 판정값 자체를 없앨지 정한다.
|
||||
|
||||
## 6. haewon이 정할 것
|
||||
|
||||
- **부정의 범위**: "후기 광고 논란"처럼 사실이지만 병원에 불리한 언급을 negative로 볼지, 규제 사실 서술은 neutral로 볼지. 초안은 negative다.
|
||||
- **mixed 처리**: 점수에서 25점으로 셀지, 제외할지. 초안은 25점. 1차 보정에서 haewon 라벨에 mixed가 한 건도 없었다(29건 중 0건). R2로 mixed 조건을 좁혔으므로 재판정 후 mixed 건수를 보고 25점 유지 여부를 정한다.
|
||||
- **다른 병원 추천 문맥**: 대상 병원이 언급되지 않고 경쟁 병원만 추천된 답변을 별도 지표(경쟁 노출)로 셀지.
|
||||
- **R5와 R6의 경계** (v0.2 추가): 병원 홍보 문구가 인용된 답변에서 A-02는 positive, A-03은 neutral로 갈렸다. 질문 성격으로 가를지(평가 요청 대 사실 조회), 답변에 종합 서술이 있는지로 가를지 확정이 필요하다.
|
||||
- **약한 언급 처리**: §2-1 탐지기로 오탐을 걷어내니 진짜 약한 언급은 **1건**(G-08 openai)뿐이다. "View", "Wooa", "Wish PS" 가 병원 목록으로 나열된 영어권 커뮤니티 인용이다. 현재는 **언급으로 세고 표시**해 두었고, 리포트에 제외 점수(57.9)를 함께 적는다. 1건이라 어느 쪽으로 정해도 점수는 0.5점 움직인다. haewon 확정 필요.
|
||||
- **mixed 값 유지 여부**: R2 적용 후 240건 중 mixed가 0건이다. 판정값에서 mixed를 빼고 positive/neutral/negative 셋으로 갈지 정한다.
|
||||
- **R4-a·R6 재시도 여부**: 이번에 철회했다(§5). 2차 보정 표본에서 같은 유형이 반복되면 그때 다시 본다.
|
||||
@ -1,61 +0,0 @@
|
||||
# 마이그레이션 파일과 운영 DB 대조표
|
||||
|
||||
작성 2026-09-14 · 읽기 전용 조회 결과입니다. 이 문서를 만들며 DB 를 바꾸지 않았습니다.
|
||||
|
||||
**정본은 마이그레이션 파일입니다**(haewon 결정 2026-09-14).
|
||||
|
||||
## 왜 필요한가
|
||||
|
||||
`supabase migration list` 기준으로 로컬 마이그레이션 10개 중 **원격 이력에 기록된 것은 3개**뿐입니다.
|
||||
나머지는 SQL 편집기로 직접 만들어 이력에 남지 않았습니다.
|
||||
이 상태에서 `migration repair` 로 "적용됨"을 기록하면 파일과 DB 가 실제로는 다른데 같다고 기록돼
|
||||
`db diff`·`db pull` 이 어긋난 결과를 냅니다. 그래서 repair 전에 무엇이 다른지 먼저 확인했습니다.
|
||||
|
||||
## 대조 결과
|
||||
|
||||
| 테이블 | 상태 | 파일에만 있음 (DB 에 추가 필요) | DB 에만 있음 (파일에 반영 필요) | 비고 |
|
||||
|---|---|---|---|---|
|
||||
| `analysis_runs` | 일치 | - | - | - |
|
||||
| `channel_configs` | 일치 | - | - | - |
|
||||
| `channel_latest` | 뷰 | - | captured_at, channel, clinic_id, followers, handle, health_score, health_status, posts, rating, rating_scale, reviews, screenshot_url, total_views | VIEW 입니다. 마이그레이션 파일에 정의가 없습니다 |
|
||||
| `channel_snapshots` | 일치 | - | - | - |
|
||||
| `channel_weekly_delta` | 뷰 | - | captured_at, channel, clinic_id, current_followers, current_reviews, followers_change_pct, prev_followers, prev_reviews | VIEW 입니다. 마이그레이션 파일에 정의가 없습니다 |
|
||||
| `clinic_registry` | 다름 | is_active, verified_at, verified_by | notes | - |
|
||||
| `clinics` | 일치 | - | - | - |
|
||||
| `content_performance` | 일치 | - | - | - |
|
||||
| `content_plans` | 일치 | - | - | - |
|
||||
| `discovery_leads` | 일치 | - | - | - |
|
||||
| `marketing_reports` | 일치 | - | - | - |
|
||||
| `performance_metrics` | 일치 | - | - | - |
|
||||
| `scrape_results` | 일치 | - | - | - |
|
||||
| `screenshots` | 일치 | - | - | - |
|
||||
| `strategy_adjustments` | 일치 | - | - | - |
|
||||
| `supporter_builds` | 일치 | - | - | - |
|
||||
| `supporter_inputs` | 일치 | - | - | - |
|
||||
|
||||
표 17개 중 **15개가 정확히 일치**합니다. 손볼 곳은 `clinic_registry` 하나와 정의가 빠진 뷰 2개입니다.
|
||||
|
||||
## 확인한 것
|
||||
|
||||
- `channel_latest`·`channel_weekly_delta` 는 **뷰(VIEW)** 입니다. 표가 어긋난 것이 아니라 뷰 정의가 마이그레이션 파일에 없습니다.
|
||||
- `clinic_registry` 의 `notes` 는 **74곳 전부에 값이 있어 실제로 쓰이는 컬럼**입니다. 지우면 안 되고 파일에 반영해야 합니다.
|
||||
- `is_active`·`verified_at`·`verified_by` 는 파일에만 있습니다. 코드가 쓰는지 확인한 뒤 추가할지 파일에서 뺄지 정합니다.
|
||||
|
||||
## 다음에 할 일 (순서를 바꾸면 안 됩니다)
|
||||
|
||||
1. `clinic_registry` 의 `is_active`·`verified_at`·`verified_by` 를 운영 DB 에 추가하거나, 쓰지 않으면 마이그레이션 파일에서 뺍니다.
|
||||
2. `notes` 컬럼과 뷰 2개의 정의를 마이그레이션 파일에 추가합니다.
|
||||
3. 둘이 맞은 뒤에 `supabase migration repair --status applied <version>` 으로 이력을 맞춥니다.
|
||||
4. 그다음부터 `supabase db push` 가 새 마이그레이션만 적용합니다.
|
||||
|
||||
3번을 먼저 하면 어긋난 상태가 기록으로 굳습니다.
|
||||
|
||||
## 대조 방법
|
||||
|
||||
```
|
||||
supabase db query --linked -o csv \
|
||||
"select table_name, column_name from information_schema.columns where table_schema='public' order by table_name, ordinal_position;"
|
||||
```
|
||||
|
||||
파일 쪽은 `CREATE TABLE` 과 `ALTER TABLE ... ADD COLUMN` 을 파싱해 모았습니다.
|
||||
제약·인덱스·함수·트리거·RLS 는 이 대조에 넣지 않았습니다. 컬럼만 봅니다.
|
||||
@ -1,172 +0,0 @@
|
||||
# 설계: 신청 접수 알림과 완료 안내 메일 v0.1
|
||||
|
||||
작성 2026-09-18 · 대상 AI Discovery 랜딩(`/discovery`) 신청 흐름
|
||||
|
||||
---
|
||||
|
||||
## 1. 지금 무엇이 비어 있나
|
||||
|
||||
랜딩 신청 모달은 이렇게 말합니다.
|
||||
|
||||
> 입력하신 URL을 실측 진단한 뒤, 남겨주신 연락처로 리포트를 안내드립니다.
|
||||
|
||||
**보내는 코드가 없습니다.** `workers/`, `scripts/`, `supabase/functions/` 전체에 메일 발송이 없습니다. 유일한 `mailto:`는 저장 실패 시 사람이 직접 메일을 쓰라고 띄우는 폴백입니다(`DiscoveryLeadModal.tsx:82`).
|
||||
|
||||
그래서 지금은 이렇게 됩니다.
|
||||
|
||||
```
|
||||
신청 → discovery_leads (status: new)
|
||||
→ supporter_builds 큐 → 워커가 빌드 → preview
|
||||
→ 여기서 끝. 신청자에게도, 운영자에게도 아무 연락이 없음
|
||||
```
|
||||
|
||||
2026-09-18 JK성형외과 신청이 정상 접수되고 10분 만에 빌드까지 끝났지만, 신청자가 직접 확인하기 전까지 아무도 몰랐습니다.
|
||||
|
||||
**문제는 두 갈래입니다.**
|
||||
|
||||
| | 문제 |
|
||||
|---|---|
|
||||
| 운영자 | 신청이 들어온 사실을 알 방법이 없습니다. `discovery_leads`는 anon insert만 열려 있고 조회는 service_role 전용이며, 폴러는 `supporter_builds`만 봅니다 |
|
||||
| 신청자 | 화면이 약속한 안내를 받지 못합니다 |
|
||||
|
||||
---
|
||||
|
||||
## 2. 설계 원칙
|
||||
|
||||
**원칙 1. 자동 발송은 접수 알림까지만 한다.**
|
||||
|
||||
완료 안내를 자동으로 보내면 사람 검토를 건너뛴 결과물 링크가 나갑니다. 이번 JK 건에서 첫 화면 대표 사진이 사람 확인이 필요한 사진이었던 것을 생각하면, 자동 발송이 아니었던 것이 결과적으로 안전했습니다. 완료 안내는 **사람이 보내기를 누를 때** 나갑니다.
|
||||
|
||||
**원칙 2. 화면의 약속과 코드를 맞춘다.**
|
||||
|
||||
지금 모달 문구는 자동으로 갈 것처럼 읽힙니다. 실제 동작에 맞게 문구를 고치거나, 동작을 문구에 맞추거나 둘 중 하나를 해야 합니다. 이 설계는 후자에 가깝되 검토 단계를 명시하는 쪽입니다.
|
||||
|
||||
**원칙 3. "리포트"라는 말을 정확히 쓴다.**
|
||||
|
||||
현재 파이프라인이 만드는 것은 **서포터즈 사이트**입니다. 랜딩이 말하는 AEO/GEO 36항목 진단 리포트(`/discovery/viewclinic` 형태)는 이 빌드에서 생성되지 않습니다. 신청 한 건이 서로 다른 두 산출물로 갈라져 있어, 메일 문안에서 무엇을 보내는지 분명히 해야 합니다.
|
||||
|
||||
---
|
||||
|
||||
## 3. 보내는 메일 3종
|
||||
|
||||
### 3-1. 접수 확인 (신청자에게, 자동)
|
||||
|
||||
- **시점**: `discovery_leads` insert 직후
|
||||
- **받는 사람**: 신청자가 남긴 연락처
|
||||
- **내용**: 접수 사실, 어떤 URL로 접수됐는지, 언제쯤 연락이 가는지, 문의처(`o2oteam@o2o.kr`)
|
||||
- **보내지 않는 것**: 결과 링크, 진행률
|
||||
|
||||
### 3-2. 신규 신청 알림 (운영자에게, 자동)
|
||||
|
||||
- **시점**: 3-1과 동시
|
||||
- **받는 사람**: 운영 담당 주소 1개
|
||||
- **내용**: 병원명·URL·연락처, 빌드 큐 등록 여부, 확인 화면 링크(`/images/<clinic_id>`, `/supporters/<clinic_id>`)
|
||||
- **목적**: 신청을 놓치지 않는 최소 장치. 이것만 있어도 지금 상태보다 낫습니다
|
||||
|
||||
### 3-3. 완료 안내 (신청자에게, 사람이 보냄)
|
||||
|
||||
- **시점**: 운영자가 이미지 검토와 결과 확인을 마치고 **보내기를 눌렀을 때**
|
||||
- **받는 사람**: 신청자
|
||||
- **내용**: 무엇을 만들었는지, 미리보기 링크(noindex), 병원이 확인해야 할 것 두 가지(`/images/<id>` 사진 확인, `/supporters/<id>` 확인 항목), 색인 전환은 법무 확인 뒤라는 안내
|
||||
- **전제**: 발송 전에 사진 검토가 끝나 있어야 합니다
|
||||
|
||||
---
|
||||
|
||||
## 4. 무엇으로 보내나
|
||||
|
||||
### 4-1. 발송 수단
|
||||
|
||||
**현재 메일 발송 키가 하나도 없습니다.** `.env`에 Resend·SendGrid·SES·SMTP 어느 것도 없어 계정부터 필요합니다.
|
||||
|
||||
| 후보 | 장점 | 단점 |
|
||||
|---|---|---|
|
||||
| **Resend** (추천) | Edge Function에서 fetch 한 번. 도메인 인증이 단순. 무료 한도로 이 규모는 충분 | 계정·도메인 인증 필요 |
|
||||
| SendGrid | 전달률 기록이 길다 | 설정 항목이 많다 |
|
||||
| AWS SES | 단가가 가장 낮다 | 샌드박스 해제 심사가 필요 |
|
||||
|
||||
발신 도메인은 `o2o.kr` 계열을 쓰고, 신규 도메인은 초기 전달률이 낮으므로 SPF·DKIM·DMARC를 먼저 맞춥니다.
|
||||
|
||||
### 4-2. 발송 위치
|
||||
|
||||
**Supabase Edge Function `notify-lead` 를 새로 만듭니다.**
|
||||
|
||||
프런트에서 직접 부르지 않습니다. 프런트가 부르면 anon 키로 메일을 쏠 수 있게 되어 스팸 경로가 됩니다. DB 변경을 신호로 삼습니다.
|
||||
|
||||
```
|
||||
discovery_leads INSERT
|
||||
→ Postgres 트리거 (pg_net)
|
||||
→ Edge Function notify-lead (service_role 검증)
|
||||
→ Resend 호출 2건 (신청자 접수 확인 + 운영자 알림)
|
||||
→ email_log 기록
|
||||
```
|
||||
|
||||
완료 안내(3-3)는 같은 함수의 다른 진입점으로 두고, 운영자 화면의 버튼에서 호출합니다.
|
||||
|
||||
---
|
||||
|
||||
## 5. 필요한 DB 변경
|
||||
|
||||
### 5-1. `email_log` 신설
|
||||
|
||||
무엇을 언제 누구에게 보냈는지 남깁니다. 중복 발송을 막고, 안 갔다는 문의에 답할 근거가 됩니다.
|
||||
|
||||
| 열 | 용도 |
|
||||
|---|---|
|
||||
| `id` | |
|
||||
| `kind` | `lead_ack` · `lead_notify` · `build_done` |
|
||||
| `lead_id` / `clinic_id` | 대상 |
|
||||
| `to_addr` | 받는 주소 |
|
||||
| `status` | `sent` · `failed` |
|
||||
| `provider_id` | 발송 서비스가 준 id. 전달 추적용 |
|
||||
| `error` | 실패 사유 |
|
||||
| `created_at` | |
|
||||
|
||||
중복 발송 방지는 `(kind, lead_id)` 유니크로 겁니다.
|
||||
|
||||
### 5-2. `discovery_leads.status` 를 실제로 쓴다
|
||||
|
||||
지금은 모든 행이 `new`로 고정입니다. 흐름을 담도록 값을 정합니다.
|
||||
|
||||
```
|
||||
new → notified(접수 메일 발송) → building → review(사람 검토 중)
|
||||
→ delivered(완료 안내 발송) → closed
|
||||
```
|
||||
|
||||
### 5-3. 연락처가 이메일이 아닐 수 있다
|
||||
|
||||
모달은 "이메일 또는 전화번호"를 받습니다. 전화번호면 메일을 보낼 수 없습니다.
|
||||
|
||||
- 저장 시 형식을 판별해 `contact_kind`(`email` · `phone`)를 같이 남깁니다
|
||||
- `phone`이면 접수 메일을 건너뛰고 운영자 알림에 **전화로 연락 필요**를 표시합니다
|
||||
|
||||
---
|
||||
|
||||
## 6. 화면에서 고칠 것
|
||||
|
||||
| 위치 | 지금 | 바꿀 안 |
|
||||
|---|---|---|
|
||||
| 신청 모달 완료 문구 | "실측 진단한 뒤, 남겨주신 연락처로 리포트를 안내드립니다" | "접수됐습니다. 확인 메일을 보내 드렸습니다. 결과가 준비되면 남겨주신 연락처로 안내드립니다" |
|
||||
| 운영자 화면 | 없음 | 완료 안내 발송 버튼. 사진 검토가 끝나지 않았으면 버튼을 잠그고 이유를 표시 |
|
||||
|
||||
---
|
||||
|
||||
## 7. 만들 순서
|
||||
|
||||
| 순서 | 할 일 | 비고 |
|
||||
|---|---|---|
|
||||
| 1 | 발송 서비스 계정과 도메인 인증 | **haewon 결정 필요.** 이것 없이는 아무것도 못 보냅니다 |
|
||||
| 2 | `email_log` 테이블, `contact_kind` 열 추가 | |
|
||||
| 3 | Edge Function `notify-lead` + 트리거 | 3-1·3-2 자동 발송 |
|
||||
| 4 | 모달 문구 수정 | 1~3과 무관하게 지금 할 수 있습니다 |
|
||||
| 5 | 완료 안내 발송 버튼 | 사진 검토 완료 조건과 묶습니다 |
|
||||
|
||||
**1번이 없으면 2~3번이 무의미하므로, 4번을 먼저 넣어 화면과 코드의 어긋남부터 없애는 방법도 있습니다.**
|
||||
|
||||
---
|
||||
|
||||
## 8. haewon 결정이 필요한 것
|
||||
|
||||
1. **발송 서비스**: Resend를 추천합니다. 계정과 발신 도메인(`o2o.kr` 하위 무엇으로 할지)이 필요합니다
|
||||
2. **운영자 알림 수신 주소**: 개인 주소인지 공용 주소인지
|
||||
3. **완료 안내를 사람이 보내는 방식으로 할지**: 자동 발송으로 하면 사진 검토를 건너뛴 링크가 나갈 수 있습니다. 이 설계는 사람이 보내는 쪽을 전제합니다
|
||||
4. **"리포트"의 정의**: 완료 안내로 보내는 것이 서포터즈 사이트인지, AEO/GEO 진단 리포트인지, 둘 다인지. 지금 파이프라인은 앞의 것만 만듭니다
|
||||
@ -1,171 +0,0 @@
|
||||
# 핸드오버 · 2026-09-18 (금)
|
||||
|
||||
다음 작업은 2026-09-19(토) 또는 다음 주. 이 문서 하나만 읽으면 이어서 할 수 있게 적는다.
|
||||
|
||||
---
|
||||
|
||||
## 0. 오늘 한 일 한 줄
|
||||
|
||||
랜딩에서 들어온 JK성형외과 신청을 자동 빌드로 완주시키고, 그 과정에서 드러난 파이프라인 결함 6건을 고쳤다. 그리고 진단 리포트에서 사이트 빌드까지 이어지는 제품 화면을 새로 만들었다.
|
||||
|
||||
---
|
||||
|
||||
## 1. 지금 상태
|
||||
|
||||
### 저장소 두 곳, 커밋 완료
|
||||
|
||||
**Productize-2** (`main`) · 프런트엔드
|
||||
```
|
||||
f38c0f3 feat(front): 진단 요약과 보완 계획을 보여 준 뒤 빌드하게 한다
|
||||
14c4b82 refactor(design): 요청문과 설명문을 텍스트 역할 토큰으로 가른다
|
||||
be1ea72 feat(front): 진단 리포트에서 사이트 빌드로 이어지는 화면
|
||||
74c70d6 docs(design): 신청 접수 알림과 완료 안내 메일 설계 v0.1
|
||||
5cf28d9 feat(front): 병원이 사이트에 실을 사진을 한 장씩 고르는 화면
|
||||
```
|
||||
|
||||
**Productize** (`feature/supporters-autobuild`) · 파이프라인
|
||||
```
|
||||
1eb166e fix(worker): 병원 입력을 큐 빌드에서도 반영하고, 이미지 확인 결과를 사이트에 적용한다
|
||||
fa48d43 fix(supporters): 의료진 목록 페이지를 사람별로 나누고 지번 주소도 좌표로 바꾼다
|
||||
```
|
||||
|
||||
**푸시하지 않았다.** 두 저장소 모두 로컬에만 있다.
|
||||
|
||||
`f38c0f3` 은 haewon 육안 확인 전에 커밋했다. 퇴근 시점에 작업 트리를 비워 두려고 먼저 넣은 것이라, 마음에 안 들면 `git reset --soft HEAD~1` 로 되돌리면 된다.
|
||||
|
||||
### 추적하지 않는 파일 2개
|
||||
|
||||
`scripts/out/naver_briefing_summary.json`, `scripts/out/sentiment_summary.json` 는 09-15 측정 산출물이고 오늘 세션 것이 아니라 그대로 뒀다. 주간보고가 이 파일을 근거로 인용하므로 보고서를 공유할 계획이면 같이 커밋해야 한다.
|
||||
|
||||
### 배포
|
||||
|
||||
| 대상 | 주소 | 상태 |
|
||||
|---|---|---|
|
||||
| JK성형외과 서포터즈 | https://supporters-jkplastic.vercel.app | 배포됨 · noindex · 의료진 9명·주변 안내 123곳 반영 |
|
||||
| 프런트엔드 | 배포 안 함 | 오늘 만든 화면은 로컬에만 있다 |
|
||||
|
||||
---
|
||||
|
||||
## 2. 오늘 고친 파이프라인 결함 6건
|
||||
|
||||
| # | 결함 | 상태 |
|
||||
|---|---|---|
|
||||
| 1 | 의료진 목록 페이지를 사람별로 못 나눔. 9명이 "과전문의"라는 없는 사람 1명으로 합쳐짐 | 고침 |
|
||||
| 2 | 지번 주소를 좌표로 못 바꿔 주변 안내 전체가 빔 | 고침 |
|
||||
| 3 | 글이 예전 의료진 id를 들고 있으면 astro build 중단 | 고침 |
|
||||
| 4 | `--deploy` 없는 부분 재실행이 `preview_url` 을 지움 | 고침 |
|
||||
| 5 | 큐 워커가 `--from-supabase` 를 안 넘겨 **병원 입력이 하나도 반영되지 않던 것** | 고침 |
|
||||
| 6 | 이미지 수집 개수 중복 계산 (36장을 51장으로 보고) | 고침 |
|
||||
|
||||
**수정 효과 (JK 재실행 실측)**
|
||||
|
||||
| 항목 | 전 | 후 |
|
||||
|---|---|---|
|
||||
| 의료진 | 1명 (가짜 이름) | 9명 (실명·직함·약력 분리) |
|
||||
| 수집 뉴스 | 958건 | 1,165건 (기고·인터뷰 217 → 387) |
|
||||
| 주변 안내 | 0곳 | 국문 123곳 · 영문 108곳 |
|
||||
|
||||
---
|
||||
|
||||
## 3. 오늘 만든 화면
|
||||
|
||||
```
|
||||
/discovery/:id 진단 리포트 → [이대로 빌드하기]
|
||||
/build/:id 빌드 진행 (5단계, supporter_builds 폴링)
|
||||
/images/:id 사진 확인 (한 장씩 사용/빼기)
|
||||
/supporters/:id 확인 항목 입력 (기존)
|
||||
```
|
||||
|
||||
**새 파일**
|
||||
```
|
||||
src/pages/SiteBuildPage.tsx 빌드 진행 화면
|
||||
src/pages/ImageReviewPage.tsx 사진 확인 화면
|
||||
src/components/discovery/ImageReviewPanel.tsx 사진 확인 본체
|
||||
src/components/discovery/BuildPlanSection.tsx 진단 요약 → 보완 계획 → 빌드
|
||||
src/components/ClinicNav.tsx 4단계 절차 상단 메뉴 + 이전 단계 플로팅 버튼
|
||||
src/lib/buildPhases.ts 워커 17단계 → 고객용 5묶음
|
||||
src/lib/buildCoverage.ts 빌드가 보완하는 카테고리 선정
|
||||
```
|
||||
|
||||
**설계상 지켜야 할 것 두 가지**
|
||||
|
||||
1. **빌드는 병원 홈페이지를 고치지 않는다.** 진단의 실행 항목은 단 하나도 빌드로 해결되지 않는다. 화면에서 "실행 항목 N건 해결"로 쓰면 지키지 못할 약속이 된다. 오늘 한 번 그렇게 만들었다가 고쳤다.
|
||||
2. **"결과"라는 말을 쓰지 않는다.** 빌드가 끝나도 사진 확인과 확인 항목이 남아 최종이 아니다.
|
||||
|
||||
**텍스트 역할 토큰** (`src/index.css`)
|
||||
```
|
||||
.ui-ask 요청문 (17px·600·인디고) · .ui-ask-block 은 왼쪽 규칙선
|
||||
.ui-body 설명 본문 (16px·slate-600)
|
||||
.ui-note 보조·출처 (14px·slate-400)
|
||||
.on-dark / .on-light 다크 섹션과 그 안의 흰 카드
|
||||
.btn-on-dark 다크 섹션 위 주 버튼 (기본 CTA 그라디언트는 흰 배경 전용)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. 바로 이어서 할 수 있는 것
|
||||
|
||||
### 4-1. haewon 육안 확인 (최우선)
|
||||
|
||||
`f38c0f3` 의 화면을 아직 안 보셨다. 확인할 것:
|
||||
|
||||
- `/discovery/viewclinic` 하단 **What We Will Fix** 섹션의 문구와 버튼 대비
|
||||
- 네 화면 오가며 **이전 단계 플로팅 버튼** (왼쪽 아래)
|
||||
- `/images/jkplastic` 썸네일 줄
|
||||
|
||||
```bash
|
||||
cd ~/orca/workspaces/INFINITH/Productize-2 && npm run dev # 포트 3000 사용 중이면 자동으로 다음 포트
|
||||
```
|
||||
|
||||
### 4-2. 분석 리포트의 남은 요소 흡수 (오늘 하던 것)
|
||||
|
||||
haewon이 고른 방향은 **"분석 리포트의 좋은 부분을 진단으로 흡수"** 였다. 채널 점수 링·로드맵 타임라인·KPI 세 가지 중 아직 하나도 안 옮겼다. 중간에 "OK 버튼" 이야기로 방향이 틀어져 `BuildPlanSection` 을 먼저 만들었다.
|
||||
|
||||
| 요소 | 기존 컴포넌트 | 데이터 |
|
||||
|---|---|---|
|
||||
| 채널 건강도 링 | `src/components/report/ChannelOverview.tsx` | `supporter_builds.report.channels` 에 실제 발견 채널이 있다 |
|
||||
| 로드맵 타임라인 | `src/components/report/RoadmapTimeline.tsx` | `result.actions` 의 P0/P1/P2 + effort 로 시간축을 만들 수 있다 |
|
||||
| KPI 대시보드 | `src/components/report/KPIDashboard.tsx` | **데이터 없음.** 목표치를 지어내면 안 된다. 재측정 기준으로 바꾸는 편이 맞다 |
|
||||
|
||||
로드맵이 가장 쉽고 값이 크다. 데이터가 이미 있고 진단의 Action Plan에 시간축만 없다.
|
||||
|
||||
### 4-3. 분석 리포트 자체의 결함 (옮기든 안 옮기든 남는 문제)
|
||||
|
||||
`/report/:id` 를 바노바기 리포트로 열어 확인한 것:
|
||||
- 히어로가 비어 있다. 첫 화면에 그라디언트만 크게 깔리고 제목·병원명이 없다
|
||||
- `데이터 없음 (NaN년)`. 개원 연도 계산이 깨져 NaN이 화면에 나온다
|
||||
- 섹션 사이 여백이 화면 하나 가까이 된다
|
||||
|
||||
---
|
||||
|
||||
## 5. 결정 대기 (haewon)
|
||||
|
||||
### 5-1. 메일 (설계 끝, 결정 필요)
|
||||
|
||||
`docs/DESIGN_신청알림메일_v0.1.md` §8
|
||||
|
||||
1. **발송 서비스**: Resend 추천. 계정과 발신 도메인(`o2o.kr` 하위 무엇으로)
|
||||
2. **운영자 알림 수신 주소**: 개인인지 공용인지
|
||||
3. **완료 안내를 사람이 보내는 방식으로 확정할지**
|
||||
4. **"리포트"의 정의**: 가장 큰 건. 랜딩은 AEO/GEO 진단 리포트를 약속하는데 파이프라인은 서포터즈 사이트를 만든다. 메일 문안을 쓰려면 이걸 먼저 정해야 한다
|
||||
|
||||
### 5-2. 진단 데이터가 3곳뿐
|
||||
|
||||
`DISCOVERY_RESULTS` 에 viewclinic·taeha·wonjin 만 있다. JK처럼 빌드만 한 병원은 `/discovery/:id` 에서 **No Audit Yet** 화면을 본다(오늘 만들었다). 진단은 36항목 실측이라 자동으로 안 따라온다. 자동화 범위를 어디까지 잡을지 별도 과제.
|
||||
|
||||
### 5-3. 기타
|
||||
|
||||
- `clinicDoctors` 4명 고정. JK는 9명인데 소개 페이지에 4명만 나온다
|
||||
- planner가 주변 안내 좌표를 받고도 범용 기본표를 쓴다
|
||||
- 뷰 의료진 id 두 벌 (`dr-cho` 수작업 10개 vs `dr-chojinwoo` 자동 27개). 뷰 재빌드 때 한쪽으로 모아야 한다
|
||||
- `check.mjs --update-layout` 으로 레이아웃 기준선 생성
|
||||
|
||||
---
|
||||
|
||||
## 6. 알아 두면 시간 아끼는 것
|
||||
|
||||
- **빌드는 백그라운드로 돌리고 다른 작업을 같이 한다.** `nohup node workers/supporters-build/run.mjs ... &` 로 띄우고 로그를 감시한다. 오늘 내내 그렇게 했다.
|
||||
- **같은 병원 빌드를 동시에 두 개 돌리면 안 된다.** 작업 폴더(`~/supporters-builds/<clinic>-<id>/`)를 공유해 서로 덮어쓴다.
|
||||
- **부분 재실행은 `--from <단계>`.** 글은 `--regenerate` 없으면 보존되어 생성 비용이 0이다.
|
||||
- **워커는 Productize 워크트리에서 돈다.** Productize-2 가 아니다. launchd 폴러 `com.infinith.supporters-poll`, 로그 `~/supporters-builds/poll.log`.
|
||||
- **주간보고는 gitignore 처리했다** (`docs/*주간보고*`). 개인 문서라 저장소에 올리지 않는다.
|
||||
@ -1,286 +0,0 @@
|
||||
# 세션 핸드오버 · 2026-09-12(토) · INFINITH AI Discovery Product 빌드
|
||||
|
||||
작성 2026-09-11 · 갱신 2026-09-12(토) · 작성자 Claude (haewon 세션) · 상태: §1 확인 완료, §4-1 완료
|
||||
하위 문서: 서포터즈 사이트 빌드 절차 `~/orca/workspaces/INFINITH/Productize/docs/NEXT_SESSION_2026-09-12_PRODUCT_BUILD.md`, 자동 빌드 상세 `…/NEXT_SESSION_SUPPORTERS_AUTOBUILD_v3.md`, 자동 채점 `docs/AI_DISCOVERY_BUILD2_HANDOVER.md`, 회고 `docs/INFINITH_AI_Discovery_Retrospective_2026-09-08.md`.
|
||||
|
||||
## 0. 세션 시작 시 붙여 넣을 프롬프트
|
||||
|
||||
```
|
||||
INFINITH AI Discovery Product 빌드 세션. 제품은 "병원 URL 하나 → AI 답변 준비도 진단(AEO/GEO v2) → 서포터즈 사이트 자동 빌드(글·영상·뉴스룸·주변 안내·회복 일정 플래너·오시는 길) → 병원 확인·재빌드 → 측정(질문뱅크·감성·GA4·크롤러 로그)" 파이프라인이다.
|
||||
코드 위치: 2026-09-12 에 브랜치 3개를 main 으로 합쳤다. 작업은 워크트리 ~/orca/workspaces/INFINITH/Productize-2 (브랜치 main) 에서 한다. 프로덕션은 main → infinith-demo.vercel.app.
|
||||
메인 체크아웃 ~/Claude/Agentic Marketing/INFINITH 는 브랜치 feature/ai-sentiment-rubric 이고 다른 작업의 미커밋 변경(src/ 등 39줄, docs 폴더 재정리)이 있으니 건드리지 않는다. Supabase CLI 링크는 그쪽에만 있어 DB 조회는 거기서 supabase db query --linked 로 한다.
|
||||
먼저 docs/NEXT_SESSION_2026-09-12_AI_DISCOVERY_PRODUCT.md 를 읽는다(§5-4 도메인·레지스트리, §8 인사이트 포함). 시작 절차: (1) git status 와 .vercel 연결을 확인하고 다른 세션이 열려 있지 않은지 haewon 에게 묻는다 (2) §1 표의 "확인" 칸을 git log·Supabase 로 실제 확인한다 (3) §5 결정 대기의 답을 받는다 (4) §4 순서대로 진행한다. 한 세션은 한 저장소·한 배포 대상만 다룬다.
|
||||
주소 상태: webforai.kr 등록·위임·연결 모두 끝났다(2026-09-14). webforai.kr 은 AI Discovery 랜딩(신설 프로젝트 infinith-discovery), viewpl·wonjin·oracle.webforai.kr 은 각 서포터즈 사이트다. 세 곳 모두 noindex 유지. 색인 허용(--indexable)은 법무 3건(외국인환자 유치업, 의료광고 사전심의, 지원 관계 고지)이 끝나기 전에는 붙이지 않는다. hosting.kr 의 자동연장·도메인 잠금은 아직 꺼져 있어 켜야 한다.
|
||||
원칙: 근거 없는 사실은 생성하지 않는다("확인 대기"). 사람 게이트(의학 검토·게시 승인·이미지 육안·채널 확인·구조 기준선)는 자동화하지 않는다. 언급률·점수는 "스키마를 넣으면 인용된다"고 약속하지 않는다(정직성 규약).
|
||||
고객 화면에 운영자 어휘 금지. 존댓말, 엠대시 금지(커밋 메시지 포함), 은유 금지, 흰 배경 위 연보라 텍스트 금지(인디고 #1D0024). INFINITH 리포트 PDF 는 PPTX 덱(infinith-deck 스킬)으로 만든 뒤 변환한다.
|
||||
클라이언트 격리: INFINITH 문서·코드에 다른 서비스 이름을 쓰지 않는다. 산출 전 grep.
|
||||
머지 직전에 브랜치 끝을 다시 읽는다. 세션 시작 때 읽은 커밋 값은 머지 시점에 낡아 있을 수 있다. 머지 뒤 git rev-list --count main..<branch> 가 0 인지 확인하고, 통합 뒤 페이지 목록을 이전 배포와 대조한다.
|
||||
운영 DB 에 db push 를 쓰지 않는다. 마이그레이션 이력이 실제와 어긋나 있다(docs/DB_SCHEMA_DIVERGENCE_2026-09-14.md). 새 마이그레이션은 supabase db query --linked --file 로 그 파일만 적용한다.
|
||||
```
|
||||
|
||||
## 1. 제품 여정과 현재 상태
|
||||
|
||||
> **2026-09-12 확인 결과.** 아래 표에서 "확인"으로 남겨 둔 두 항목은 이미 해결된 상태였다. 다음 세션은 이 전제로 시작한다.
|
||||
>
|
||||
> | 항목 | 이전 기재 | 실제 | 근거 |
|
||||
> |---|---|---|---|
|
||||
> | 랜딩 리드 CTA | 미머지 | **main 에 있음.** `feature/discovery-url-cta` 는 main 대비 ahead 0 / behind 22 인 낡은 포인터라 머지 대상이 아니다 | `DiscoveryLeadModal.tsx`·`DiscoveryLandingPage.tsx`·`20260831_discovery_leads.sql` 이 main 에 있고 blob 해시가 세 브랜치 동일 |
|
||||
> | AEO/GEO v2 기준표 | main 머지 여부 확인 필요 | **main 에 있음** | `aeoGeoRubricV2.ts`·`discoveryScoreV2.ts` blob 해시 세 브랜치 동일 |
|
||||
> | Supabase 마이그레이션 | 적용 상태 확인 필요 | **전부 적용됨.** `discovery_leads` 0행, `supporter_builds` 2행, `supporter_inputs` 0행 | REST 조회 HTTP 200 |
|
||||
> | `discovery_results` | (§4-3 대상) | **없음(HTTP 404).** §4-3 미착수 확정 | REST 조회 |
|
||||
> | launchd 폴러 | 설치됨 | **로드됨, 마지막 종료 코드 0** | `launchctl list` |
|
||||
> | 배포 | 3곳 | **4곳 모두 200.** 단 `supporters-viewclinic/plan` 은 404(2026-09-07 빌드라 플래너 이전 판) | HTTP 확인 |
|
||||
|
||||
|
||||
| 단계 | 고객이 보는 것 | 구현 | 위치 | 상태 |
|
||||
|---|---|---|---|---|
|
||||
| 1 진입 | `/discovery` 랜딩, "URL 입력으로 시작" | `DiscoveryLandingPage.tsx`, `DiscoveryLeadModal` → Supabase `discovery_leads` | 메인, 브랜치 feature/discovery-url-cta | 프리뷰만. **미머지, 마이그레이션 `20260831_discovery_leads.sql` 적용 여부 확인** |
|
||||
| 2 진단 | `/discovery/:id` 리포트 (AEO 100 + GEO 100, v2 기준표), 덱·PDF | `scripts/audit_aeo_geo.py`(auto 22항목) → `src/data/discovery_<id>.ts` → `DiscoveryReportPage.tsx`; `scripts/build_discovery_report.ts` → HTML/PDF; 덱 빌더 `docs/reports/<clinic>/01_readiness_report/build_*_deck*.py` | 메인 + Productize(v2 기준표 `src/data/aeoGeoRubricV2.ts`, `src/lib/discoveryScoreV2.ts`) | 반자동. 결과가 소스 파일 커밋 방식이라 병원마다 코드 배포가 필요하다. **v2 기준표가 main(프로덕션)에 머지됐는지 확인** |
|
||||
| 3 서포터즈 사이트 | `supporters-<clinic>.vercel.app` 미리보기(noindex): 글·영상·뉴스룸·주변 안내 `/stay`·`/en/stay`·방문 안내(지도·길찾기)·회복 일정 플래너 `/plan`·`/en/plan` | 워커 `workers/supporters-build/run.mjs` 15단계(evidence→…→tourism→recovery→planner→…→deploy), 템플릿 `templates/supporters-astro`, 게이트 `scripts/gate/rules.mjs` | Productize | 동작. 뷰(수작업 샘플 `view-supporters-sample`)·원진·오라클 3곳 배포. 병원당 약 $0.6, 30분 |
|
||||
| 4 병원 확인 | `/supporters/:id` 또는 리포트 안 `ClinicInputsPanel`: 채널 확인, 의료진, 검토·승인, 자유 답변 | Supabase `supporter_builds`·`supporter_inputs`, `apply_inputs.mjs`, 재빌드 `run.mjs --from data --from-supabase --build-id` | 메인(UI) + Productize(반영) | 동작하되 **재빌드 자동 트리거 없음**(supporter_inputs insert → 큐 연결 필요) |
|
||||
| 5 운영 | 큐 → 빌드 → 미리보기 링크 | `poll.mjs` + Mac launchd 5분 폴러, `supporter_builds` 큐 | Productize | 파일럿 수준. Mac 이 켜져 있어야 돈다. 실제 랜딩 신청 1건이 큐 → 완주까지 간 적 없음 |
|
||||
| 6 측정 | 질문뱅크 언급률(2엔진), 감성, GA4 AI 유입, AI 크롤러 히트, 공식 vs 서포터즈 재채점 | `scripts/run_question_bank_openai.py`·`merge_qb_results.py`·`sentiment_qb.py`·`ga4_ai_traffic.py`, `data/ai_channels.json`, 리포트 Integrity 절 "측정 가능 범위" | 메인(QB·감성) + Productize(GA4) | 스크립트 단위. 리포트 카드 자동 갱신 없음. GA4 는 GCP 계정 대기 |
|
||||
| 7 계약·가격 | 상담 기반 계약 | 가격 문서 `docs/INFINITH_가격정책_사업부보고서_v1.1.docx`(2026-04, 이전 제품 INSIGHT/INTELLIGENCE 티어), AI Discovery 가격 제안은 `docs/INFINITH_vs_FrostAI_비교_및_가격제안_2026-09-08.html` | 메인 | **AI Discovery 전용 티어 미확정** |
|
||||
|
||||
2026-09-10~11 추가: 회복 일정 플래너 `/plan`(설계 v0.2 §13, 관광공사 데이터 합침), 주변 안내 `/stay`(장소·축제·동선 지도·날씨, 플래너와 별개 페이지로 복원), 오시는 길 지도·길찾기, 전역 언어 전환·영어 홈(뷰), 모바일 헤더, 발행(색인) 단계. 전부 템플릿·워커에 통합됨.
|
||||
|
||||
## 2. 코드·브랜치·배포 지도
|
||||
|
||||
| 무엇 | 어디 | 브랜치 | 배포 |
|
||||
|---|---|---|---|
|
||||
| 제품 프런트(랜딩·리포트·확인 패널) | 메인 `src/` | main(프로덕션), feature/ai-sentiment-rubric(감성·플래너 작업), feature/discovery-url-cta(리드 CTA) | `infinith-demo.vercel.app` (main 에서 `vercel --prod`, Gitea 자동 트리거 없음) |
|
||||
| 채점·질문뱅크·감성·리포트 스크립트 | 메인 `scripts/`, `docs/reports/` | feature/ai-sentiment-rubric | 로컬 실행 |
|
||||
| 워커·템플릿·게이트·GA4 뼈대·v2 기준표 | Productize `workers/`, `supporters/`, `templates/`, `scripts/fetch_medical_tourism.py` | feature/supporters-autobuild | 병원별 `supporters-<clinic>` (워커가 강제 link) |
|
||||
| 뷰 수작업 샘플 | 메인 `supporters/` | feature/ai-sentiment-rubric | `view-supporters-sample` 수동 |
|
||||
| Supabase | `supabase/migrations/` (`supporter_builds`, `supporter_builds_queue`, `discovery_leads`) + Edge Functions(기존 리포트 파이프라인) | 메인 | 적용 상태 확인 필요(CLI 토큰 만료 이력) |
|
||||
| 키 | Productize `.env`(TOUR_API·NAVER·OPENAI·YOUTUBE), 메인 `.env`(진단·QB) | | 회사 계정. 세션 시작 때 `grep -E "^KEY=." .env` 로 값 존재만 확인 |
|
||||
|
||||
**2026-09-12 갱신**: feature/supporters-autobuild 와 feature/ai-sentiment-rubric 을 `feature/product-1.0` 으로 합쳤다. feature/discovery-url-cta 는 main 에 이미 담겨 있어 제외했다.
|
||||
핸드오버가 적지 않은 **네 번째 브랜치 `Productize`** 가 있다. 고유 커밋 1개(랜딩 SSR 프리렌더·메타/JSON-LD·robots/sitemap/llms.txt)를 갖고 main 대비 74 뒤이며 origin 에 올라가 있다. 랜딩의 AI 크롤러 가독성이라 AEO 와 직접 닿는데, 합칠지 버릴지 아직 안 정했다.
|
||||
|
||||
(아래는 머지 전 상태 기록) 세 브랜치는 서로 머지되지 않았다. `supporters/` 폴더가 양쪽에 있고 Base.astro 구현이 다르다(메인: i18n.ts 전역 전환·영어 홈; Productize: langswitch·영어 입구 /en/plan). 플래너·지도·헤더 CSS 는 같다.
|
||||
|
||||
## 3. 지금 되는 것 / 안 되는 것 (한 줄씩)
|
||||
|
||||
되는 것
|
||||
- 병원 이름·URL 로 채널 발견·근거 수집·자동 채점·리포트 HTML/PDF·덱(뷰·원진·오라클·태하 사례).
|
||||
- 워커 한 번으로 서포터즈 사이트 초안 발행(글 6~8편, 영상, 뉴스룸, 주변 안내, 이미지 규칙, 지도, 플래너, 관광공사 데이터). 게이트 41건 테스트.
|
||||
- 병원 확인 패널 입력 → 수동 재빌드.
|
||||
- 질문뱅크 120문항 2엔진 실측, 감성 판정, Before/After 페이지(`scripts/build_before_after.py`).
|
||||
|
||||
안 되는 것
|
||||
- 랜딩 신청 → 큐 → 빌드 → 미리보기 링크 회신이 사람 손 없이 이어지지 않는다(리드 테이블과 큐가 분리, CTA 브랜치 미머지).
|
||||
- 진단 결과가 코드 파일(`discovery_<id>.ts`)이라 병원마다 프런트 배포가 필요하다. 런타임 데이터(Supabase 또는 JSON 스토리지)로 옮겨야 "URL 입력 → 리포트 링크"가 된다.
|
||||
- 측정 지표가 리포트 카드에 자동으로 들어가지 않는다(스크립트 산출물을 사람이 옮긴다).
|
||||
- 워커가 Mac 에 묶여 있다. 규칙표 잠정값·큐레이션 장소·의료진 후보·이미지·채널 12건은 사람 확인 대기.
|
||||
- 색인 작업의 사람 몫(네이버 등록·GSC 소유자·도메인·법무)이 체크리스트 파일로만 나오고, 담당자에게 알림·요청하는 UI 가 아직 없다(§4-9).
|
||||
- AI Discovery 가격·계약 조건 미확정. 랜딩 FAQ 의 해지·환불 항목 삭제 상태.
|
||||
|
||||
## 4. Product 빌드 순서 (권장)
|
||||
|
||||
1. ~~**브랜치 정리**~~ **완료 (2026-09-12, 브랜치 `feature/product-1.0`)**. feature/supporters-autobuild 는 main 의 상위집합이라 fast-forward 했고, feature/ai-sentiment-rubric 머지에서 충돌 8건이 났다. 겹친 22개 파일 중 실제로 다른 것은 9개뿐이었다. feature/discovery-url-cta 는 낡은 포인터라 제외했다.
|
||||
- **정본 결정(haewon)**: `supporters/`(뷰 수작업 샘플)를 정본으로 한다. 핸드오버 초안의 "템플릿을 정본으로" 와 반대 방향이다. `templates/supporters-astro/` 는 `npm run export:template` 로 `supporters/` 에서 생성되는 파생물이므로 구조적으로 맞아떨어진다.
|
||||
- Productize 쪽에만 있던 GA4 태그와 영문 법적 고지 처리를 정본으로 이식했다.
|
||||
2. **리드 → 큐 연결**: `discovery_leads` 마이그레이션 적용 → insert 시 `supporter_builds` 큐에 행 생성(DB 트리거 또는 폴러가 leads 도 읽기) → 워커가 evidence 부터 완주 → 미리보기 URL 을 `supporter_builds.preview_url` 에 기록 → 신청자에게 링크 회신(초기에는 수동 메일, `o2oteam@o2o.kr`).
|
||||
3. **진단 결과 런타임화**: `audit_aeo_geo.py` 출력 JSON 을 Supabase(`discovery_results`) 또는 스토리지에 저장하고 `/discovery/:id` 가 런타임에 읽는다. 기존 `DISCOVERY_RESULTS` 레지스트리는 샘플(뷰) 전용으로 남긴다. 사람 판정(v2Results 4항목)은 확인 패널에서 입력.
|
||||
4. **재빌드 자동화**: `supporter_inputs` insert → 큐 → `run.mjs --from data --from-supabase`. 색인 허용 빌드(`PUBLIC_INDEXABLE=true`)는 `approvedAt` 있는 글만 낸다는 규칙 유지.
|
||||
5. **측정 카드**: 질문뱅크·감성·GA4·크롤러 히트 결과를 `discovery_results` 에 붙이고 리포트 Integrity 절과 Before/After 에 자동 표시. 언급률은 신뢰구간과 함께.
|
||||
6. **운영 이관**: Mac launchd 폴러 → Trigger.dev(또는 동등)로. 키·비용 로그(`status.json` costUsd)·실패 알림.
|
||||
7. **가격·계약**: AI Discovery 티어(진단만 / 진단+서포터즈 / +측정·관리) 확정, 랜딩 FAQ·계약서·해지 조건. FrostAI 비교 문서의 제안을 출발점으로.
|
||||
8. **다음 병원**: 톡스앤필·차앤박(피부과), 레지스트리 73곳 중 회귀 병원. 한 화면 비교 산출물은 만들지 않는다.
|
||||
9. **발행(색인) 단계**: "서포터즈 사이트 완료"의 정의는 배포가 아니라 색인 작업과 사람 체크리스트까지 끝난 상태다(haewon 결정 2026-09-11). 워커 `publish` 단계(`supporters/scripts/publish_site.mjs`)가 자동으로 하는 것: 라이브 확인(홈·robots·sitemap·noindex 헤더), IndexNow 로 사이트맵 URL 전부 알림(빙·네이버 공용, 색인 허용 빌드일 때만), Google Search Console 사이트맵 제출과 URL 색인 상태 조회(서비스 계정 `GOOGLE_SERVICE_ACCOUNT_JSON` 있을 때), 검색엔진 소유 확인 메타 태그(site.json `googleSiteVerification`·`naverSiteVerification`), IndexNow 키 파일(`public/<key>.txt`) 자동 생성, `--indexable` 로 noindex 헤더 제거·승인 글만 발행, `--domain` 으로 정식 도메인 연결. 사람에게 남기는 것(`~/supporters-builds/<clinic>/publish-checklist.md`, `status.humanTasks`): 네이버 서치어드바이저 등록·소유 확인·사이트맵/RSS 제출(공개 API 없음), Google Search Console 소유자 추가(첫 1회), 정식 도메인 결정, 색인 허용 전환 전 법무 3건·게시 승인, 병원 자산에서 백링크·sameAs, 네이버 블로그 재작성 트랙. 하지 않는 것: Google Indexing API(채용·라이브 방송 전용), 색인 보장 약속. 제품에서는 이 체크리스트를 확인 패널(`ClinicInputsPanel`) 또는 운영자 알림으로 사람에게 요청하게 만드는 것이 다음 일이다.
|
||||
|
||||
## 5. haewon 결정 대기
|
||||
|
||||
1. ~~**Product 1.0 범위**~~ **결정 (2026-09-12, haewon): (b) 진단 + 서포터즈 사이트 초안.** 랜딩 CTA 는 여기까지 약속한다. 측정·관리는 내부 참고로만 쓰고 약속하지 않는다. 남은 일은 §4-2 리드→큐 연결과 §4-3 진단 런타임화다.
|
||||
2. **가격 티어**(§4-7)와 계약 최소 기간·해지 조건(2026-04 문서의 P0 두 항목이 그대로 미결).
|
||||
3. ~~**뷰 샘플 통일**~~ **결정 (2026-09-12, haewon): 메인 `supporters/` 가 정본.** 템플릿은 `npm run export:template` 로 여기서 생성한다. 영어 홈 `/en` 은 템플릿에 추가했고 영문 사실이 없는 병원에서도 문장이 성립하도록 조각 단위로 조립한다(§5-2 판단 2). `view-supporters-sample` 배포 자리를 워커로 옮길지는 아직 안 정했다.
|
||||
4. **법무**: 외국인환자 유치업 등록 여부(플래너·숙소 링크), 의료광고 사전심의 대상 여부, 지원 관계 고지 문구(sponsorNotice). 이 세 가지는 색인 허용 전환의 전제.
|
||||
5. ~~**도메인**~~ **결정 (2026-09-14, haewon): 공용 도메인 `webforai.kr` 의 병원별 서브도메인.** 뷰 `viewpl` · 원진 `wonjin` · 오라클 `oracle`. 구매 품의 결재 대기이고, 절차와 규모 대비는 §5-4 에 있다. 남은 미결은 `webforai.co.kr` 함께 확보할지, `webforai.com`(Afternic 파킹) 인수할지, 서포터즈 실명 모집 방식이다.
|
||||
6. **워커 호스팅**과 GCP 계정(GA4 서비스 계정).
|
||||
7. **규칙표 잠정값**: 뷰·원진·오라클 `procedures.json` 의 provisional 값(출국 최소일·입원·실밥)을 병원 확인 뒤 공개할지, 공개 전까지 "병원 확인 전" 배지로 둘지.
|
||||
8. 서포터즈 빌드 하위 결정(하위 문서 §4)과 오라클 핸드오버 §3 미결은 그대로.
|
||||
|
||||
## 5-2. 2026-09-12 세션에서 내린 판단 (기록)
|
||||
|
||||
핸드오버에 없던 것을 작업 중에 발견해 판단한 두 가지다. 되돌리려면 아래 "되돌리는 법"대로 하면 된다.
|
||||
|
||||
### 판단 1. 영문 의료광고 고지를 병원 승인 없이 번역하지 않는다
|
||||
|
||||
- **발견**: 정본이 된 `supporters/src/lib/i18n.ts` 가 지원 관계 고지와 부작용 고지를 영문으로 번역해 푸터에 싣고 있었다. `site.json` 에 `sponsorNoticeEn` 은 없었고, 어디에도 병원 승인 표시가 없었다. Productize 쪽 구현은 같은 자리에서 번역을 거부하고 한국어 원문에 확인 대기 표기를 붙이고 있었다.
|
||||
- **왜 문제인가**: 지원 관계 고지와 부작용 고지는 의료법 56조와 추천보증 심사지침의 규율 대상이다. 문구를 임의로 번역하면 병원이 승인하지 않은 문장이 병원 이름으로 나간다. §5-4 법무가 미결이고 색인 허용의 전제로 적혀 있는 항목이다.
|
||||
- **한 것**: `Base.astro` 가 `site.json` 의 `sponsorNoticeEn` 과 factSheet 의 `sideEffectNoticeEn` 을 먼저 읽고, 없으면 한국어 원문을 싣고 "The English wording of these notices is pending clinic approval." 을 함께 낸다. `i18n.ts` 의 영문 `foot_sponsor`·`foot_side` 는 이 자리에 쓰지 않는다고 주석으로 표시했다.
|
||||
- **되돌리는 법**: 병원이 영문 문구를 확정하면 `site.json` 에 `sponsorNoticeEn`, factSheet 에 `sideEffectNoticeEn` 을 넣는다. 코드 수정은 필요 없고 확인 대기 표기가 자동으로 사라진다.
|
||||
|
||||
### 판단 2. 누락 탐지기를 고쳐 상호 밖의 고유 사실까지 잡는다
|
||||
|
||||
- **발견**: `export_template.mjs` 의 `CLINIC_RE` 가 상호(`뷰성형외과`·`viewclinic`)만 찾아서 지명으로 적힌 사실을 놓쳤다. 빈 데이터로 템플릿을 빌드하니 영어 홈이 `Founded 확인 대기 · Sinnonhyeon Station Exit 3` 를 냈다. 또 이 검사는 빌드 도구의 기본 인자(`--clinic viewclinic`) 때문에 늘 실패해 신호가 되지 못했다.
|
||||
- **왜 문제인가**: 템플릿은 모든 병원 사이트의 원본이다. 화면을 그리는 코드에 한 병원의 사실이 문장으로 박히면 다른 병원 화면에 **틀린 사실**이 나간다. 값이 비어 화면이 비는 것과 다르다.
|
||||
- **한 것**: 지명·건물 표현(`Sinnonhyeon`·`Bongeunsa`·`Gimpo`·`신논현`·`봉은사`·`19-storey`·`View Plastic`)을 패턴에 넣었다. 그리고 심각도를 나눠, 문장을 그리는 코드(`src/pages`·`layouts`·`components`·`lib`)에 남으면 내보내기를 멈추고, 데이터(`src/data`)와 빌드 도구(`scripts`)는 알림만 한다. 데이터는 워커가 병원마다 갈아끼우고 빌드 도구는 고객이 보지 않기 때문이다.
|
||||
- **곁가지로 잡은 것**: 같은 점검에서 영어 홈의 하드코딩 7곳(공항 경로·건물 층수·히어로 alt·영문 병원명·404 상호·히어로 칩·머리말 기본값 `VIEW SUPPORTERS`)과, 영문 사실이 없는 병원에서 문장이 `is a at .` 로 깨지는 문제를 함께 고쳤다. 게이트는 두 가지 모두 오류로 잡지 못한다.
|
||||
|
||||
### 여기서 배운 규칙
|
||||
|
||||
- **템플릿에 넣기 전 빈 데이터로 빌드해 본다.** 한 병원의 데이터로만 확인하면 하드코딩과 데이터 참조가 구별되지 않는다. 화면이 같아 보이기 때문이다.
|
||||
- **게이트가 통과해도 화면은 깨질 수 있다.** 게이트는 의료광고 문구와 금칙어를 보지, 빈 값이 만든 비문을 보지 않는다. 배포 전 로컬 확인이 필요한 이유다.
|
||||
|
||||
## 5-3. 이미지 (2026-09-12) · 사람 게이트와 남은 일
|
||||
|
||||
### 자동 분류를 믿을 수 없다
|
||||
|
||||
뷰성형외과 수집 이미지 12장 중 6장이 의료광고 금지 유형인데 전부 시설(clinic)로 분류돼 있었다.
|
||||
전후 사진(밑선 절개 흉터 1개월·6개월 경과), 시술 행위 사진(팔 주사·드레싱), 광고 모델 신체 사진 4장,
|
||||
장비 제조사 성능 주장 그래픽, 가슴 라인 일러스트(랜딩 카드 썸네일로 노출)다.
|
||||
|
||||
**파일명·alt 로는 한 장도 걸리지 않는다.** 91장을 그 방식으로 검사해 0장이 걸렸다.
|
||||
파일명이 `img02` 이고 alt 가 "가슴확대 시설 사진" 이면 어떤 규칙도 전후 사진임을 알 수 없다.
|
||||
규칙 문서 §4 의 `IMAGE_RULE`(파일명·alt 검사) 계획은 이 유형에 효과가 없다.
|
||||
|
||||
→ **이미지 육안 확인은 사람 게이트로 남긴다.** `scripts/build_image_review.mjs` 가 검토 요청서를 만든다.
|
||||
화면에 실제로 나가는 것을 먼저 보여주고, 체크하면 `excludeImages` 에 넣을 목록이 만들어진다.
|
||||
`node scripts/build_image_review.mjs --out <파일.html> --site <id>=<siteDir>`
|
||||
|
||||
### 대표 이미지 배정 (워커 heroes 단계)
|
||||
|
||||
`PostCard` 의 대체 순서가 건물 사진 하나로 수렴해 목록에 같은 사진이 나란히 섰다.
|
||||
`scripts/assign_post_heroes.mjs` 가 글 분류별로 어울리는 유형을 골라 글마다 다른 사진을 배정한다.
|
||||
의료진 사진은 쓰지 않고(저자·검토자 표시가 지정 용도), 수집 alt 가 효과·결과를 주장하면 중립 문구로 바꾸며,
|
||||
이미지가 모자라면 다시 쓰지 않고 비운다.
|
||||
|
||||
### 남은 일
|
||||
|
||||
| 항목 | 상태 |
|
||||
|---|---|
|
||||
| 원진 66장 육안 검토 | **요청 상태.** 그중 15장이 화면에 나간다. 아직 아무도 보지 않았다 |
|
||||
| 뷰 17장 | 12장 확인 완료(6장 제외). 의료진 13장은 검토자 표시 용도라 별도 |
|
||||
| 오라클 이미지 | **0장.** 근거 수집일 2026-09-10 이 EUC-KR 디코딩(2026-09-11 반영)보다 하루 빨라 `images[].alt` 가 전부 `????` 로 깨졌고 분류기가 한 장도 고르지 못한다. **코드는 이미 고쳐져 있으므로 `--from evidence` 재수집이면 풀린다.** 다만 `oracleclinic.com` 이 응답하지 않는다(포트 80 연결 거부, 2026-09-12 기준). 그룹 사이트는 살아 있으나 Next.js 이미지 최적화 뒤에 있고 내용이 브랜드 배너·인증 마크라 대부분 제외 대상이다 |
|
||||
| 뷰 이미지 부족 | 제외 후 쓸 수 있는 것이 4장이라 글 5편 중 2편이 건물 사진으로 떨어진다. 병원 제공 시설 사진이 필요하다 |
|
||||
|
||||
## 5-4. 사이트 주소와 병원 레지스트리 (2026-09-14)
|
||||
|
||||
### 결정
|
||||
|
||||
서포터즈 사이트는 **공용 도메인 `webforai.kr` 의 병원별 서브도메인**에 둔다(haewon 2026-09-14).
|
||||
병원마다 도메인을 사면 병원 수만큼 갱신 대상이 늘고 한 건만 놓쳐도 그 병원 사이트가 멈춘다.
|
||||
근거와 리스크 비교는 `docs/business/INFINITH_서포터즈_사이트주소_방식비교_2026-09-14.docx` 에 있다.
|
||||
|
||||
병원 도메인의 서브도메인(`supporters.viewclinic.com`)은 제외했다. `viewclinic.com` 네임서버가 Cafe24 라
|
||||
병원 웹 개발사 협조가 전제된다. 원진은 whoisdomain.kr, 오라클은 네임서버 응답이 없다.
|
||||
|
||||
### 지금 상태
|
||||
|
||||
| 항목 | 상태 |
|
||||
|---|---|
|
||||
| `webforai.kr` 구매 | **완료 (2026-09-14).** (주)오투오 명의, 14,000원, 만료 2027-09-14 |
|
||||
| 네임서버 위임 | **완료.** `ns1·ns2.vercel-dns.com`. 존 전체를 Vercel 이 관리하므로 이후 서브도메인 추가에 사람이 DNS 를 만질 일이 없다 |
|
||||
| 주소 연결 | **완료.** 아래 표 참조 |
|
||||
| `clinic_registry` 서브도메인 컬럼 | **적용 완료** (`supabase/migrations/20260914_clinic_subdomain.sql`) |
|
||||
| `webforai.co.kr` | 미등록. 함께 확보할지 미결 |
|
||||
| `webforai.com` | 이미 등록됨(Afternic 파킹). 인수 여부 미결 |
|
||||
| 자동연장·도메인 잠금 | **꺼져 있음. 켜야 한다.** hosting.kr 도메인 상세 화면 왼쪽 패널 |
|
||||
|
||||
### 연결된 주소 (2026-09-14)
|
||||
|
||||
| 주소 | 내용 | Vercel 프로젝트 |
|
||||
|---|---|---|
|
||||
| `webforai.kr` · `www.webforai.kr` | AI Discovery 랜딩 | **`infinith-discovery` (신설)** |
|
||||
| `viewpl.webforai.kr` | 뷰성형외과 서포터즈 | `supporters-viewclinic` |
|
||||
| `wonjin.webforai.kr` | 원진성형외과 서포터즈 | `supporters-wonjin` |
|
||||
| `oracle.webforai.kr` | 오라클피부과 서포터즈 | `supporters-oracle` |
|
||||
|
||||
세 서포터즈 사이트 모두 canonical·사이트맵이 새 주소이고 noindex 는 유지된다.
|
||||
`infinith-demo` 는 건드리지 않았다. 기존 주소(`supporters-*.vercel.app`)도 그대로 살아 있다.
|
||||
|
||||
### AI Discovery 랜딩을 별도 프로젝트로 뺀 이유
|
||||
|
||||
`/discovery` 는 `infinith-demo` 안의 경로라 도메인만 붙이면 루트에 기존 INFINITH 랜딩이 뜬다.
|
||||
SPA 라 rewrite 로도 안 된다. React Router 가 주소로 판단하기 때문이다.
|
||||
|
||||
그래서 루트에서 무엇을 그릴지 환경변수로 정하게 했다(`VITE_SITE=discovery`, `src/main.tsx`).
|
||||
한 코드베이스로 두 사이트를 내므로 랜딩을 고치면 양쪽에 함께 반영된다.
|
||||
`index.html` 의 제목·설명·og 도 같은 변수로 갈아끼운다(`vite.config.ts`). 답변엔진이 읽는 첫 신호라 비워 두지 않는다.
|
||||
|
||||
새 프로젝트 `infinith-discovery` 의 환경변수(`VITE_SITE`·`VITE_SUPABASE_URL`·`VITE_SUPABASE_ANON_KEY`)는
|
||||
프로젝트 설정에 저장돼 있어 다음 배포에도 유지된다.
|
||||
|
||||
### 레지스트리에 넣은 것
|
||||
|
||||
`clinic_registry` 가 서브도메인의 단일 원본이다. 코드가 이름을 매번 짓지 않는다.
|
||||
수백 곳으로 늘면 "뷰"·"연세"·"그랜드" 처럼 여러 병원이 쓰는 이름에서 충돌이 난다.
|
||||
|
||||
- `subdomain`: 공용 도메인 앞 라벨. 소문자·숫자·붙임표, 2~40자, 붙임표 시작·끝 금지, 연속 붙임표 금지
|
||||
- `clinic_slug`: 워커 `--clinic` 값. 빌드 폴더·Vercel 프로젝트 이름에 쓰여 서브도메인과 다를 수 있다(뷰는 `viewpl` 대 `viewclinic`)
|
||||
- `subdomain_assigned_at`: 배정 시각
|
||||
- 둘 다 대소문자 무시 유니크. 예약어 70여 개 차단(메일·인증 계열 `mx`·`dmarc`·`autodiscover`·`ns1` 포함)
|
||||
|
||||
예약어를 병원에 내주면 나중에 메일·API·관리 화면을 붙일 때 주소를 회수해야 하고,
|
||||
회수 시점에는 이미 색인된 주소가 깨진다. 중복·예약어·형식 위반이 실제로 차단되는 것을 확인했다.
|
||||
|
||||
오라클피부과는 이 레지스트리가 성형외과 73곳으로 만들어져 등록돼 있지 않았다. 2026-09-14 에 넣어 74곳이다.
|
||||
|
||||
### 새 병원을 붙이는 순서 (2026-09-14 확립)
|
||||
|
||||
1. `clinic_registry` 에 `subdomain`·`clinic_slug` 를 넣는다. 이름은 코드가 짓지 않는다.
|
||||
2. 워커를 `--domain <서브도메인>.webforai.kr --deploy` 로 돌린다. DNS·인증서는 Vercel 이 자동으로 한다.
|
||||
`node workers/supporters-build/run.mjs --clinic viewclinic --url https://www.viewclinic.com --work ~/supporters-builds/viewclinic-test --from build --skip-generate --deploy --domain viewpl.webforai.kr`
|
||||
원진은 `--clinic wonjin --domain wonjin.webforai.kr`, 오라클은 `--clinic oracle --domain oracle.webforai.kr`.
|
||||
3. **`--indexable` 은 붙이지 않는다.** 법무 3건(외국인환자 유치업 등록, 의료광고 사전심의 대상 여부, 지원 관계 고지 문구)이 끝난 뒤 별도 품의로 올린다.
|
||||
4. 연결 뒤 주소 응답과 noindex 헤더·메타·샘플 배너가 남아 있는지 확인한다.
|
||||
|
||||
### 규모가 커질 때 대비할 것
|
||||
|
||||
공용 도메인은 평판을 공유한다. 비슷한 틀의 콘텐츠가 수백 개 서브도메인으로 쌓이면
|
||||
doorway 패턴으로 판정될 수 있고, 한 곳의 문제가 전체에 옮는다. 지금 없는 방어가 하나 있다.
|
||||
|
||||
- **병원 간 본문 유사도 상한을 발행 게이트에 추가한다.** 현재 `HOME_DUPLICATE` 는 같은 병원 안의 중복만 본다.
|
||||
- 상위 도메인 첫 화면을 병원 목록으로 만들지 않는다. 나열하면 네트워크로 읽힌다.
|
||||
- 전환이 필요해지는 경우는 병원이 자체 도메인을 요구하거나 평판 문제가 생길 때다. 전체를 옮기지 않고 해당 병원부터 옮긴다.
|
||||
|
||||
### DB 작업 시 주의
|
||||
|
||||
운영 DB 에 `supabase db push` 를 쓰지 않는다. 마이그레이션 이력에 10개 중 3개만 기록돼 있어
|
||||
나머지 7개를 다시 실행하려 든다. 새 마이그레이션은 `supabase db query --linked --file <파일>` 로 그 파일만 적용한다.
|
||||
대조표는 `docs/DB_SCHEMA_DIVERGENCE_2026-09-14.md` 에 있고, 정본은 마이그레이션 파일로 정했다(haewon 2026-09-14).
|
||||
표 17개 중 15개는 일치하고 손볼 곳은 `clinic_registry` 하나와 정의가 빠진 뷰 2개다.
|
||||
|
||||
## 6. 함정 (제품 관점)
|
||||
|
||||
- **한 Vercel 프로젝트에 두 폴더**: 2026-09-11 Productize/supporters 와 메인 supporters 가 같은 프로젝트에 배포해 `/plan` 이 404 됐다. 지금은 Productize 쪽 연결을 껐다. 배포는 병원 폴더와 메인 supporters 에서만.
|
||||
- **페이지를 덮어쓰지 않는다**: 플래너 통합 때 `/stay`(주변 안내)를 `/plan` 으로 리다이렉트해 세 사이트에서 주변 안내가 사라졌다(09-11 17시 복원). 새 기능이 기존 페이지와 '같은 주제'로 보여도 haewon 확인 전에는 기존 페이지를 지우거나 리다이렉트하지 않는다.
|
||||
- **세 브랜치 분산**: 같은 파일(`supporters/`)이 두 브랜치에서 다르게 진화했다. 머지 전에 "템플릿이 정본"을 정하고 뷰 샘플을 옮긴다.
|
||||
- **진단 결과가 코드**: `discovery_<id>.ts` 커밋 방식은 병원 수가 늘면 배포 병목이다(§4-3).
|
||||
- **게이트는 고객 화면 전체를 본다**: 운영 어휘("실측"·"API 집계"·"임베드")가 데이터 값을 타고 화면에 나가면 빌드가 막힌다. 운영 기록은 데이터에만.
|
||||
- **원문 문장은 걸러야 한다**: 병원 페이지 원문 32문장 중 14건이 홍보문·정형 고지였다. 워커 planner 단계 필터(CARE/NOT_CARE) 없이는 "병원 안내 기준" 배지가 의료광고 위반이 된다.
|
||||
- **헤드리스 크롬 모바일 캡처**: 창 최소 폭 때문에 400px 직접 캡처가 틀린다. 1280 창 안 390px iframe 하네스로.
|
||||
- **lint**: 메인 체크아웃은 한때 tsc 가 없어 `npm run lint` 가 가짜 통과였다. 실제 0 에러인지 `npx tsc --noEmit` 로 본다.
|
||||
- **엠대시·타 서비스 이름**: 커밋 메시지와 문서에서 두 번 잡혔다. 산출 전 엠대시 검색(`grep -cP "\x{2014}"`)과 타 서비스명 격리 grep(전역 CLAUDE.md 의 목록).
|
||||
|
||||
## 7. 파일 참조
|
||||
|
||||
- 진단: `src/data/aeoGeoRubricV2.ts`, `src/lib/discoveryScoreV2.ts`, `src/pages/DiscoveryReportPage.tsx`, `scripts/audit_aeo_geo.py`, `docs/AEO_GEO_RUBRIC_v2.md`
|
||||
- 랜딩·리드: `src/pages/DiscoveryLandingPage.tsx`, `src/components/discovery/`, `supabase/migrations/20260831_discovery_leads.sql`
|
||||
- 확인 패널·큐: `src/components/discovery/ClinicInputsPanel.tsx`, `src/pages/SupportersBuildPage.tsx`, `supabase/migrations/20260907_supporter_builds*.sql`
|
||||
- 워커·템플릿: Productize `workers/supporters-build/`, `supporters/scripts/`, `templates/supporters-astro/`, 브리프 `supporters/briefs/<clinic>/`
|
||||
- 플래너·지도: 템플릿 `src/components/Planner.astro`·`VisitMap.astro`, `src/lib/plan.ts`·`tour.ts`, `supporters/scripts/build_planner_data.mjs`, 설계 `docs/INFINITH_Viewclinic_Supporters_Site_Design_v0.2.md` §13
|
||||
- 측정: `scripts/run_question_bank_openai.py`, `scripts/sentiment_qb.py`, `docs/AI_ANSWER_SENTIMENT_RUBRIC_v0.1.md`, Productize `scripts/ga4_ai_traffic.py`, `data/ai_channels.json`
|
||||
- 사이트 주소·레지스트리: `supabase/migrations/20260914_clinic_subdomain.sql`, `docs/DB_SCHEMA_DIVERGENCE_2026-09-14.md`, `docs/business/INFINITH_서포터즈_사이트주소_방식비교_2026-09-14.docx`, 품의서 `docs/business/INFINITH_품의서_webforai.kr_도메인구매_2026-09-14.docx`
|
||||
- 이미지 육안 검토: `supporters/scripts/build_image_review.mjs`, 대표 이미지 배정 `supporters/scripts/assign_post_heroes.mjs`
|
||||
- 가격·경쟁: `docs/INFINITH_vs_FrostAI_비교_및_가격제안_2026-09-08.html`, `docs/INFINITH_가격정책_사업부보고서_v1.1.docx`
|
||||
- PRD: `docs/prd/INFINITH_AI-Discovery_PRD_20260828_v02.md`, `docs/prd/INFINITH_Global_Medical_Hub_PRD_v0.1.html`(Productize 에 v0.2), `docs/prd/INFINITH_Dermatology_Supporters_PRD_v0.1.html`
|
||||
|
||||
## 8. 이번 이틀(09-10~11)의 인사이트 · 다음 개발에 그대로 적용할 것
|
||||
|
||||
1. **한 세션 = 한 저장소 = 한 배포 대상.** 두 세션이 같은 Vercel 프로젝트와 같은 `supporters/` 폴더를 다른 브랜치에서 건드려 `/plan` 404 와 주변 안내 실종이 났다. 세션 시작 때 `git status`·`.vercel/project.json`·열린 세션 여부를 확인하고, 그 세션이 맡지 않은 자산은 손대지 않는다. 브랜치 통합(§4-1)이 이 문제의 근본 해결이므로 첫 일로 했다. **2026-09-12 완료.**
|
||||
- 다만 통합 중에 같은 사고를 한 번 더 냈다. 머지 기준으로 삼은 브랜치 끝이 세션 도중 9개 커밋만큼 앞서 있었고(주변 안내 복원·워커 publish 단계), 그것을 모르고 올린 첫 머지가 둘을 떨어뜨렸다. 푸시 전에 발견해 되돌렸다.
|
||||
- **머지 직전에 브랜치 끝을 다시 읽는다.** 세션 시작 때 읽은 `git rev-parse`/`git log` 값은 머지 시점에 낡아 있을 수 있다. 다른 워크트리가 열려 있으면 특히 그렇다. 머지 뒤에는 `git rev-list --count main..<branch>` 가 0 인지 확인한다.
|
||||
2. **"완료"를 산출물이 아니라 고객 결과로 정의하면 자동·사람 경계가 드러난다.** 서포터즈 사이트 완료 = 색인 작업 + 사람 체크리스트로 바꾸자 IndexNow·GSC 는 자동, 네이버 등록·소유자 추가는 사람으로 갈렸고 워커 단계가 그 경계를 코드로 가졌다. 진단(사람 판정 4항목), 병원 확인(재빌드 트리거)도 같은 방식으로 "완료 정의 → 자동 부분 → 사람 요청"을 명시한다.
|
||||
3. **병원 원문은 인용 전에 걸러야 한다.** 원문 32문장 중 14건이 홍보문·정형 고지·표 조각이었다. 원문을 데이터로 쓰는 모든 경로(recoveryNotes, OCR, 뉴스 제목)에 화이트리스트 필터와 "제외된 문장 목록 눈으로 보기"를 둔다. 필터 없는 "병원 안내 기준" 배지는 의료광고 위반이다.
|
||||
4. **게이트는 데이터 값도 본다.** 좌표 출처의 "실측"이 화면에 새어 나가 빌드가 막혔다. 데이터 필드마다 고객 노출 여부를 정하고 운영 기록(source·coordSource·note)은 렌더하지 않는다.
|
||||
5. **규칙표를 먼저 쓰면 코드·테스트가 따라온다.** 설계 v0.2 §13 의 status(clinic/provisional)·출처 필수 규칙이 그대로 procedures.json 스키마와 게이트 테스트가 됐다. 새 기능은 데이터 규칙표부터 문서화한다.
|
||||
6. **관광공사 데이터는 공급이지 수요가 아니다.** 선호는 화면에서 직접 묻고, 큐레이션이 1순위, 외부 데이터는 보강(2순위)이라는 우선순위를 점수로 코드화했다. 외부 API 를 붙일 때마다 같은 질문(공급인가 수요인가, 누가 1순위인가)을 먼저 한다.
|
||||
7. **디자인 규칙은 텍스트가 아니라 리듬이다.** "흰 카드만 쌓인 페이지"는 홈·영상 페이지의 섹션 리듬(다크 히어로+글래스 통계 → 틴트 → 다크 → 라이트 → 틴트 → 다크)을 안 따라서 완성도가 낮아 보였다. 새 페이지는 기존 페이지 두 개의 구성을 먼저 분석하고 같은 리듬으로 짠다. 웜 그라디언트는 CTA 카드 한 장에만.
|
||||
8. **전역 CSS 와 컴포넌트 클래스는 접두어로 분리한다.** `.kpi` 충돌로 그라디언트가 되살아났다. 컴포넌트 클래스는 `plan-`·`vm-` 처럼 접두어를 쓰고, 새 클래스는 전역 CSS 에 grep 한다.
|
||||
9. **모바일 검증은 하네스로.** 헤드리스 크롬 창 최소 폭 때문에 400px 캡처가 틀렸고, 실제 문제(헤더 겹침)는 아이프레임 하네스에서만 보였다. 8페이지×3사이트 그리드 캡처를 배포 전 기본 절차로 둔다.
|
||||
10. **언어 전환은 전역 하나.** 페이지별 토글은 사용자 관점에서 틀렸다. 영어 홈이 없는 사이트는 영어 입구(/en/plan)를 정해야 하고, 템플릿에 영어 홈을 넣을지는 §5-3 결정에 달렸다.
|
||||
11. **기존 페이지는 haewon 확인 전에 지우거나 리다이렉트하지 않는다.** 새 기능이 기존 페이지와 같은 주제로 보여도 병존이 기본이다(주변 안내 `/stay` 와 회복 일정 `/plan`). **2026-09-12 재발.** 브랜치 통합 머지가 `/stay` 를 떨어뜨렸다. 페이지를 지우려는 의도가 없어도 머지 기준을 잘못 잡으면 같은 결과가 난다. 통합 뒤 페이지 목록을 이전 배포와 대조한다.
|
||||
12. **제품 병목은 세 가지로 좁혀졌다.** 진단 결과가 코드 파일, 리드→큐 미연결, 브랜치 3개 분산. 이 셋을 풀어야 "URL 입력 → 리포트 링크 → 서포터즈 미리보기"가 사람 손 없이 이어진다.
|
||||
@ -1,82 +0,0 @@
|
||||
# 다음 세션 프롬프트 · 2026-09-19 이후
|
||||
|
||||
아래 §0 을 그대로 복사해 새 세션 첫 메시지로 넣으면 된다.
|
||||
|
||||
---
|
||||
|
||||
## §0. 복사해서 붙일 프롬프트
|
||||
|
||||
```
|
||||
INFINITH AI Discovery 제품 개발을 이어서 한다.
|
||||
|
||||
작업 폴더: ~/orca/workspaces/INFINITH/Productize-2 (main)
|
||||
파이프라인: ~/orca/workspaces/INFINITH/Productize (feature/supporters-autobuild)
|
||||
|
||||
시작하기 전에 읽어라:
|
||||
docs/HANDOVER_2026-09-18.md 어제까지의 상태와 결정 대기 목록
|
||||
docs/DESIGN_신청알림메일_v0.1.md 메일 설계 (결정 대기 4건은 §8)
|
||||
|
||||
두 저장소 모두 커밋은 되어 있고 푸시는 안 했다. 시작할 때 git status 와
|
||||
다른 세션이 같은 폴더를 열고 있지 않은지 먼저 확인해라.
|
||||
|
||||
오늘 할 일은 아래에서 고른다. 1번을 먼저 하고, 내가 확인해 준 뒤 2번으로 간다.
|
||||
|
||||
1. 어제 만든 화면 육안 확인
|
||||
npm run dev 로 띄우고 /discovery/viewclinic, /build/jkplastic,
|
||||
/images/jkplastic 를 열어 둬라. 내가 본다.
|
||||
특히 볼 것: What We Will Fix 섹션 문구·버튼 대비, 왼쪽 아래 이전 단계 버튼,
|
||||
사진 확인 썸네일 줄.
|
||||
|
||||
2. 분석 리포트(/report/:id)의 로드맵 타임라인을 진단 리포트로 옮긴다
|
||||
진단의 Action Plan 은 P0/P1/P2 목록만 있고 시간축이 없다.
|
||||
기존 RoadmapTimeline 컴포넌트와 result.actions 의 effort 값을 쓴다.
|
||||
데이터를 지어내지 마라. 없는 값은 확인 대기로 둔다.
|
||||
|
||||
지켜야 할 것:
|
||||
- 서포터즈 빌드는 병원 홈페이지를 고치지 않는다. 진단 실행 항목이
|
||||
빌드로 해결된다고 화면에 쓰지 마라.
|
||||
- 고객 화면에 "결과"라는 말을 쓰지 마라. 빌드가 끝나도 사진 확인과
|
||||
확인 항목이 남아 최종이 아니다.
|
||||
- 텍스트는 src/index.css 의 .ui-ask / .ui-body / .ui-note 역할 토큰을 쓴다.
|
||||
다크 섹션은 .on-dark, 그 안의 흰 카드는 .on-light, 버튼은 .btn-on-dark.
|
||||
- 프로덕션 배포(vercel --prod, 병원 사이트)는 내 확인을 받고 실행한다.
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## §1. 선택지 (위 프롬프트에서 2번 대신 고를 수 있는 것)
|
||||
|
||||
우선순위순이 아니라 성격순이다. 무엇을 할지는 haewon이 정한다.
|
||||
|
||||
### A. 분석 리포트 요소 흡수 (어제 정한 방향)
|
||||
|
||||
| 요소 | 난이도 | 비고 |
|
||||
|---|---|---|
|
||||
| 로드맵 타임라인 | 낮음 | 데이터가 이미 있다. 가장 값이 크다 |
|
||||
| 채널 건강도 링 | 중간 | `supporter_builds.report.channels` 를 쓴다 |
|
||||
| KPI 대시보드 | 보류 | 목표치 데이터가 없다. 지어내지 말고 재측정 기준으로 바꾸는 편이 맞다 |
|
||||
|
||||
### B. 메일 붙이기
|
||||
|
||||
결정 4건(설계 §8)이 먼저다. 다만 **순서 4번(모달 문구 수정)은 계정 없이 지금 할 수 있다.** 화면이 약속하는 것과 실제 동작의 어긋남부터 없애는 조치다.
|
||||
|
||||
### C. 파이프라인 잔여
|
||||
|
||||
- `clinicDoctors` 4명 고정을 병원 규모에 맞출지
|
||||
- planner가 tourism 좌표를 쓰도록
|
||||
- 뷰 의료진 id 두 벌 정리 (뷰 재빌드와 묶어서)
|
||||
- `check.mjs --update-layout` 레이아웃 기준선
|
||||
|
||||
### D. 분석 리포트 자체 수정
|
||||
|
||||
히어로 공백 · `NaN년` · 과한 여백. 옮기든 접든 남는 문제다.
|
||||
|
||||
---
|
||||
|
||||
## §2. 다음 측정에서 볼 값
|
||||
|
||||
09-15 실측에서 네이버 AI 브리핑이 120문항 중 119문항에 노출되는데 **논브랜드 83건 중 뷰성형외과 언급은 1건**이었다. 브랜드명을 아는 사람에게만 답변이 나오고, 시술이나 지역으로 찾는 사람에게는 도달하지 못한다.
|
||||
|
||||
서포터즈 사이트가 이 구간을 움직이는지가 제품의 핵심 증명이다. 뷰·원진·오라클이 색인 허용으로 전환된 뒤 같은 120문항을 다시 돌려 비교한다.
|
||||
|
||||
근거: `docs/reports/viewclinic/03_question_bank/Viewclinic_QB_Sentiment.md`, `scripts/out/naver_briefing_summary.json`
|
||||
@ -1,318 +0,0 @@
|
||||
# 뷰성형외과 MOU · POC 범위 검토 보고서
|
||||
|
||||
작성 2026-09-15(화) · INFINITH AI Discovery · 대표 보고용 초안
|
||||
입력: 2026-09-15 회의록(황본부장·감본부장), Global Medical Hub PRD v0.2(2026-09-09), 뷰성형외과 POC 제안서(2026-09-01), 서포터즈 색인 보류 사유 보고(2026-09-14), 답변엔진 확장 검토 보고서(2026-09-15), 웹 조사 2건(2026-09-15)
|
||||
법 해석은 자문이 아니며, 계약·등록 전에 변호사 확인이 필요합니다.
|
||||
|
||||
---
|
||||
|
||||
## 0. 결론
|
||||
|
||||
1. **이번 MOU·POC 범위는 서포터즈 사이트와 AEO/GEO 진단·측정까지로 한정할 것을 권고합니다.** 글로벌 의료관광 플랫폼(다병원 허브, 상담·예약 중개, 숙박·관광 연계)은 이번 범위에서 명시적으로 제외합니다.
|
||||
2. **플랫폼 사업은 현재 조직(개발팀장 1명, 주니어 개발자 5명, 운영팀 없음)으로 가능하지 않다고 판단합니다.** 개발보다 운영과 수요 확보가 병목입니다.
|
||||
- 선두 사업자 강남언니는 2025년 광고선전비로 343억 원을 썼습니다.
|
||||
- 오프라인 통역센터와 8개 언어 통역을 운영하고 있습니다.
|
||||
- 병원 제휴를 수천 곳 단위로 확보했습니다.
|
||||
3. **PRD v0.2가 차별점으로 삼은 "기존 앱은 정보를 앱 안에 가둔다"는 전제는 일부만 맞습니다.**
|
||||
- 크리에이트립과 여신티켓은 AI 크롤러 허용과 llms.txt, 다국어 블로그를 이미 운영합니다.
|
||||
- 강남언니도 AI 검색봇은 허용하고, 후기 페이지만 막고 있습니다.
|
||||
4. **MOU의 목적은 사례 확보와 홍보입니다.** 비용을 받지 않는 POC라면 우리가 제공할 범위, 뷰성형외과가 제공할 것, 끝나는 날짜를 문서로 고정해야 합니다. 범위를 열어 둔 미팅은 무상 플랫폼 POC 요청으로 이어질 가능성이 높습니다.
|
||||
5. **미팅 전에 이 문서의 7장 안건표로 대표 결정을 먼저 받는 순서를 권고합니다.**
|
||||
|
||||
---
|
||||
|
||||
## 1. 회의록 검토
|
||||
|
||||
### 1-1. 회의에서 확인된 사항
|
||||
|
||||
| 구분 | 내용 | 담당 |
|
||||
|---|---|---|
|
||||
| 뷰성형외과 내부 진행 | 실무진이 원장에게 보고를 마쳤고, 진행 결정이 났습니다 | 뷰성형외과 |
|
||||
| 진행 순서 | MOU → 홍보 기사 → 프로젝트 런칭 | 양사 |
|
||||
| 순서에 대한 이견 | 대표는 MOU 우선을 원합니다. 뷰성형외과는 범위를 조금이라도 구체화한 뒤 MOU를 하자는 입장입니다 | 황본부장 조율 |
|
||||
| 뷰성형외과 쪽 키 | 지난 미팅은 마케팅 담당자 3명이 나왔고, 보고 이후 원장 쪽 방향(B2C 의료관광 포털)이 무게를 갖게 됐습니다 | |
|
||||
| 감본부장 입장 | 비용 없는 플랫폼 POC라면 미팅에 가지 않고, 문서로 범위를 먼저 정리합니다. 유료 논의라면 참석합니다 | 감본부장 |
|
||||
| 범위 문서 | 향후 1~2개월에 무엇을 할지 구체화한 문서를 대표에게 보고한 뒤, 그 문서로 뷰성형외과와 협의합니다 | 감본부장 |
|
||||
| 황본부장 입장 | 서포터즈는 기본 범위로 보고, 메디컬·버티컬 포털은 결정되지 않은 큰 그림입니다. 1단계에서 큰 그림을 그릴 필요는 없다는 데 동의했습니다 | 황본부장 |
|
||||
| 배포 현황 | webforai.kr 랜딩과 서포터즈 3곳(서브도메인)을 배포했고, 구글·네이버 서치콘솔에 등록했습니다. 색인은 보류 중입니다 | 감본부장 |
|
||||
| 자동화 수준 | 뷰성형외과 품질을 100으로 보면, 다른 병원은 URL만으로 60~70% 수준입니다. 홈페이지 콘텐츠가 적은 병원은 결과가 부실합니다 | 감본부장, 금요일(9/18) 연구소 회의에서 시연 |
|
||||
| 공유 | 사이트 URL을 management 방에 공유합니다 | 감본부장 |
|
||||
|
||||
### 1-2. 문서에 옮기기 전에 바로잡을 발언
|
||||
|
||||
| 회의 발언 | 확인 결과 | 문서에 쓸 표현 |
|
||||
|---|---|---|
|
||||
| "모델 2개. 클로드·제미나이·구글 AI 오버뷰를 추가하면 한 번에 몇천 원이 몇만 원 단위로 오른다" | 비용 방향은 맞습니다. 120문항 기준 추정치(후처리 포함, 중앙값)는 아래와 같습니다. 그런데 **비용만의 문제가 아닙니다.** Gemini 검색 그라운딩 약관은 결과의 분석·자동 수집을 금지합니다. Google AI Overview는 공식 API가 없어 제3자 수집 방식입니다 | "현재 2개 엔진. Claude는 추가 가능하고, Gemini·AI Overview는 약관·수집 원칙 확인이 먼저입니다" |
|
||||
| "의료관광법상 기관 검증을 통하지 않은 실물 인물 콘텐츠를 배포하지 못해 색인을 안 했다" | 이 내용의 조문은 확인되지 않았습니다. 9/14 사유 보고서 기준 보류 사유는 두 가지이고, 법률 확인 항목이 따로 두 가지 있습니다 | "병원 승인 글 0편, 지원 관계 고지 문구 미확정. 법률 확인 2건(의료광고 사전심의, 유치업 등록 필요 여부)" |
|
||||
| "강남언니·바비톡은 사용자 600만~천만, 병원 3천~5천 곳 제휴" | 확인된 값은 아래와 같습니다. 방향은 맞지만 숫자는 출처가 있는 값으로 씁니다 | 아래 3-2 표 수치 |
|
||||
|
||||
**답변엔진 추가 시 비용 추정 (120문항, 후처리 포함, 중앙값)**
|
||||
|
||||
| 구성 | 1회 측정 | 5회 반복 |
|
||||
|---|---|---|
|
||||
| 현행 2개 (ChatGPT, Perplexity) | 약 ₩11,440 | 약 ₩57,200 |
|
||||
| + Claude | 약 ₩29,150 | 약 ₩145,740 |
|
||||
| + Claude, Gemini, Google AI Mode | 약 ₩35,790 | 약 ₩178,950 |
|
||||
|
||||
**강남언니·바비톡 확인 수치**
|
||||
|
||||
| 항목 | 확인된 값 |
|
||||
|---|---|
|
||||
| 강남언니 해외 | 누적회원 148만, MAU 23만(2025-09) |
|
||||
| 강남언니 일본 | 사용자 160만, 제휴병원 1,500곳(2025-10) |
|
||||
| 바비톡 | 병원 3,300곳·1,000만 다운로드는 채용 플랫폼 소개문에만 있고 1차 출처 미확인 |
|
||||
|
||||
### 1-3. 회의록 표기 정리 (음성 인식 오류)
|
||||
|
||||
| 회의록 표기 | 바른 표기 |
|
||||
|---|---|
|
||||
| 비성형외과, 비서약과, 뷰 성향 | 뷰성형외과 |
|
||||
| 김수원 원장 | 최순우 원장(대표자, 팩트 시트 기준) |
|
||||
| 데미나이 | Gemini |
|
||||
| AOG로, AO, AO 최적화 | AEO·GEO |
|
||||
| 인류관광 | 의료관광 |
|
||||
| 웹 포 AI | webforai.kr |
|
||||
| 메트코 | 확인 필요 |
|
||||
|
||||
---
|
||||
|
||||
## 2. Global Medical Hub PRD v0.2 검토
|
||||
|
||||
### 2-1. PRD가 제안한 것
|
||||
|
||||
- **3층 구조입니다.**
|
||||
- 1층 정보·발견: 다국어 병원 원문 허브
|
||||
- 2층 상담·예약: 유치업 등록 후 운영
|
||||
- 3층 회복기 여정: 숙박·식사·관광 제휴 링크
|
||||
- **구조:** AIO2O 단독 소유, 뷰성형외과는 앵커 파트너(첫 병원, 병원 2~4곳 소개, 의료 자문)입니다.
|
||||
- **1단계(8~12주) 게이트:** 병원 4곳 개설자 승인, 외국인 질문 뱅크 인용 기준선 확보, 상담 신청 월 10건.
|
||||
|
||||
### 2-2. 지금도 유효한 판단
|
||||
|
||||
- 파이프라인 IP는 AIO2O가 100% 가집니다. 병원 자회사나 합작법인 구조는 중립성과 IP 측면에서 불리합니다.
|
||||
- 1단계는 수수료를 받지 않고 병원에 직접 연결합니다. 대가를 받는 순간 유치업 등록 대상이 됩니다.
|
||||
- 후기, 전후 사진, 병원 비교, 할인으로 설득하는 기능은 만들지 않습니다(의료법 56조).
|
||||
|
||||
### 2-3. PRD의 공백과 보완할 점
|
||||
|
||||
| 공백 | 내용 | 보완 |
|
||||
|---|---|---|
|
||||
| 운영 인력 가정 없음 | "비개발자 운영자가 운영"을 전제로 했지만, 현재 운영팀이 없습니다. 게이트 실패 처리, 이미지 육안 확인, 채널 판정, 정정 접수, 병원 응대, 상담 신청 전달이 모두 사람 업무입니다 | 운영 인원을 선행 조건으로 명시 |
|
||||
| 수요(외국인 환자) 확보 계획 없음 | AI 인용만으로 유입을 기대합니다. 경쟁 앱은 광고비와 오프라인 센터로 수요를 확보합니다 | 유입 채널과 예산 추정 추가 |
|
||||
| 경쟁 전제가 일부 틀림 | "기존 앱은 정보를 앱 안에 가둔다"고 봤지만, 크리에이트립·여신티켓은 AI 크롤러 허용, llms.txt, 영·일·중 블로그를 운영합니다. BeautsGO는 AI 에이전트용 예약 스킬을 배포했습니다 | 차별점을 "병원 승인 원문 + 실측 측정"으로 좁혀 재정의 |
|
||||
| 1단계 개발량 | PRD §06 기준 1단계에 필요한 신규 7건과 부분 2건입니다. 신규: 외국인 질문 뱅크, 외국어 게이트, 다국어 라우팅·상담 폼, 다병원 허브 도메인, 계정·역할·권한, 상담 신청·알림, 측정 인프라. 부분: 운영자 콘솔, 상시 워커 | 현재 인력으로 8~12주는 낙관적. 4장 참고 |
|
||||
| 규제 변경 미반영 | 2026-05-15 시행규칙 개정으로 여행업자 자본금 5천만 원 예외가 삭제돼 유치업 등록은 자본금·보증보험 각 1억 원입니다. 등록 후 2년 안에 유치 실적이 없으면 등록취소 사유입니다. 2026-05-12부터 병원이 유치 경로를 보고합니다 | §05 표 갱신 |
|
||||
| 민감정보 리스크 | 2026-09-04~05 강남언니에서 22만 명(해외 약 6만 명) 민감정보 유출이 있었습니다. 상담 신청 폼만 받아도 건강정보 처리자가 됩니다 | 1단계 상담 폼 최소 수집, 처리방침·보안 체계 선행 |
|
||||
| 앵커 소개 역량 미확인 | 원장의 병원 네트워크가 명단·수·관계 단계로 확인되지 않았습니다 | MOU 전 확인 질문으로 유지 |
|
||||
| 브랜드 리스크 | 앵커 병원의 2026-07 공정위 시정명령 이력이 허브 브랜드에 붙을 수 있습니다 | MOU 홍보 기사 문안에서 관리 |
|
||||
|
||||
---
|
||||
|
||||
## 3. 글로벌 의료관광 플랫폼 딥리서치
|
||||
|
||||
### 3-1. 시장
|
||||
|
||||
| 항목 | 수치 | 출처 |
|
||||
|---|---|---|
|
||||
| 외국인 환자 | 2024년 117만 명 → 2025년 201만 명 | 보건복지부 2025-04, 2026-04 |
|
||||
| 2025 진료과 | 피부과 62.9%, 성형외과 11.2% | 보건복지부 2026-04 |
|
||||
| 2025 국적 | 중국 30.8%, 일본 29.8%, 대만 9.2%, 미국 8.6% | 보건복지부 2026-04 |
|
||||
| 의원급·서울 비중 | 87.7%, 87.2% | 보건복지부 2026-04 |
|
||||
| 2026 상반기 외국인 카드 결제 | 1조4,203억 원(전년 대비 +38.6%) | 하나카드 데이터, 2026-08-27 |
|
||||
| 미용성형 부가세 환급 | 2025-12-31 종료. 재도입 법안이 국회 소위에 있음. 성형외과의사회는 종료 후 20~30% 감소를 주장(통계 아님) | 2025-07-31, 2026-08-14 |
|
||||
|
||||
시장은 커지고 있습니다. 다만 성장의 대부분은 피부과이고, 성형외과 비중은 11%입니다.
|
||||
|
||||
### 3-2. 경쟁 플랫폼
|
||||
|
||||
| 플랫폼 | 외국인 서비스 현황 | 규모·실적 |
|
||||
|---|---|---|
|
||||
| 강남언니 | 일본 2019, 영어 2023-12, 태국 2024 말, 중국·대만 2025-10. 6개 언어. 2026-03 논현동 오프라인 센터, 2026-05 8개 언어 통역(환자 무료). 일본 앱 예약 연 약 55만 건 | 해외 누적회원 148만, 해외 MAU 23만(2025-09). 매출 2025년 752억, 영업이익 75억(전년 대비 -41.3%), 광고선전비 343억(+100.6%) |
|
||||
| 바비톡 | 2025-08-25 일본어 웹. 영어·중국어는 시범 | 2025 매출 367.7억, 영업이익 73억(재무정보 사이트). 해외 사용자 미공개 |
|
||||
| 크리에이트립 | 12개 언어, 숙소 예약·통역 버디. **유치업 등록 사업자** | MAU 160만, 제휴 약 1,200곳. 2025 거래의 51%가 뷰티·메디컬. 누적 투자 181억 원 이상 |
|
||||
| 여신티켓 | 2024-01 영·일·중 글로벌 앱. 부작용 보험 | 글로벌 MAU 4만 이상, 국내 병원 약 5,000곳 |
|
||||
| BeautsGO | 5개 언어, 숙박 지원, AI 에이전트용 예약 스킬 배포(2026-03) | 제휴 1,300곳 이상 |
|
||||
| 메디컬코리아(KHIDI) | 공공 포털, 유치기관 조회, 영·일문 | |
|
||||
|
||||
### 3-3. 이 사업에 필요한 규제 요건 (변호사 확인 필요)
|
||||
|
||||
| 요건 | 내용 |
|
||||
|---|---|
|
||||
| 유치업 등록 | 의료해외진출법 제6조, 시행규칙 제4조 기준입니다. 자본금 1억 원, 보증보험 1억 원, 국내 사무소가 필요하고 3년마다 갱신합니다. 등록 후 2년간 실적이 없으면 등록취소 사유입니다 |
|
||||
| 미등록 유치 | 3년 이하 징역 또는 3천만 원 이하 벌금 |
|
||||
| 수수료 상한 | 의원 30%, 병원·종합병원 20%, 상급종합병원 15% |
|
||||
| 환자 알선 금지 | 의료법 27조 3항. 강남언니는 진료비 비율 수수료 모델로 1·2심 모두 유죄(징역 8월, 집행유예 2년)였고, 이후 광고형으로 전환했습니다 |
|
||||
| 외국인 유치 광고 | 의료법 56조 2항 12호가 국내 광고를 금지합니다. 의료해외진출법 제15조는 공항·보세판매장 등 지정 장소만 허용합니다. 온라인 다국어 매체에 어떻게 적용되는지는 미확인입니다 |
|
||||
| 사전심의 매체 | 일평균 이용자 10만 명 이상 인터넷매체 |
|
||||
| 민감정보·국외 이전 | 개인정보보호법 제23조, 제28조의8에 따른 별도 동의 |
|
||||
| 숙박·관광 직접 판매 | 여행업 등록(종합 5천만 원, 국내외 3천만 원) |
|
||||
|
||||
### 3-4. 경제성
|
||||
|
||||
| 항목 | 값 | 비고 |
|
||||
|---|---|---|
|
||||
| 강남언니 수익 모델 | 이벤트 페이지 조회당 과금(CPV) | 상담·내원과 무관하게 과금 |
|
||||
| 의료관광 예약 1건당 고객 확보 비용 | 600~1,800달러 이상 | 업체 블로그 수치, 실측 아님 |
|
||||
| 병원당 월 광고비, 운영 인력 구성 | 공신력 있는 공개 자료 없음 | |
|
||||
|
||||
---
|
||||
|
||||
## 4. 현재 조직으로 가능한 사업인가
|
||||
|
||||
### 4-1. 플랫폼이 요구하는 역량과 보유 역량
|
||||
|
||||
| 역량 | 플랫폼에 필요한 수준 | 현재 | 판정 |
|
||||
|---|---|---|---|
|
||||
| 병원 원문 생성·발행 | 병원당 자동 온보딩, 다국어 | 서포터즈 워커 동작. URL만으로 60~70% 품질. 병원 승인 0편 | **보유(개선 필요)** |
|
||||
| 다병원 허브 개발 | 계정·권한, 허브 도메인, 다국어 라우팅, 상담 폼, 운영 콘솔 | 개발팀장 1명, 주니어 5명. 기존 제품 유지와 병행 | 1층만 가능. 기간은 PRD보다 길어짐 |
|
||||
| 운영 | 병원 응대, 콘텐츠 검수, 이미지 육안 확인, 정정, 게이트 실패 처리 | **운영팀 없음.** 현재 감본부장 1인이 운영 | **부족** |
|
||||
| 외국인 고객 응대 | 영·일·중 상담, 예약 조율, 통역, 분쟁 대응(경쟁사는 24시간 채팅, 오프라인 센터) | 없음 | **불가** |
|
||||
| 수요 확보 | 해외 광고, 인플루언서, 현지 법인(강남언니 일본법인 순손실 46억) | 없음. AI 인용에만 의존 | **불가** |
|
||||
| 병원 공급 확보 | 병원 영업 조직(경쟁사 수천 곳) | 앵커 소개에 의존, 명단 미확인 | **부족** |
|
||||
| 규제·자본 | 유치업 법인, 자본금·보증보험 2억 원, 2년 내 실적, 민감정보 보안 | 없음 | 2층 이상 **불가** |
|
||||
|
||||
### 4-2. 판단
|
||||
|
||||
- **2층(상담·예약)과 3층(여정)은 현재 조직으로 할 수 없습니다.** 개발 인력의 문제가 아니라, 고객 응대·병원 영업·자본·규제 대응 조직이 없기 때문입니다.
|
||||
- **1층(정보 허브)만 따로 만드는 것은 기술적으로 가능합니다.** 그러나 운영 인력과 유입 채널이 없으면, 경쟁 앱이 이미 가진 다국어 정보와 차별점이 약합니다. 1층은 서포터즈 사이트를 병원 수만큼 늘린 것과 결과가 비슷합니다.
|
||||
- **기회비용이 큽니다.** 같은 인력으로 할 일이 있습니다.
|
||||
- AI Discovery 진단 판매
|
||||
- 서포터즈 자동화 품질을 60~70%에서 올리기
|
||||
- 병원 승인 흐름 만들기
|
||||
- 측정 리포트 자동화
|
||||
|
||||
이 일들은 유료 전환과 직접 이어집니다.
|
||||
- **따라서 결론은 "서포터즈 사이트까지"입니다.** 회의에서 나온 감본부장 판단과 같습니다.
|
||||
|
||||
### 4-3. 플랫폼을 다시 검토할 조건 (제안)
|
||||
|
||||
아래 조건을 모두 채우면 PRD 1층부터 재검토합니다.
|
||||
|
||||
1. 서포터즈를 유료로 계약한 병원 10곳 이상
|
||||
2. 병원 승인 글 기준으로 외국인 질문 뱅크 인용률 개선을 2곳 이상에서 확인
|
||||
3. 운영 인력 2명 이상 확보(병원 응대·콘텐츠 검수)
|
||||
4. 유치업 등록 사업자와 제휴해 상담·예약을 위탁하는 구조(직접 등록하지 않는 방식) 확인
|
||||
5. 병원 소개 명단의 계약 가능성 확인
|
||||
|
||||
---
|
||||
|
||||
## 5. 권고 범위: MOU · POC (8주)
|
||||
|
||||
### 5-1. 포함
|
||||
|
||||
| 번호 | 항목 | 산출물 | 비고 |
|
||||
|---|---|---|---|
|
||||
| 1 | 뷰성형외과 서포터즈 사이트 운영 (국문·영문) | viewpl.webforai.kr. 글, 영상, 뉴스룸, 방문 안내, 회복 일정 플래너, 주변 안내 | 배포 완료. 색인은 병원 승인 후 |
|
||||
| 2 | 병원 승인 흐름 | 원장 의학 검토·개설자 게시 승인 기록, 지원 관계 고지 문구 확정 | 색인 전환의 조건 |
|
||||
| 3 | 색인 전환 | 승인 글만 공개하는 재배포, IndexNow·서치콘솔 제출 | 법률 확인 2건 결과에 따름 |
|
||||
| 4 | AEO/GEO 기준선과 재측정 | 국문 120문항·영문 60문항 × ChatGPT·Perplexity. 착수 시·4주·8주 3회, 5회 반복 | 언급률과 신뢰구간으로만 보고 |
|
||||
| 5 | 공식 홈페이지 개선 가이드 | 9/1 POC 제안서 P0·P1 항목(스키마, NAP, 정의문, FAQ)의 적용 명세 | 적용은 병원 웹 담당. 우리가 직접 수정할지는 7장 안건 |
|
||||
| 6 | 측정 연결 | GA4 AI 유입 채널그룹, 서치콘솔, 가능하면 서버 로그 | 병원 계정 권한 필요 |
|
||||
| 7 | 결과 보고서와 홍보용 사례 | 8주 결과 보고서 1부, 사례 요약 1쪽 | MOU 홍보와 타 병원 영업에 사용 |
|
||||
|
||||
### 5-2. 제외 (MOU 문구에 명시)
|
||||
|
||||
- 다병원 의료관광 포털, 병원 비교·목록 서비스
|
||||
- 외국인 환자 상담·예약 중개, 통역, 픽업, 결제, 수수료
|
||||
- 숙박·관광 상품 판매
|
||||
- 앱 개발
|
||||
- 답변엔진 추가(Claude, Gemini, Google AI Overview). 유료 전환 시 옵션으로 제안
|
||||
- 공식 홈페이지 직접 개발·유지보수(5-1의 5번 결정에 따름)
|
||||
|
||||
### 5-3. 뷰성형외과가 제공할 것
|
||||
|
||||
| 항목 | 이유 |
|
||||
|---|---|
|
||||
| 원장 의학 검토와 개설자 게시 승인(주기·담당자 지정) | 승인 없이는 색인을 열 수 없습니다 |
|
||||
| 지원 관계 고지 문구 확정 | 추천·보증 심사지침 |
|
||||
| 홈페이지·영상·이미지 자산 사용 허락 범위 | 저작권·의료광고 책임 |
|
||||
| GA4·서치콘솔 권한 | 측정 항목 4개가 계정 없이는 미검증입니다 |
|
||||
| 사례 공개와 홍보 기사 사용 동의(병원명, 수치 공개 범위) | MOU의 목적 |
|
||||
| (선택) 소개 가능한 병원 명단 | 향후 확장 검토용. 이번 범위의 의무 아님 |
|
||||
|
||||
### 5-4. 비용
|
||||
|
||||
- **우리 쪽 직접 비용은 작습니다.** 측정 3회 × 5회 반복 기준으로 약 17만 원입니다(현행 2개 엔진). 사이트 호스팅과 빌드 비용은 병원당 소액입니다.
|
||||
- **실제 비용은 인력 시간입니다.** 병원 응대, 승인 수집, 보고서 작성에 감본부장 시간이 들어갑니다. POC 기간을 8주로 고정하는 이유입니다.
|
||||
|
||||
---
|
||||
|
||||
## 6. 원장의 포털 제안에 대한 답변 문안 (안)
|
||||
|
||||
> 원장님께서 말씀하신 글로벌 의료관광 포털 방향에 공감합니다. 다만 외국인 환자 상담·예약 중개는 유치업 등록과 다국어 응대 조직이 필요한 사업이라, 이번 협약에서는 뷰성형외과 서포터즈 사이트와 AI 답변 노출 측정으로 효과를 먼저 확인하려고 합니다. 8주 결과가 나오고 참여 병원이 늘어나면, 포털 단계는 그 결과를 근거로 다시 제안드리겠습니다.
|
||||
|
||||
---
|
||||
|
||||
## 7. 대표 보고 시 결정 요청 안건
|
||||
|
||||
| 번호 | 결정 사항 | 권고안 | 대안 |
|
||||
|---|---|---|---|
|
||||
| 1 | MOU·POC 범위 | 서포터즈 사이트 + 진단·측정(5-1) | PRD 1층 포함 |
|
||||
| 2 | 플랫폼 사업 추진 여부 | 이번에는 제외, 4-3 조건 충족 시 재검토 | PRD 1단계 착수 |
|
||||
| 3 | POC 비용 | 무상, 8주 고정, 홍보·사례 사용권을 대가로 | 유료 POC(최소 금액 제시) |
|
||||
| 4 | 공식 홈페이지 개선 | 가이드 제공, 적용은 병원 | 우리가 직접 수정(추가 인력 필요) |
|
||||
| 5 | 답변엔진 추가 | 현행 2개 유지, Claude는 유료 옵션 | Claude 포함 |
|
||||
| 6 | 색인 전환 전 법률 확인 | 사전심의·유치업 해당 여부 변호사 확인 후 전환 | 승인만 받고 전환 |
|
||||
| 7 | MOU 순서 | 범위 문서 합의 → MOU → 홍보 기사 | MOU 먼저, 범위는 부속서 |
|
||||
| 8 | 미팅 참석 | 문서 합의 후 실행 단계에서 참석 | 이번 조율 미팅부터 참석 |
|
||||
|
||||
## 8. 뷰성형외과 미팅에서 확인할 질문
|
||||
|
||||
1. MOU로 원하는 결과는 무엇입니까? 홍보, 외국인 환자 유입, 포털 참여 중 우선순위를 확인합니다.
|
||||
2. 원장 검토·승인은 누가, 어떤 주기로 할 수 있습니까?
|
||||
3. 홍보 기사에 병원명과 수치를 어디까지 공개할 수 있습니까?
|
||||
4. 공식 홈페이지 수정 권한과 담당자는 누구입니까?
|
||||
5. 소개 가능한 병원 명단, 병원 수, 현재 관계 단계는 어떻습니까?
|
||||
6. 포털을 원하신다면 비용 분담이나 투자 의향이 있습니까?
|
||||
|
||||
## 9. 다음 액션
|
||||
|
||||
| 날짜 | 할 일 | 담당 |
|
||||
|---|---|---|
|
||||
| 9/15(화) | 사이트 URL management 방 공유 | 감본부장 |
|
||||
| 9/16(수) | 이 문서 대표 보고, 7장 안건 결정 | 감본부장 → 대표 |
|
||||
| 9/17(목) | 결정안으로 범위 문서 확정, 황본부장 통해 뷰성형외과 전달 | 감본부장, 황본부장 |
|
||||
| 9/18(금) | 연구소 회의: 병원별 자동화 수준 시연(60~70% 구간 사례 포함) | 감본부장 |
|
||||
| 합의 후 | 변호사 확인 2건 요청, 병원 승인 요청서 발송 | 감본부장 |
|
||||
|
||||
---
|
||||
|
||||
## 부록. 출처
|
||||
|
||||
**내부 문서**
|
||||
- `docs/prd/INFINITH_Global_Medical_Hub_PRD_v0.2.html` (2026-09-09)
|
||||
- `docs/reports/viewclinic/02_poc_proposal/Viewclinic_POC_Proposal.pptx` (2026-09-01)
|
||||
- `docs/business/INFINITH_서포터즈_색인보류_사유보고_2026-09-14.docx` (메인 체크아웃)
|
||||
- `docs/reports/answer_engines/INFINITH_답변엔진_확장검토_2026-09-15.docx`
|
||||
|
||||
**시장·규제**
|
||||
- 보건복지부 2025 외국인환자 유치 실적: https://www.mohw.go.kr/board.es?mid=a10503010100&bid=0027&act=view&list_no=1490280 (2026-04)
|
||||
- 보건복지부 2024 실적: https://www.mohw.go.kr/board.es?mid=a10503000000&bid=0027&act=view&list_no=1485191 (2025-04)
|
||||
- 2026 상반기 외국인 카드 결제: https://www.medicaldaily.co.kr/post/22664 (2026-08-27)
|
||||
- 부가세 환급 종료 후 동향: https://www.hankyung.com/article/2026081424556 (2026-08-14)
|
||||
- 유치업 수수료 고시 제2024-79호: https://www.mohw.go.kr/boardDownload.es?bid=0026&list_no=1481343&seq=1 (2024-05-08. 현행 제2025-72호 본문은 미대조)
|
||||
- 의료해외진출법·시행령·시행규칙, 의료법, 개인정보보호법, 관광진흥법 시행령: 국가법령정보센터 원문(law.go.kr)
|
||||
- 강남언니 의료법 위반 1심: https://www.fnnews.com/news/202201271700056405 (2022-01-27)
|
||||
- 강남언니 의료법 위반 2심: https://www.fnnews.com/news/202307061827216638 (2023-07-06)
|
||||
- 대법원 2018도20928: https://www.scourt.go.kr/portal/news/NewsViewAction.work?seqnum=6637&gubun=4
|
||||
- 미등록 유치 병원 등록취소 소송: https://www.doctorsnews.co.kr/news/articleView.html?idxno=165135 (2026-06 판결 보도)
|
||||
|
||||
**경쟁 플랫폼**
|
||||
- 강남언니 글로벌 확장: https://zdnet.co.kr/view/?no=20251015091542 (2025-10-15)
|
||||
- 강남언니 글로벌 서비스 소개서: https://docs.channel.io/unni/ko/articles/%EA%B8%80%EB%A1%9C%EB%B2%8C-%EC%84%9C%EB%B9%84%EC%8A%A4-%EC%86%8C%EA%B0%9C%EC%84%9C--a34d448f
|
||||
- 강남언니 통역·예약: https://www.mt.co.kr/industry/2026/08/12/2026081210155697732 (2026-08-12)
|
||||
- 강남언니 통역 도입: https://zdnet.co.kr/view/?no=20260511103126 (2026-05-11)
|
||||
- 강남언니 실적: https://www.newsspace.kr/news/article.html?no=15572 (발행일 미확인)
|
||||
- 강남언니 개인정보 유출: https://www.swtvnews.com/news/newsview.php?ncode=1065595512012384 (2026-09-08)
|
||||
- 바비톡 일본 진출: https://www.venturesquare.net/1001286 (2025-08)
|
||||
- 크리에이트립 규모: https://www.asiatime.co.kr/article/20250516500383 (2025-05-17)
|
||||
- 크리에이트립 거래: https://www.mt.co.kr/future/2026/01/29/2026012908442998862 (2026-01-29)
|
||||
- 크리에이트립 유치업 등록: https://creatrip.com/en/blog/14800
|
||||
- 여신티켓 글로벌: https://www.enetnews.co.kr/news/articleView.html?idxno=39846
|
||||
- BeautsGO: https://www.beautsgo.com/ · https://github.com/BeautsGO/beautsgo-booking
|
||||
- AI 크롤러·llms.txt 현황: 각 사이트 robots.txt·sitemap·llms.txt 직접 조회 (2026-09-15)
|
||||
|
||||
**답변엔진 약관·단가**
|
||||
- Gemini API 추가 약관: https://ai.google.dev/gemini-api/terms
|
||||
- Claude web search: https://platform.claude.com/docs/en/agents-and-tools/tool-use/web-search-tool
|
||||
- 전체 단가 출처: 답변엔진 확장 검토 보고서 9장
|
||||
@ -1,469 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""뷰성형외과 MOU·POC 범위 검토 + 의료관광 허브 플랫폼 경쟁분석(상세) 워드 보고서 빌더.
|
||||
|
||||
원본 초안: 같은 폴더 INFINITH_뷰성형외과_MOU_POC범위_검토보고서_2026-09-15.md
|
||||
AI 답변 인용 집계(4-6): scripts/out/qb_*_results.jsonl(국문 120, 2026-09-01),
|
||||
docs/reports/viewclinic/05_global_en/qb_en_*_results.jsonl(영문 60, 2026-09-10)
|
||||
|
||||
python3 docs/business/build_mou_hub_competition_docx.py [--force]
|
||||
|
||||
주의: 2026-09-15 이후 haewon이 이 문서를 직접 편집했다. 이 빌더로 다시 만들면 편집이 사라지므로
|
||||
결과 파일이 있으면 멈춘다. 이후 변경은 patch_*.py 처럼 편집본에 직접 삽입한다.
|
||||
"""
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
HERE = Path(__file__).resolve().parent
|
||||
sys.path.insert(0, str(HERE.parent / "reports" / "answer_engines"))
|
||||
from build_engine_review_docx import Document, Cm, Pt, INDIGO, VIOLET, GRAY, bullets, heading, para, set_font, table # noqa: E402
|
||||
|
||||
OUT = HERE / "INFINITH_의료관광허브_경쟁분석_및_뷰성형외과_MOU범위_2026-09-15.docx"
|
||||
|
||||
|
||||
def h3(doc, text):
|
||||
heading(doc, text, 2)
|
||||
|
||||
|
||||
def note(doc, text):
|
||||
para(doc, text, 10, False, GRAY)
|
||||
|
||||
|
||||
def profile(doc, title, rows, implication):
|
||||
h3(doc, title)
|
||||
table(doc, ["항목", "내용"], rows, widths=[3.4, 13.6], size=9.5)
|
||||
p = doc.add_paragraph()
|
||||
p.paragraph_format.space_after = Pt(10)
|
||||
p.paragraph_format.line_spacing = 1.35
|
||||
set_font(p.add_run("허브 관점: "), 10.5, True)
|
||||
set_font(p.add_run(implication), 10.5)
|
||||
|
||||
|
||||
def build():
|
||||
doc = Document()
|
||||
sec = doc.sections[0]
|
||||
sec.page_width, sec.page_height = Cm(21.0), Cm(29.7)
|
||||
sec.left_margin = sec.right_margin = Cm(2.0)
|
||||
sec.top_margin = sec.bottom_margin = Cm(2.0)
|
||||
|
||||
# 표지
|
||||
para(doc, "INFINITH AI Discovery · 대표 보고", 11, True, VIOLET, 4)
|
||||
heading(doc, "의료관광 허브 플랫폼 경쟁분석과 뷰성형외과 MOU 범위 검토", 0)
|
||||
para(doc, "회의록 검토 · Global Medical Hub PRD v0.2 검토 · 경쟁분석(상세) · 조직 적합성 · MOU·POC 권고안", 12, False, INDIGO, 4)
|
||||
para(doc, "작성 2026-09-15(화) · 조사 기준일 2026-09-15 · 법 해석은 자문이 아니며 계약·등록 전 변호사 확인이 필요합니다", 10, False, GRAY, 14)
|
||||
|
||||
# 0. 결론
|
||||
heading(doc, "0. 결론")
|
||||
bullets(doc, [
|
||||
("이번 MOU·POC 범위는 서포터즈 사이트와 AEO/GEO 진단·측정까지로 한정할 것을 권고합니다. ", "글로벌 의료관광 플랫폼(다병원 허브, 상담·예약 중개, 숙박·관광 연계)은 이번 범위에서 명시적으로 제외합니다."),
|
||||
("플랫폼 사업은 현재 조직(개발팀장 1명, 주니어 개발자 5명, 운영팀 없음)으로 가능하지 않다고 판단합니다. ", "병목은 개발이 아니라 운영, 수요 확보, 자본·규제 대응입니다. 강남언니는 2025년 광고선전비 343억 원을 썼고, 오프라인 통역센터와 8개 언어 통역을 운영합니다."),
|
||||
("허브가 노리는 'AI가 인용하는 다국어 원문' 영역에는 이미 경쟁자가 있습니다. ", "외국인 질문 60문항 실측에서 Perplexity는 Bookimed(28문항), ClinicsOnCall(26), Korea Clinic Guide(20), ClinicSeoul(17), 강남언니 영문(14)을 인용했습니다. 광고 없는 영문 가이드 사이트가 120개 FAQ 형태로 운영 중입니다."),
|
||||
("PRD v0.2의 '기존 앱은 정보를 앱 안에 가둔다'는 전제는 일부만 맞습니다. ", "크리에이트립·여신티켓은 AI 크롤러 허용, llms.txt, 다국어 블로그를 운영합니다. 강남언니도 AI 검색봇을 허용하고 후기만 막습니다."),
|
||||
("MOU의 목적은 사례 확보와 홍보입니다. ", "무상 POC라면 제공 범위, 뷰성형외과가 제공할 것, 종료일을 문서로 고정해야 합니다. 대표 결정 안건 8건은 8장에 있습니다."),
|
||||
])
|
||||
|
||||
# 1. 회의록 검토
|
||||
heading(doc, "1. 회의록 검토 (2026-09-15)")
|
||||
h3(doc, "1-1. 확인된 사항")
|
||||
table(doc, ["구분", "내용", "담당"], [
|
||||
["뷰성형외과 내부 진행", "실무진이 원장 보고를 마쳤고 진행 결정이 났습니다", "뷰성형외과"],
|
||||
["진행 순서", "MOU → 홍보 기사 → 프로젝트 런칭", "양사"],
|
||||
["순서 이견", "대표는 MOU 우선. 뷰성형외과는 범위를 구체화한 뒤 MOU", "황본부장 조율"],
|
||||
["뷰성형외과 쪽 방향", "보고 이후 원장 쪽 방향(B2C 의료관광 포털)이 무게를 가짐", ""],
|
||||
["감본부장 입장", "비용 없는 플랫폼 POC면 미팅 불참, 문서로 범위 선정리. 유료 논의면 참석", "감본부장"],
|
||||
["범위 문서", "향후 1~2개월 할 일을 구체화해 대표 보고 후 뷰성형외과와 협의", "감본부장"],
|
||||
["황본부장 입장", "서포터즈는 기본 범위. 메디컬·버티컬 포털은 미결정. 1단계에서 큰 그림은 불필요", "황본부장"],
|
||||
["배포 현황", "webforai.kr 랜딩과 서포터즈 3곳 배포, 구글·네이버 서치콘솔 등록, 색인 보류", "감본부장"],
|
||||
["자동화 수준", "뷰성형외과 품질을 100으로 보면 URL만으로 다른 병원은 60~70%", "감본부장, 9/18 연구소 회의 시연"],
|
||||
], widths=[3.4, 10.4, 3.2])
|
||||
|
||||
h3(doc, "1-2. 문서에 옮기기 전에 바로잡을 발언")
|
||||
table(doc, ["회의 발언", "확인 결과", "문서에 쓸 표현"], [
|
||||
["답변엔진을 추가하면 한 번에 몇천 원이 몇만 원 단위로 오른다",
|
||||
"비용 방향은 맞습니다. 다만 Gemini 검색 그라운딩 약관은 결과의 분석·자동 수집을 금지하고, Google AI Overview는 공식 API가 없습니다. 비용만의 문제가 아닙니다",
|
||||
"현재 2개 엔진. Claude는 추가 가능, Gemini·AI Overview는 약관·수집 원칙 확인이 먼저"],
|
||||
["의료관광법상 기관 검증을 거치지 않은 실물 인물 콘텐츠를 배포하지 못해 색인을 안 했다",
|
||||
"해당 조문을 확인하지 못했습니다. 9/14 사유 보고 기준 보류 사유는 병원 승인 글 0편과 지원 관계 고지 문구 미확정입니다. 법률 확인은 사전심의·유치업 2건입니다",
|
||||
"병원 승인 글 0편, 고지 문구 미확정. 법률 확인 2건"],
|
||||
["강남언니·바비톡은 사용자 600만~천만, 병원 3천~5천 곳",
|
||||
"강남언니 해외 누적회원 148만·해외 MAU 23만(2025-09), 일본 제휴병원 1,500곳(2025-10). 바비톡 병원 3,300곳은 1차 출처 미확인",
|
||||
"4장 확인 수치"],
|
||||
], widths=[4.6, 7.6, 4.8], size=9)
|
||||
table(doc, ["답변엔진 구성 (120문항, 후처리 포함, 중앙값)", "1회 측정", "5회 반복"], [
|
||||
["현행 2개 (ChatGPT, Perplexity)", "약 ₩11,440", "약 ₩57,200"],
|
||||
["+ Claude", "약 ₩29,150", "약 ₩145,740"],
|
||||
["+ Claude, Gemini, Google AI Mode", "약 ₩35,790", "약 ₩178,950"],
|
||||
], widths=[9.0, 4.0, 4.0])
|
||||
note(doc, "회의록 음성 인식 표기: 비성형외과·비서약과 → 뷰성형외과, 김수원 원장 → 최순우 원장(대표자), 데미나이 → Gemini, AOG·AO → AEO·GEO, 인류관광 → 의료관광, 웹 포 AI → webforai.kr. '메트코'는 확인 필요.")
|
||||
|
||||
# 2. PRD 검토
|
||||
heading(doc, "2. Global Medical Hub PRD v0.2 검토")
|
||||
h3(doc, "2-1. PRD가 제안한 것")
|
||||
bullets(doc, [
|
||||
"3층 구조: 1층 정보·발견(다국어 병원 원문 허브), 2층 상담·예약(유치업 등록 후), 3층 회복기 여정(숙박·식사·관광 제휴 링크)",
|
||||
"구조: AIO2O 단독 소유, 뷰성형외과는 앵커 파트너(첫 병원, 병원 2~4곳 소개, 의료 자문)",
|
||||
"1단계(8~12주) 게이트: 병원 4곳 개설자 승인, 외국인 질문 뱅크 인용 기준선, 상담 신청 월 10건",
|
||||
], size=10.5)
|
||||
h3(doc, "2-2. 지금도 유효한 판단")
|
||||
bullets(doc, [
|
||||
"파이프라인 IP는 AIO2O 100%. 병원 자회사·합작법인 구조는 중립성과 IP에서 불리",
|
||||
"1단계는 수수료 없이 병원 직결. 대가를 받으면 유치업 등록 대상",
|
||||
"후기·전후 사진·비교·할인으로 설득하는 기능은 만들지 않음(의료법 56조)",
|
||||
], size=10.5)
|
||||
h3(doc, "2-3. 공백과 보완")
|
||||
table(doc, ["공백", "내용", "보완"], [
|
||||
["운영 인력 가정 없음", "비개발자 운영자를 전제했으나 운영팀이 없습니다. 게이트 실패·이미지 육안·채널 판정·정정·병원 응대·상담 전달은 사람 업무입니다", "운영 인원을 선행 조건으로"],
|
||||
["수요 확보 계획 없음", "AI 인용만으로 유입을 기대합니다. 경쟁 앱은 광고비·오프라인 센터로 수요를 확보합니다", "유입 채널·예산 추정"],
|
||||
["경쟁 전제 일부 오류", "앱들이 정보를 가둔다고 봤지만 크리에이트립·여신티켓·BeautsGO는 오픈웹·AI 대응을 합니다. 영문 가이드 사이트가 이미 AI 인용을 받습니다(4-6)", "차별점 재정의(4-7)"],
|
||||
["1단계 개발량", "§06 기준 신규 7건(외국인 질문 뱅크, 외국어 게이트, 다국어 라우팅·상담 폼, 다병원 허브 도메인, 계정·권한, 상담 알림, 측정 인프라)과 부분 2건(운영 콘솔, 상시 워커)", "현재 인력으로 8~12주는 낙관적"],
|
||||
["규제 변경 미반영", "2026-05-15 시행규칙 개정으로 여행업자 자본금 5천만 원 예외 삭제. 유치업은 자본금·보증보험 각 1억 원. 등록 후 2년 무실적이면 취소 사유. 2026-05-12부터 병원이 유치 경로 보고", "§05 갱신"],
|
||||
["민감정보 리스크", "2026-09-04~05 강남언니 22만 명(해외 약 6만 명) 민감정보 유출. 상담 폼만 받아도 건강정보 처리자가 됩니다", "최소 수집, 보안 체계 선행"],
|
||||
["앵커 소개 역량 미확인", "원장의 병원 네트워크 명단·수·관계 단계 미확인", "MOU 전 확인 질문"],
|
||||
["브랜드 리스크", "앵커 병원의 2026-07 공정위 시정명령 이력", "홍보 기사 문안 관리"],
|
||||
], widths=[3.2, 9.8, 4.0], size=9)
|
||||
|
||||
# 3. 시장
|
||||
heading(doc, "3. 시장 개요")
|
||||
table(doc, ["항목", "수치", "출처"], [
|
||||
["외국인 환자", "2024년 117만 명 → 2025년 201만 명(연환자 272만 명)", "보건복지부 2025-04, 2026-04"],
|
||||
["2025 진료과", "피부과 62.9%(131.3만 명), 성형외과 11.2%(23.3만 명), 내과 9.2%", "보건복지부 2026-04"],
|
||||
["2025 국적", "중국 30.8%, 일본 29.8%, 대만 9.2%, 미국 8.6%, 태국 약 2.9%", "보건복지부 2026-04"],
|
||||
["의원급·서울 비중", "의원급 87.7%, 서울 87.2%", "보건복지부 2026-04"],
|
||||
["2026 상반기 외국인 카드 결제", "1조4,203억 원(전년 대비 +38.6%), 피부과 7,685억 원", "하나카드 데이터 2026-08-27"],
|
||||
["정부 목표", "2027년 70만 명(2023 발표), 2024년 조기 달성. 새 목표 미확인", "보건복지부 2023-05-29"],
|
||||
["미용성형 부가세 환급", "2025-12-31 종료. 재도입 법안 국회 소위(2026-07-29). 성형외과의사회는 종료 후 20~30% 감소 주장(통계 아님)", "2025-07-31, 2026-08-14"],
|
||||
], widths=[3.6, 9.4, 4.0], size=9.5)
|
||||
note(doc, "시장은 커지고 있으나 성장의 중심은 피부과입니다. 성형외과 비중은 11%이고, 부가세 환급 종료의 영향은 공식 통계로 아직 확인되지 않았습니다.")
|
||||
|
||||
# 4. 경쟁분석
|
||||
heading(doc, "4. 경쟁분석 (상세)")
|
||||
h3(doc, "4-1. 경쟁 구도")
|
||||
table(doc, ["유형", "사업자", "핵심 자산", "허브(PRD 1층)와 겹치는 부분"], [
|
||||
["국내 미용의료 앱의 해외 확장", "강남언니, 바비톡, 여신티켓", "병원 수천 곳 제휴, 후기·가격 데이터, 광고 매출", "다국어 병원 정보, 상담·예약"],
|
||||
["외국인 여행·뷰티 예약 플랫폼", "크리에이트립, BeautsGO, Klook", "외국인 사용자, 숙박·관광 결합, 유치업 등록(크리에이트립)", "상담·예약, 회복기 여정"],
|
||||
["해외 의료관광 중개 사이트", "Bookimed, ClinicsOnCall, PlacidWay", "다국가 병원 비교, 무료 코디네이터, 병원 수수료", "영문 병원 정보, AI 인용"],
|
||||
["영문 한국 클리닉 가이드 사이트", "Korea Clinic Guide, ClinicSeoul, 개별 가이드 사이트 다수", "영문 원문·FAQ·가격 정보, 저비용 운영", "AI가 인용하는 다국어 원문. 허브의 핵심 가치와 직접 겹침"],
|
||||
["공공 포털", "메디컬코리아(KHIDI), 강남구 의료관광, 한국관광공사 영문", "공신력, 유치기관 조회, 무료", "병원 목록·안내"],
|
||||
["커뮤니티·후기", "Reddit, RealSelf", "실사용자 경험담. AI 답변의 주요 근거", "외국인 질문 답변(후기는 의료법상 허브가 만들 수 없음)"],
|
||||
], widths=[3.4, 3.8, 4.8, 5.0], size=9)
|
||||
|
||||
h3(doc, "4-2. 사업자별 프로필")
|
||||
profile(doc, "① 강남언니 (힐링페이퍼)", [
|
||||
["해외 출시", "일본 2019-11, 영어(UNNI) 2023-12, 태국 2024 말, 중국·대만 2025-10-15. 한·영·일·태·중(간체·번체) 6개 언어"],
|
||||
["규모", "해외 누적회원 148만, 해외 MAU 23만, 월 상담 약 1.3만 건(2025-09). 일본 사용자 160만·제휴병원 1,500곳(2025-10). 일본 앱 예약 연 약 55만 건(2026-08). 73개국 이용"],
|
||||
["성장", "태국 7개월 만에 예약자 10배(2025-06). 2026-07 기준 전년 대비 태국 13배, 중국 12배"],
|
||||
["오프라인·통역", "2026-03 논현동 언니가이드 센터 개소. 2026-05 오프라인 통역(8개 언어, 환자 무료). LINE·WeChat·WhatsApp 상담. 5개월 통역 1,600건"],
|
||||
["수익 모델", "이벤트 페이지 조회당 과금(CPV). 상담·내원과 무관하게 과금. 성과형 광고 상품 병행. 입점 병원에 유치의료기관 등록 요구"],
|
||||
["실적", "매출 2023 417억 / 2024 530억 / 2025 752억. 영업이익 122억 / 128억 / 75억(2025년 -41.3%). 2025 광고선전비 343억(+100.6%), 일본법인 순손실 46억"],
|
||||
["규제 이력", "진료비 비율 수수료 모델로 의료법 27조 3항 위반 1심(2022-01)·2심(2023-07) 징역 8월·집행유예 2년. 이후 광고형 전환. 2026-09-04~05 민감정보 219,665명 유출(해외 약 6만 명)"],
|
||||
["AI·오픈웹", "AI 검색봇(OAI-SearchBot·PerplexityBot·Claude-SearchBot) 허용, 후기(/reviews) 차단, 학습 크롤러 차단. hreflang 6개 언어. 영문 unni.app/us, US 병원 사이트맵 20건. llms.txt 없음"],
|
||||
], "외국인 수요와 병원 공급을 이미 확보한 선두 사업자입니다. 허브가 정보 허브로 경쟁하면 병원 수·후기·통역에서 비교가 되지 않습니다. 다만 영업이익이 줄고 개인정보 사고가 난 시점이라 병원들의 대안 채널 수요는 있을 수 있습니다.")
|
||||
profile(doc, "② 바비톡 (케어랩스 계열)", [
|
||||
["해외 출시", "일본어 웹 2025-08-25. 영어·중국어 이벤트는 CPV 무과금 시범. 중국·대만·영미권은 계획 단계"],
|
||||
["병원 입점", "유치의료기관 등록 필수. 이벤트 등록 시 구글 예약 자동 연동"],
|
||||
["실적", "2025 매출 367.7억, 영업이익 73억(재무정보 사이트, 게시일 미확인)"],
|
||||
["미확인", "병원 3,300곳·1,000만 다운로드(채용 플랫폼 소개문만 있음), 해외 사용자 수, 유치업 등록"],
|
||||
["AI·오픈웹", "robots.txt 전체 허용, /ja 사이트맵. /en 주소가 일본어를 보여줌. llms.txt 없음"],
|
||||
], "해외 확장 초기이며 일본 중심입니다. 구글 예약 연동처럼 외부 표면으로 나가는 방향이라, 영문·다국어 원문 영역에 들어올 가능성이 있습니다.")
|
||||
profile(doc, "③ 크리에이트립 (Creatrip)", [
|
||||
["포지션", "외국인 한국 여행 앱. 뷰티·의료 예약, 숙소 예약, 통역 버디(14일)"],
|
||||
["규모", "누적가입 94만(80개국, 2024-11), MAU 160만, 제휴 약 1,200곳, 12개 언어(사이트 15개 로케일)"],
|
||||
["거래", "2025 거래 +40%, 뷰티·메디컬 +71%로 거래의 51%. 대만은 의료 비중 49%"],
|
||||
["투자", "시리즈B까지 누적 181억 원 이상. 마이리얼트립 30억(2024-08)"],
|
||||
["규제", "외국인환자 유치업 등록번호 A-2019-01-01-3686(자사 공지)"],
|
||||
["AI·오픈웹", "전체 허용, llms.txt 공개, 'Best Seoul Skin Clinics 2026' 등 영문 블로그"],
|
||||
], "PRD의 2층(상담·예약)과 3층(여정)을 이미 합법 구조로 운영하는 사업자입니다. 허브가 2~3층으로 가면 정면 경쟁이고, 반대로 상담·예약을 위탁할 제휴 후보이기도 합니다.")
|
||||
profile(doc, "④ 여신티켓 (YeoTi)", [
|
||||
["해외 출시", "2024-01 영·일·중 글로벌 앱"],
|
||||
["규모", "글로벌 MAU 4만 이상, 2025 상반기 +173.4%. 국내 병원 약 5,000곳"],
|
||||
["차별 기능", "부작용 보험(1건당 최대 100만 원), 앱 구매"],
|
||||
["AI·오픈웹", "GPTBot·ClaudeBot·PerplexityBot 개별 허용(결제 경로만 차단, 2026-08-12 갱신). 영문 llms.txt, 영·일·중 블로그"],
|
||||
], "국내 병원 수가 크고 AI 크롤러 대응을 명시적으로 하는 사업자입니다. '앱은 AI에 닿지 않는다'는 전제를 쓸 수 없는 근거입니다.")
|
||||
profile(doc, "⑤ BeautsGO", [
|
||||
["포지션", "외국인 대상 한국 뷰티·클리닉 예약. 5개 언어, 숙박 지원"],
|
||||
["규모", "제휴 클리닉 1,300곳 이상. 운영사·투자 미확인"],
|
||||
["AI 대응", "AI 에이전트용 예약 스킬을 GitHub(2026-03-31)·Smithery·ModelScope에 배포"],
|
||||
], "AI 답변엔진을 넘어 AI 에이전트가 직접 예약하는 경로까지 준비하고 있습니다. 허브가 '인용'만 목표로 하면 한 단계 뒤처집니다.")
|
||||
profile(doc, "⑥ 해외 의료관광 중개 사이트: Bookimed · ClinicsOnCall · PlacidWay", [
|
||||
["Bookimed", "우크라이나 기반 다국가 의료관광 마켓플레이스. 환자 무료, 병원이 수수료(요율 비공개). 연간 거래액 2,210만 달러, 문의 17만 건(자사 보고서). 영문 한국 병원 페이지 운영"],
|
||||
["ClinicsOnCall", "영국(글래스고) 소재. 환자 무료 코디네이션, 프리미엄 옵션 $20·$50. 통역·픽업·숙박·비자·24시간 코디네이터. 러시아어 중심 6개 언어. 한국 클리닉 수 미확인"],
|
||||
["PlacidWay", "해외 의료관광 중개 사이트. 운영 주체·한국 병원 수는 이번 조사에서 미확인(사이트 응답 시간 초과)"],
|
||||
["AI 인용 실측", "영문 60문항 Perplexity 답변에서 Bookimed 28문항, ClinicsOnCall 26문항, PlacidWay 14문항 인용(2026-09-10)"],
|
||||
], "외국인이 영어로 물을 때 AI가 실제로 가져다 쓰는 출처입니다. 허브의 'AI 인용' 목표는 이 사이트들과 경쟁합니다. 이들은 다국가·다언어 코디네이터 조직을 이미 갖고 있습니다.")
|
||||
profile(doc, "⑦ 영문 한국 클리닉 가이드 사이트: Korea Clinic Guide · ClinicSeoul", [
|
||||
["Korea Clinic Guide", "한국 클리닉 537곳 이상 목록, 20개 이상 진료 분야, 블로그·후기·영상·여행 계획 가이드. 병원 노출 광고형으로 보임. 운영 주체 미확인"],
|
||||
["ClinicSeoul", "RiskIntel Corp 운영 독립 가이드. '클리닉 후원 0, 유료 순위 0'을 표방. 35개 이상 심층 가이드, 120개 FAQ, 검증 가격 정보"],
|
||||
["AI 인용 실측", "영문 60문항 Perplexity 답변에서 Korea Clinic Guide 20문항, ClinicSeoul 17문항 인용. ChatGPT에서도 ClinicSeoul 4문항"],
|
||||
], "PRD 1층이 만들려는 것(중립, 원문, FAQ, 비교·순위 없음)과 가장 가까운 형태가 소규모 영문 사이트로 이미 운영되고 있고, AI 인용도 받고 있습니다. 허브의 차별점은 '병원 승인 원문'과 '실측 측정'만 남습니다.")
|
||||
profile(doc, "⑧ 공공 포털과 여행 OTA", [
|
||||
["메디컬코리아(KHIDI)", "유치기관 등록 조회 시스템, 영문·일문 사이트. 공신력"],
|
||||
["강남구 의료관광", "medicaltour.gangnam.go.kr. 국문 120문항 Perplexity 답변 14문항, ChatGPT 3문항에서 인용"],
|
||||
["한국관광공사 영문", "english.visitkorea.or.kr. 영문 60문항 Perplexity 12문항, ChatGPT 5문항 인용"],
|
||||
["Klook", "강남언니(UNNI) 연계 한국 뷰티·스킨케어 클리닉 예약 상품(페이지 접속 불가로 검색 결과만 확인)"],
|
||||
], "공공 포털은 무료이고 공신력이 있어 AI가 인용합니다. 허브가 공공 데이터와 같은 목록형 정보를 만들면 차별점이 없습니다.")
|
||||
|
||||
h3(doc, "4-3. 기능 비교")
|
||||
table(doc, ["기능", "강남언니", "바비톡 JP", "크리에이트립", "여신티켓", "BeautsGO", "해외 중개", "영문 가이드", "INFINITH 허브 1단계"], [
|
||||
["다국어 병원 정보", "O(6)", "O(일)", "O(12)", "O(3)", "O(5)", "O", "O(영)", "O(영·일·중 계획)"],
|
||||
["후기", "O", "O", "O", "O", "O", "O", "O", "X(의료법)"],
|
||||
["가격 공개", "이벤트가", "O", "O", "O", "가격표", "견적", "O", "비용 항목만"],
|
||||
["상담·예약", "O", "O", "O", "O", "O", "O", "X", "상담 신청 병원 직결"],
|
||||
["통역", "O(동행)", "병원별", "O(버디)", "미확인", "미확인", "O", "X", "X"],
|
||||
["숙박·여정", "미확인", "미확인", "O", "미확인", "O", "O", "안내", "제휴 링크(3단계)"],
|
||||
["결제", "O", "미확인", "O", "O", "미확인", "미확인", "X", "X(2단계)"],
|
||||
["병원 승인 원문", "X", "X", "X", "X", "X", "X", "X", "O"],
|
||||
["AI 인용 실측·리포트", "X", "X", "X", "X", "X", "X", "X", "O"],
|
||||
], widths=[2.6, 1.6, 1.6, 1.8, 1.6, 1.6, 1.6, 1.6, 2.4], size=8)
|
||||
note(doc, "O: 제공 확인, X: 제공하지 않음 또는 해당 없음, 미확인: 공개 자료로 확인 못 함. 병원 승인 원문과 AI 인용 실측은 공개 자료에서 다른 사업자의 제공 사실을 찾지 못한 항목입니다.")
|
||||
|
||||
h3(doc, "4-4. 수익 모델과 규제 대응")
|
||||
table(doc, ["사업자", "수익 모델", "유치업 등록", "규제 리스크"], [
|
||||
["강남언니", "CPV 광고, 성과형 광고", "입점 병원에 유치의료기관 등록 요구. 자사 등록 미확인", "과거 수수료 모델 유죄, 민감정보 유출"],
|
||||
["바비톡", "CPV 광고(해외 일부 무과금 시범)", "입점 병원에 유치의료기관 등록 요구", "공개 사례 미확인"],
|
||||
["크리에이트립", "예약 거래(수수료 구조 미공개)", "등록(A-2019-01-01-3686)", "공개 사례 미확인"],
|
||||
["여신티켓", "시술권 판매·광고", "미확인", "공개 사례 미확인"],
|
||||
["Bookimed", "병원 수수료", "해외 법인. 한국 등록 미확인", "한국 병원 알선 시 등록 여부가 쟁점이 될 수 있음"],
|
||||
["영문 가이드 사이트", "광고 또는 무광고(운영 주체별)", "해당 없음(중개 안 함)", "낮음"],
|
||||
["INFINITH 허브(PRD)", "1단계 무수수료, 2단계 병원 구독·예약 수수료", "2단계 전 등록 필요(자본금·보증보험 각 1억 원)", "1단계는 낮음, 2단계부터 알선·광고·민감정보"],
|
||||
], widths=[3.0, 4.6, 4.8, 4.6], size=9)
|
||||
table(doc, ["규제 요건", "내용 (변호사 확인 필요)"], [
|
||||
["유치업 등록", "의료해외진출법 제6조, 시행규칙 제4조: 자본금 1억 원, 보증보험 1억 원, 국내 사무소, 3년 갱신. 2년 무실적이면 등록취소 사유(제24조)"],
|
||||
["미등록 유치", "3년 이하 징역 또는 3천만 원 이하 벌금(제28조). 2026-06 미등록 업자 경유 유치 병원의 등록취소가 법원에서 유지됨"],
|
||||
["수수료 상한", "의원 30%, 병원·종합병원 20%, 상급종합병원 15%(보건복지부 고시)"],
|
||||
["환자 알선 금지", "의료법 제27조 제3항. 건강보험 미가입 외국인 유치는 예외 허용, 국내 거주 외국인은 제외"],
|
||||
["외국인 유치 광고", "의료법 제56조 제2항 제12호, 의료해외진출법 제15조: 국내 광고는 공항·보세판매장 등 지정 장소만. 온라인 다국어 매체 적용 여부는 미확인"],
|
||||
["사전심의 매체", "의료법 제57조, 시행령 제24조: 일평균 이용자 10만 명 이상 인터넷매체"],
|
||||
["개인정보", "개인정보보호법 제23조(민감정보 별도 동의), 제28조의8(국외 이전)"],
|
||||
["여행업", "관광진흥법 시행령: 종합여행업 자본금 5천만 원, 국내외여행업 3천만 원"],
|
||||
], widths=[3.4, 13.6], size=9)
|
||||
|
||||
h3(doc, "4-5. AI 답변엔진·오픈웹 대응 (2026-09-15 직접 조회)")
|
||||
table(doc, ["사업자", "AI 크롤러", "llms.txt", "다국어 오픈웹 콘텐츠"], [
|
||||
["강남언니", "검색봇 허용, 후기 차단, 학습봇 차단", "없음", "hreflang 6개 언어, 영문 병원 사이트맵 20건"],
|
||||
["바비톡", "전체 허용", "없음", "일본어 사이트맵"],
|
||||
["크리에이트립", "전체 허용", "있음", "15개 로케일, 영문 클리닉 블로그"],
|
||||
["여신티켓", "GPTBot·ClaudeBot·PerplexityBot 개별 허용", "있음(영문)", "영·일·중 블로그"],
|
||||
["BeautsGO", "미확인", "미확인", "5개 언어 사이트맵, AI 에이전트 예약 스킬"],
|
||||
], widths=[3.0, 5.4, 2.6, 6.0], size=9)
|
||||
|
||||
h3(doc, "4-6. 실측: AI 답변은 어떤 출처를 인용했나")
|
||||
para(doc, "INFINITH가 뷰성형외과 진단에서 수집한 공식 API 답변의 인용 URL을 도메인별로 집계했습니다. 수치는 해당 도메인을 1회 이상 인용한 답변 수입니다. 1회 측정이며 소비자 앱 화면과 다를 수 있습니다.", 10.5)
|
||||
table(doc, ["출처", "국문 120문항 ChatGPT", "국문 120문항 Perplexity"], [
|
||||
["네이버 블로그", "0", "81"],
|
||||
["강남언니", "4", "45"],
|
||||
["하이닥", "0", "28"],
|
||||
["캐시닥", "3", "27"],
|
||||
["병원 공식(뷰)", "6", "27"],
|
||||
["강남구 의료관광", "3", "14"],
|
||||
["바비톡", "3", "7"],
|
||||
["여신티켓", "0", "3"],
|
||||
["인용 URL이 있는 답변 수", "43", "120"],
|
||||
], widths=[7.0, 5.0, 5.0], size=9.5)
|
||||
note(doc, "국문 측정 2026-09-01. Perplexity는 robots.txt로 AI 크롤러를 막는 네이버 블로그를 81개 답변에서 인용했습니다. 네이버 자산의 기여도를 0으로 본 기존 판단은 Perplexity에 한해 수정이 필요합니다.")
|
||||
table(doc, ["출처", "영문 60문항 ChatGPT", "영문 60문항 Perplexity", "유형"], [
|
||||
["병원 공식(뷰 영문)", "18", "38", "병원"],
|
||||
["Reddit", "16", "33", "커뮤니티"],
|
||||
["Bookimed", "4", "28", "해외 중개"],
|
||||
["ClinicsOnCall", "0", "26", "해외 중개"],
|
||||
["Korea Clinic Guide", "0", "20", "영문 가이드"],
|
||||
["ClinicSeoul", "4", "17", "영문 가이드"],
|
||||
["강남언니(unni.app)", "0", "14", "국내 앱"],
|
||||
["PlacidWay", "0", "14", "해외 중개"],
|
||||
["한국관광공사 영문", "5", "12", "공공"],
|
||||
["RealSelf", "5", "11", "커뮤니티"],
|
||||
["크리에이트립", "0", "0", "여행 플랫폼"],
|
||||
["바비톡", "0", "0", "국내 앱"],
|
||||
], widths=[5.0, 3.6, 3.8, 4.6], size=9.5)
|
||||
note(doc, "영문 측정 2026-09-10, 미국 뉴욕 위치 기준. 인용 URL 수 ChatGPT 448개, Perplexity 2,316개.")
|
||||
bullets(doc, [
|
||||
("국내 질문에서는 강남언니와 네이버 블로그가 인용을 가져갑니다. ", "허브가 국문으로 들어갈 여지는 작습니다."),
|
||||
("외국인 질문에서는 국내 앱보다 해외 중개 사이트와 영문 가이드 사이트가 인용됩니다. ", "크리에이트립·바비톡은 0건이었습니다. 이 영역은 대형 앱이 비어 있지만, 소규모 영문 사이트가 이미 채우고 있습니다."),
|
||||
("병원 공식 영문 사이트가 가장 많이 인용됩니다. ", "병원 사이트를 강화하는 서포터즈·AEO 작업이 허브를 만드는 것보다 인용에 직접 연결됩니다."),
|
||||
], size=10.5)
|
||||
|
||||
h3(doc, "4-7. INFINITH 허브 차별점 검증")
|
||||
table(doc, ["PRD의 차별점", "경쟁 현실", "평가"], [
|
||||
["AI가 인용할 다국어 원문이 오픈웹에 없다", "영문 가이드·해외 중개 사이트가 이미 인용받고 있습니다(4-6)", "약함"],
|
||||
["기존 앱은 정보를 앱 안에 가둔다", "크리에이트립·여신티켓은 llms.txt·블로그, 강남언니는 AI 검색봇 허용", "일부만 유효"],
|
||||
["자동 온보딩(병원당 30분, $0.6)", "경쟁사는 병원이 직접 입력하거나 영업이 입력. 우리 자동화 품질은 60~70% 구간 존재", "유효하나 품질 보완 필요"],
|
||||
["병원 승인 원문(의학 검토·게시 승인 기록)", "공개 자료에서 같은 체계를 찾지 못함", "유효"],
|
||||
["AEO/GEO 실측 진단과 인용 측정 리포트", "플랫폼들은 제공하지 않음. GEO 대행사는 제공(항목·방법 비공개가 많음)", "유효"],
|
||||
["중립(비교·순위 없음)", "ClinicSeoul이 '후원 0, 유료 순위 0'을 표방", "약함"],
|
||||
["회복 일정·주변 안내 여정", "크리에이트립·BeautsGO·해외 중개가 숙박·통역까지 제공", "약함(정보 수준)"],
|
||||
], widths=[5.0, 8.4, 3.6], size=9)
|
||||
note(doc, "유효한 차별점 두 가지(병원 승인 원문, 실측 측정)는 플랫폼 없이 서포터즈 사이트와 진단 제품으로도 제공할 수 있습니다.")
|
||||
|
||||
h3(doc, "4-8. 진입 장벽")
|
||||
table(doc, ["장벽", "선두 사업자 수준", "INFINITH 현재"], [
|
||||
["병원 공급", "수천 곳 제휴(강남언니 일본 1,500곳, 여신티켓 국내 5,000곳)", "서포터즈 3곳, 승인 0편"],
|
||||
["외국인 수요", "해외 MAU 23만(강남언니), MAU 160만(크리에이트립)", "0. AI 인용 기준선만 있음"],
|
||||
["마케팅 자본", "광고선전비 343억(강남언니 2025), 누적 투자 181억 원 이상(크리에이트립)", "미편성"],
|
||||
["운영 조직", "오프라인 센터, 8개 언어 통역, 24시간 다국어 상담", "운영팀 없음"],
|
||||
["규제 자격", "유치업 등록(크리에이트립), 입점 병원 유치기관 등록 요구", "미등록, 자본금·보증보험 2억 원 필요"],
|
||||
["데이터·보안", "민감정보 처리 체계(그래도 유출 사고 발생)", "상담 데이터 없음, 체계 없음"],
|
||||
], widths=[3.2, 8.0, 5.8], size=9)
|
||||
|
||||
h3(doc, "4-9. 시사점")
|
||||
bullets(doc, [
|
||||
"플랫폼 경쟁의 승부는 병원 수, 외국인 사용자 수, 통역·예약 운영입니다. 이 셋에서 INFINITH는 출발점이 0입니다.",
|
||||
"INFINITH가 유효하게 가진 것은 병원 승인 원문 체계와 AI 인용 실측입니다. 이 둘은 병원 단위 B2B 제품으로 파는 편이 수요와 맞습니다.",
|
||||
"외국인 AI 답변에서 병원 공식 영문 사이트가 가장 많이 인용되므로, 병원별 서포터즈·영문 AEO 개선이 허브보다 효과 경로가 짧습니다.",
|
||||
"상담·예약이 필요해지면 직접 등록하지 말고, 유치업 등록 사업자(예: 크리에이트립)와 제휴하는 구조를 먼저 검토합니다.",
|
||||
], size=10.5)
|
||||
|
||||
# 5. 조직 적합성
|
||||
heading(doc, "5. 현재 조직으로 가능한 사업인가")
|
||||
table(doc, ["역량", "플랫폼에 필요한 수준", "현재", "판정"], [
|
||||
["병원 원문 생성·발행", "병원당 자동 온보딩, 다국어", "서포터즈 워커 동작, URL만으로 60~70% 품질, 승인 0편", "보유(개선 필요)"],
|
||||
["다병원 허브 개발", "계정·권한, 허브 도메인, 다국어 라우팅, 상담 폼, 운영 콘솔", "팀장 1명·주니어 5명, 기존 제품 병행", "1층만 가능, 기간 연장"],
|
||||
["운영", "병원 응대, 검수, 이미지 육안, 정정, 게이트 실패 처리", "운영팀 없음", "부족"],
|
||||
["외국인 응대", "영·일·중 상담, 예약 조율, 통역, 분쟁", "없음", "불가"],
|
||||
["수요 확보", "해외 광고, 현지 마케팅", "없음, AI 인용에만 의존", "불가"],
|
||||
["병원 공급", "병원 영업 조직", "앵커 소개 의존, 명단 미확인", "부족"],
|
||||
["규제·자본", "유치업 법인, 자본금·보증보험 2억 원, 2년 내 실적, 민감정보 보안", "없음", "2층 이상 불가"],
|
||||
], widths=[3.0, 5.4, 5.6, 3.0], size=9)
|
||||
bullets(doc, [
|
||||
("2층(상담·예약)과 3층(여정)은 현재 조직으로 할 수 없습니다. ", "개발 인력이 아니라 응대·영업·자본·규제 조직이 없습니다."),
|
||||
("1층(정보 허브)만은 기술적으로 가능합니다. ", "그러나 운영과 유입 없이는 서포터즈 사이트를 병원 수만큼 늘린 것과 결과가 비슷하고, 4-6에서 본 영문 가이드 사이트와 차별점이 약합니다."),
|
||||
("기회비용이 큽니다. ", "같은 인력으로 AI Discovery 진단 판매, 서포터즈 자동화 품질 개선(60~70% 구간), 병원 승인 흐름, 측정 리포트 자동화를 할 수 있고, 이 일들은 유료 전환과 직접 이어집니다."),
|
||||
], size=10.5)
|
||||
h3(doc, "5-1. 플랫폼을 다시 검토할 조건 (제안)")
|
||||
bullets(doc, [
|
||||
"서포터즈 유료 계약 병원 10곳 이상",
|
||||
"병원 승인 글 기준 외국인 질문 뱅크 인용률 개선을 2곳 이상에서 확인",
|
||||
"운영 인력 2명 이상 확보(병원 응대·콘텐츠 검수)",
|
||||
"유치업 등록 사업자와 상담·예약 위탁 제휴 구조 확인",
|
||||
"앵커 소개 병원 명단의 계약 가능성 확인",
|
||||
], size=10.5)
|
||||
|
||||
# 6. 권고 범위
|
||||
heading(doc, "6. 권고 범위: MOU · POC (8주)")
|
||||
h3(doc, "6-1. 포함")
|
||||
table(doc, ["번호", "항목", "산출물", "비고"], [
|
||||
["1", "뷰성형외과 서포터즈 사이트 운영(국문·영문)", "viewpl.webforai.kr: 글, 영상, 뉴스룸, 방문 안내, 회복 일정 플래너, 주변 안내", "배포 완료, 색인은 승인 후"],
|
||||
["2", "병원 승인 흐름", "원장 의학 검토·개설자 게시 승인 기록, 지원 관계 고지 문구 확정", "색인 전환 조건"],
|
||||
["3", "색인 전환", "승인 글만 공개 재배포, IndexNow·서치콘솔 제출", "법률 확인 2건 결과에 따름"],
|
||||
["4", "AEO/GEO 기준선과 재측정", "국문 120·영문 60문항 × ChatGPT·Perplexity, 착수·4주·8주, 5회 반복", "언급률·신뢰구간으로만 보고"],
|
||||
["5", "공식 홈페이지 개선 가이드", "9/1 POC 제안서 P0·P1(스키마, NAP, 정의문, FAQ) 적용 명세", "적용 주체는 8장 안건"],
|
||||
["6", "측정 연결", "GA4 AI 유입 채널그룹, 서치콘솔, 가능 시 서버 로그", "병원 계정 권한 필요"],
|
||||
["7", "결과 보고서와 사례", "8주 결과 보고서, 사례 요약 1쪽", "MOU 홍보·타 병원 영업"],
|
||||
], widths=[1.2, 4.4, 7.6, 3.8], size=9)
|
||||
h3(doc, "6-2. 제외 (MOU 문구에 명시)")
|
||||
bullets(doc, [
|
||||
"다병원 의료관광 포털, 병원 비교·목록 서비스",
|
||||
"외국인 환자 상담·예약 중개, 통역, 픽업, 결제, 수수료",
|
||||
"숙박·관광 상품 판매, 앱 개발",
|
||||
"답변엔진 추가(Claude, Gemini, Google AI Overview). 유료 전환 시 옵션",
|
||||
"공식 홈페이지 직접 개발·유지보수(8장 안건 4 결정에 따름)",
|
||||
], size=10.5)
|
||||
h3(doc, "6-3. 뷰성형외과가 제공할 것")
|
||||
table(doc, ["항목", "이유"], [
|
||||
["원장 의학 검토와 개설자 게시 승인(주기·담당자 지정)", "승인 없이는 색인을 열 수 없습니다"],
|
||||
["지원 관계 고지 문구 확정", "추천·보증 심사지침"],
|
||||
["홈페이지·영상·이미지 자산 사용 허락 범위", "저작권·의료광고 책임"],
|
||||
["GA4·서치콘솔 권한", "측정 항목 4개가 계정 없이는 미검증"],
|
||||
["사례 공개와 홍보 기사 사용 동의(병원명, 수치 공개 범위)", "MOU의 목적"],
|
||||
["(선택) 소개 가능한 병원 명단", "향후 확장 검토용, 이번 범위의 의무 아님"],
|
||||
], widths=[8.5, 8.5], size=9.5)
|
||||
h3(doc, "6-4. 비용")
|
||||
bullets(doc, [
|
||||
("직접 비용은 작습니다. ", "측정 3회 × 5회 반복 기준 약 17만 원(현행 2개 엔진)입니다. 사이트 호스팅·빌드 비용은 병원당 소액입니다."),
|
||||
("실제 비용은 인력 시간입니다. ", "병원 응대, 승인 수집, 보고서 작성에 감본부장 시간이 들어갑니다. POC 기간을 8주로 고정하는 이유입니다."),
|
||||
], size=10.5)
|
||||
|
||||
# 7. 답변 문안
|
||||
heading(doc, "7. 원장의 포털 제안에 대한 답변 문안 (안)")
|
||||
para(doc, "원장님께서 말씀하신 글로벌 의료관광 포털 방향에 공감합니다. 다만 외국인 환자 상담·예약 중개는 유치업 등록과 다국어 응대 조직이 필요한 사업이라, 이번 협약에서는 뷰성형외과 서포터즈 사이트와 AI 답변 노출 측정으로 효과를 먼저 확인하려고 합니다. 8주 결과가 나오고 참여 병원이 늘어나면, 포털 단계는 그 결과를 근거로 다시 제안드리겠습니다.", 11, False, INDIGO, 10)
|
||||
|
||||
# 8. 결정 안건
|
||||
heading(doc, "8. 대표 보고 시 결정 요청 안건")
|
||||
table(doc, ["번호", "결정 사항", "권고안", "대안"], [
|
||||
["1", "MOU·POC 범위", "서포터즈 사이트 + 진단·측정(6-1)", "PRD 1층 포함"],
|
||||
["2", "플랫폼 사업 추진", "이번에는 제외, 5-1 조건 충족 시 재검토", "PRD 1단계 착수"],
|
||||
["3", "POC 비용", "무상, 8주 고정, 홍보·사례 사용권을 대가로", "유료 POC(최소 금액 제시)"],
|
||||
["4", "공식 홈페이지 개선", "가이드 제공, 적용은 병원", "직접 수정(추가 인력 필요)"],
|
||||
["5", "답변엔진 추가", "현행 2개 유지, Claude는 유료 옵션", "Claude 포함"],
|
||||
["6", "색인 전환 전 법률 확인", "사전심의·유치업 해당 여부 변호사 확인 후 전환", "승인만 받고 전환"],
|
||||
["7", "MOU 순서", "범위 문서 합의 → MOU → 홍보 기사", "MOU 먼저, 범위는 부속서"],
|
||||
["8", "미팅 참석", "문서 합의 후 실행 단계에서 참석", "이번 조율 미팅부터 참석"],
|
||||
], widths=[1.2, 3.8, 7.0, 5.0], size=9.5)
|
||||
|
||||
# 9. 질문
|
||||
heading(doc, "9. 뷰성형외과 미팅에서 확인할 질문")
|
||||
bullets(doc, [
|
||||
"MOU로 원하는 결과의 우선순위: 홍보, 외국인 환자 유입, 포털 참여",
|
||||
"원장 검토·승인 담당자와 주기",
|
||||
"홍보 기사에 병원명과 수치를 공개할 범위",
|
||||
"공식 홈페이지 수정 권한과 담당자",
|
||||
"소개 가능한 병원 명단, 병원 수, 현재 관계 단계",
|
||||
"포털을 원한다면 비용 분담이나 투자 의향",
|
||||
], size=10.5)
|
||||
|
||||
# 10. 액션
|
||||
heading(doc, "10. 다음 액션")
|
||||
table(doc, ["날짜", "할 일", "담당"], [
|
||||
["9/15(화)", "사이트 URL management 방 공유", "감본부장"],
|
||||
["9/16(수)", "이 문서 대표 보고, 8장 안건 결정", "감본부장 → 대표"],
|
||||
["9/17(목)", "결정안으로 범위 문서 확정, 뷰성형외과 전달", "감본부장, 황본부장"],
|
||||
["9/18(금)", "연구소 회의: 병원별 자동화 수준 시연(60~70% 구간 사례 포함)", "감본부장"],
|
||||
["합의 후", "변호사 확인 2건 요청, 병원 승인 요청서 발송", "감본부장"],
|
||||
], widths=[2.4, 10.6, 4.0], size=9.5)
|
||||
|
||||
# 부록
|
||||
heading(doc, "부록. 출처")
|
||||
groups = [
|
||||
("내부 자료", [
|
||||
"Global Medical Hub PRD v0.2: docs/prd/INFINITH_Global_Medical_Hub_PRD_v0.2.html (2026-09-09)",
|
||||
"뷰성형외과 POC 제안서: docs/reports/viewclinic/02_poc_proposal/Viewclinic_POC_Proposal.pptx (2026-09-01)",
|
||||
"서포터즈 색인 보류 사유 보고: docs/business/INFINITH_서포터즈_색인보류_사유보고_2026-09-14.docx",
|
||||
"답변엔진 확장 검토 보고서: docs/reports/answer_engines/INFINITH_답변엔진_확장검토_2026-09-15.docx",
|
||||
"AI 답변 인용 원자료: scripts/out/qb_openai_results.jsonl, qb_perplexity_results.jsonl (2026-09-01), docs/reports/viewclinic/05_global_en/qb_en_*_results.jsonl (2026-09-10)",
|
||||
]),
|
||||
("시장·규제", [
|
||||
"보건복지부 2025 외국인환자 유치 실적 https://www.mohw.go.kr/board.es?mid=a10503010100&bid=0027&act=view&list_no=1490280 (2026-04)",
|
||||
"보건복지부 2024 실적 https://www.mohw.go.kr/board.es?mid=a10503000000&bid=0027&act=view&list_no=1485191 (2025-04)",
|
||||
"2026 상반기 외국인 카드 결제 https://www.medicaldaily.co.kr/post/22664 (2026-08-27)",
|
||||
"부가세 환급 종료 후 동향 https://www.hankyung.com/article/2026081424556 (2026-08-14)",
|
||||
"유치 수수료 고시 제2024-79호 https://www.mohw.go.kr/boardDownload.es?bid=0026&list_no=1481343&seq=1 (2024-05-08, 현행 제2025-72호 본문 미대조)",
|
||||
"의료해외진출법·시행령·시행규칙, 의료법, 개인정보보호법, 관광진흥법 시행령: 국가법령정보센터 law.go.kr",
|
||||
"대법원 2018도20928 https://www.scourt.go.kr/portal/news/NewsViewAction.work?seqnum=6637&gubun=4",
|
||||
"강남언니 의료법 위반 1심 https://www.fnnews.com/news/202201271700056405 (2022-01-27), 2심 https://www.fnnews.com/news/202307061827216638 (2023-07-06)",
|
||||
"미등록 유치 병원 등록취소 소송 https://www.doctorsnews.co.kr/news/articleView.html?idxno=165135",
|
||||
]),
|
||||
("경쟁 사업자", [
|
||||
"강남언니 글로벌 확장 https://zdnet.co.kr/view/?no=20251015091542 (2025-10-15)",
|
||||
"강남언니 글로벌 서비스 소개서 https://docs.channel.io/unni/ko/articles/%EA%B8%80%EB%A1%9C%EB%B2%8C-%EC%84%9C%EB%B9%84%EC%8A%A4-%EC%86%8C%EA%B0%9C%EC%84%9C--a34d448f",
|
||||
"강남언니 CPV 소개서 https://docs.channel.io/unni/ko/articles/CPV%EA%B3%BC%EA%B8%88-%EB%B0%A9%EC%8B%9D-%EC%86%8C%EA%B0%9C%EC%84%9C--e045a4e3",
|
||||
"강남언니 태국 성장 https://www.news2day.co.kr/article/20250624500218 (2025-06-24)",
|
||||
"강남언니 통역·예약 https://www.mt.co.kr/industry/2026/08/12/2026081210155697732 (2026-08-12)",
|
||||
"강남언니 통역 도입 https://zdnet.co.kr/view/?no=20260511103126 (2026-05-11)",
|
||||
"강남언니 실적 https://www.newsspace.kr/news/article.html?no=15572 (발행일 미확인)",
|
||||
"강남언니 개인정보 유출 https://www.swtvnews.com/news/newsview.php?ncode=1065595512012384 (2026-09-08)",
|
||||
"바비톡 일본 진출 https://www.venturesquare.net/1001286 (2025-08)",
|
||||
"바비톡 글로벌 서비스 https://docs.channel.io/f47c5d3a/ko/articles/%EB%B0%94%EB%B9%84%ED%86%A1-%EA%B8%80%EB%A1%9C%EB%B2%8C-%EC%84%9C%EB%B9%84%EC%8A%A4--5dc781ba",
|
||||
"크리에이트립 규모 https://www.asiatime.co.kr/article/20250516500383 (2025-05-17), 거래 https://www.mt.co.kr/future/2026/01/29/2026012908442998862 (2026-01-29), 유치업 등록 https://creatrip.com/en/blog/14800",
|
||||
"크리에이트립 투자 https://zdnet.co.kr/view/?no=20240814174433 (2024-08-14)",
|
||||
"여신티켓 글로벌 https://www.enetnews.co.kr/news/articleView.html?idxno=39846",
|
||||
"BeautsGO https://www.beautsgo.com/ · https://github.com/BeautsGO/beautsgo-booking",
|
||||
"Bookimed 보고서 https://us-uk.bookimed.com/b2b-article/medical-tourism-report-bookimed-platform/",
|
||||
"ClinicsOnCall https://clinicsoncall.com (2026-09-15 조회)",
|
||||
"Korea Clinic Guide https://koreaclinicguide.com (2026-09-15 조회)",
|
||||
"ClinicSeoul https://clinicseoul.net (2026-09-15 조회)",
|
||||
"메디컬코리아 https://www.medicalkorea.or.kr/korp/main.do",
|
||||
"AI 크롤러·llms.txt: 각 사이트 robots.txt·sitemap·llms.txt 직접 조회 (2026-09-15)",
|
||||
]),
|
||||
]
|
||||
for title, items in groups:
|
||||
h3(doc, title)
|
||||
for it in items:
|
||||
p = doc.add_paragraph()
|
||||
p.paragraph_format.space_after = Pt(2)
|
||||
set_font(p.add_run(it), 9, False, GRAY)
|
||||
|
||||
if OUT.exists() and "--force" not in sys.argv:
|
||||
sys.exit(f"{OUT.name} 이 이미 있어 멈춥니다(haewon 편집본 보호). 새로 만들려면 --force")
|
||||
doc.save(OUT)
|
||||
print(OUT)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
build()
|
||||
@ -1,190 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""haewon 편집본에 '4-10. 병원·의료 AEO/GEO 서비스 경쟁분석'과 부록 출처를 편집본에 직접 삽입한다.
|
||||
|
||||
문서를 다시 만들지 않는다. 기존 문단·표·서식은 그대로 두고, '5. 현재 조직으로 가능한 사업인가' 제목 앞에
|
||||
새 절을, 문서 끝(부록 출처 마지막)에 출처 묶음을 끼워 넣는다. '4-10.' 제목이 이미 있으면 아무것도 하지 않는다.
|
||||
조사 기준일 2026-09-15.
|
||||
|
||||
python3 docs/business/patch_aeo_geo_competition_docx.py
|
||||
"""
|
||||
import copy
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
HERE = Path(__file__).resolve().parent
|
||||
sys.path.insert(0, str(HERE.parent / "reports" / "answer_engines"))
|
||||
from build_engine_review_docx import Document, Pt, GRAY, bullets, heading, para, set_font, table # noqa: E402
|
||||
|
||||
TARGET = HERE / "INFINITH_의료관광허브_경쟁분석_및_뷰성형외과_MOU범위_2026-09-15.docx"
|
||||
ANCHOR = "5. 현재 조직으로 가능한 사업인가"
|
||||
MARK = "4-10. 병원·의료 AEO/GEO 서비스 경쟁분석"
|
||||
|
||||
|
||||
def text_of(el):
|
||||
return "".join(x.text or "" for x in el.iter() if x.tag.endswith("}t"))
|
||||
|
||||
|
||||
def h3(doc, t):
|
||||
heading(doc, t, 2)
|
||||
|
||||
|
||||
def note(doc, t):
|
||||
para(doc, t, 10, False, GRAY)
|
||||
|
||||
|
||||
def section(doc):
|
||||
h3(doc, MARK)
|
||||
para(doc, "플랫폼이 아니라 INFINITH가 지금 팔 수 있는 제품(AI Discovery 진단 + 서포터즈 사이트)이 실제로 경쟁하는 시장입니다. 국내 병원 특화 대행, 국내 범용 GEO SaaS, 해외 SaaS, 해외 의료 마케팅 에이전시를 각 사이트·보도자료에서 직접 확인했습니다. '미확인'은 공개 페이지에서 찾지 못했다는 뜻이며 기능이 없다는 뜻이 아닙니다.", 10.5)
|
||||
|
||||
h3(doc, "4-10-1. 시장 구도")
|
||||
table(doc, ["유형", "대표 사업자", "측정 방식", "가격대"], [
|
||||
["설문형 무료 진단", "이움코퍼레이션", "병원 담당자 설문 7문항, AI 엔진 질의 없음", "진단 무료, 대행 상담 후 제안"],
|
||||
["국내 병원 특화 대행", "마하닥-GEO, 아워마케팅, 크리앤서, 지오랭크, 와이더뷰티, BizSpring GEO Care for Clinic", "사이트 감사 + AI 엔진 질의(방식·문항 수 공개 수준이 업체마다 다름)", "월 29만~500만 원"],
|
||||
["국내 병원 대상 SaaS", "NATANA, 온더AI", "주간 또는 상시 AI 질의, 지표 정의 공개", "월 24만~250만 원"],
|
||||
["국내 범용 GEO", "서치폴라리스, 에이넥트, 팀하이, 블루닷, NNT Insights, RanketAI, FrostAI, 오픈타임, ZESTCOMPANY, Anymorph", "대행형은 진단+실행, SaaS는 프롬프트 추적", "월 7.9만~2,430만 원"],
|
||||
["해외 SaaS", "OtterlyAI, Profound, Peec AI, HubSpot AEO, Semrush, Ahrefs Brand Radar", "프롬프트 일간 추적, 일부 사이트 감사. 의료 특화 기능 미확인", "월 $29~$489"],
|
||||
["해외 의료 마케팅 에이전시", "Fuel Online, ADvance Media, Percepture, Specialist Practice Excellence, Plastic SEO", "코성형 등 시술별 AI visibility audit, 무료 프롬프트 배포", "월 $3,000~20,000+(Percepture)"],
|
||||
], widths=[3.0, 5.4, 5.2, 3.4], size=9)
|
||||
|
||||
h3(doc, "4-10-2. 국내 병원 특화 사업자")
|
||||
table(doc, ["사업자", "진단·측정", "방법론 공개", "국내 표면·의료법", "실행 범위", "가격", "성과 주장과 근거"], [
|
||||
["이움코퍼레이션", "설문 7문항(14점). AI 질의 없음", "문항만", "미확인", "FAQ·Schema·권위도", "상담 후", "AI 검색 이용 +68%, 재계약률 98%. 출처 없음. '200+ 병원' 실명 없음"],
|
||||
["마하닥-GEO", "AI 노출 지수 무료 진단. 항목 수 미확인", "미공개", "미확인", "JSON-LD, EEAT 콘텐츠, 홈페이지 제작", "미확인", "소아청소년과 인용 0→월 15회+, 피부과 객단가 +35%. 측정 방법 없음. '한국 1위' 근거 없음. FAQ에 자사 추천 문구"],
|
||||
["아워마케팅", "12개국 × 7개 플랫폼(ChatGPT·Claude·Gemini·Perplexity·AI Overview·AI Mode). 'API가 아닌 실제 Chrome + ISP 프록시' 화면 수집", "샘플 리포트만", "의료광고심의 가이드 메뉴", "다국어 콘텐츠, 전환 페이지, PR", "미확인", "한방 클리닉 SOV 90%+, 한의원 AI 봇 방문 462회. 레퍼런스 비공개"],
|
||||
["크리앤서", "공개 체크리스트 7항목. 진단 방식 미공개", "체크리스트만", "네이버 큐 포함. FAQ에 의료법·심의 이해 명시", "블로그, 검색광고, 플레이스, 홈페이지", "미확인", "성형외과 AI 노출 12%→78%. 측정 방법 미공개. 블로그 사례는 '가상 예시' 명시"],
|
||||
["지오랭크", "사이트 감사 7항목 0~100점 + 4개 LLM 매일 5회 새 세션", "항목·주기 공개", "미확인", "자사 도메인 발행, JSON-LD", "병의원 월 500만 원, 3개월 내 개선 없으면 50% 환불", "강남 S 피부과 ChatGPT 인용률 +280%, 예약 +42%. '자체 측정' 명시, 익명"],
|
||||
["NATANA", "ChatGPT·Gemini 매주. 페르소나 5~10명, 프롬프트 50~100개 반복. 무료 진단 5문항", "지표 6종·원문 보관 공개", "의료 표현은 고객 확인 후 실행", "수정안·초안. 발행은 Managed만", "월 24만·49만(출시 예정), Managed 250만부터", "해외 사례 인용 후 '동일 성과 보장 안 함' 명시"],
|
||||
["온더AI", "ChatGPT·Gemini·Perplexity 모니터링", "지표만", "블로그에 효과 단정·경험담 금지 안내", "콘텐츠 자동 생성, 서브도메인 자동 발행", "월 50만(50 프롬프트)·150만(200), 3개월 결제", "성모치과의원 SoV 26%→52%. 산출 방법 미공개. 실명 1곳"],
|
||||
["와이더뷰티", "미확인", "미확인", "의료광고 법률 자문 지원", "GEO·SEO, 브랜딩, 숏폼, 광고", "미확인", "미확인"],
|
||||
["BizSpring GEO Care for Clinic", "ChatGPT·Perplexity 기준 SoM", "미확인", "네이버 블로그·카카오채널 배포, 원장 승인 절차", "블로그·카카오 배포", "초기 49만+월 29만 / 초기 150만+월 100~150만", "'의료 사례 데이터 축적 중', 피부과 사례는 '예시 시나리오' 명시"],
|
||||
], widths=[2.2, 3.2, 1.6, 2.4, 2.4, 2.2, 3.0], size=7.5)
|
||||
|
||||
h3(doc, "4-10-3. 국내 범용 GEO 사업자")
|
||||
table(doc, ["사업자", "유형", "측정 방식", "국내 표면·의료법", "가격", "성과 주장 / 병원 사례"], [
|
||||
["서치폴라리스", "대행", "진단 '200개 항목'(목록 미공개). 4개 AI 엔진 + Google·Bing·Naver 검색", "브리핑 명시 없음", "스프린트 890만(4주), 월 890만, 엔터프라이즈 월 2,430만", "평균 언급률 +300% 산출법 미공개. 밝은눈안과 ChatGPT Top 3. 자사 1위 순위 글 게시"],
|
||||
["에이넥트", "진단 컨설팅", "4개 엔진 SoA·인용률·AVI, AID-Q 질문 설계", "미확인", "미확인", "병원 사례 미확인"],
|
||||
["팀하이", "SaaS", "ChatGPT·Gemini 감성 분석, GA4 연동", "미확인", "월 9.9만·49.5만", "병원 사례 미확인"],
|
||||
["블루닷", "SaaS+컨설팅", "DUCA 프레임워크, 문항 수 미확인", "미확인", "온보딩 490만부터, 3개월 3,000만부터", "평균 AI 인용 +65% 근거 미공개. 삼성서울병원 진단 글은 본문 404"],
|
||||
["NNT Insights", "SaaS", "ChatGPT·Gemini·Perplexity(Claude 옵션). 사용량 공식 공개", "네이버 AI 브리핑·AI Overview 스크린샷 수집(주 1회)", "월 50만·125만·250만", "병원 사례 미확인"],
|
||||
["오픈타임", "진단+대행", "기술 감사 배점 공개(robots 10, JSON-LD 15, llms.txt 5 등), 5개 엔진", "네이버 AI 브리핑 관측 베타", "미확인", "병원 사례 미확인"],
|
||||
["ZESTCOMPANY", "대행", "4개 엔진 + 네이버 AI 비교", "식약처·의료법 API로 주의 표현 점검", "3개월 단위, 금액 미확인", "안과 글은 현황 분석, 고객 성과 아님"],
|
||||
["RanketAI · FrostAI · Anymorph", "SaaS", "RanketAI 페이지 구조 5기준+3엔진, FrostAI 3엔진(월 7.9만부터), Anymorph 8엔진(월 110만부터)", "Anymorph 'Naver AI' 표기, 세부 미확인", "위 기재", "병원 사례 미확인"],
|
||||
], widths=[2.4, 1.6, 4.2, 3.0, 2.8, 3.0], size=8)
|
||||
|
||||
h3(doc, "4-10-4. 해외 SaaS와 의료 마케팅 에이전시")
|
||||
table(doc, ["사업자", "측정 방식", "의료·규제", "가격"], [
|
||||
["OtterlyAI", "ChatGPT·AI Overview·Perplexity·Copilot. GEO Audit 25+ 요인(크롤링·콘텐츠·Query Fan-Out)", "의료 특화 미확인", "$29(15)·$189(100)·$489(400 프롬프트)"],
|
||||
["Profound", "매일 추적. Starter는 ChatGPT만, Growth는 3엔진", "미확인", "$99·$399(연간)"],
|
||||
["Peec AI", "6엔진 중 3개 선택, 매일", "미확인", "€70·€180·€360(리뷰 사이트 기준)"],
|
||||
["HubSpot AEO", "ChatGPT·Gemini·Perplexity, 무료 Grader 1회 진단", "미확인", "$50/월(25 프롬프트)"],
|
||||
["Semrush · Ahrefs", "Semrush AI Visibility Toolkit(AI Mode·ChatGPT 등), Ahrefs 457M+ 프롬프트 DB", "미확인", "Semrush $99/월, Ahrefs $50~199/월"],
|
||||
["Fuel Online", "코성형 전용 AI visibility audit. mention rate·citation share(본문 403, 검색 스니펫 기준)", "미확인", "미확인"],
|
||||
["ADvance Media", "5개 AI 표면, 월 약 1,000단어 콘텐츠 + 소수 AI 질의 재확인", "HIPAA 준수 CRM 언급", "미공개"],
|
||||
["Percepture", "진단에 AI 인용 여부 포함(GSC·Screaming Frog·Semrush)", "'compliance awareness' 수준", "성형 SEO 월 $3,000~5,500, SEO+GEO+PR 월 $8,000~20,000+, 기술 감사 $2,500부터"],
|
||||
["Specialist Practice Excellence", "무료 마스터 프롬프트 공개(판매형 audit 아님)", "프롬프트에 AHPRA·FTC 등 규제 검토 지시", "해당 없음"],
|
||||
], widths=[3.2, 7.0, 3.2, 3.6], size=8.5)
|
||||
|
||||
h3(doc, "4-10-5. 비교 축별 INFINITH 위치")
|
||||
table(doc, ["비교 축", "경쟁사 수준 (대표 사례)", "INFINITH", "평가"], [
|
||||
["진단 기준 공개", "서치폴라리스 200항목 목록 비공개, 지오랭크 7항목, 오픈타임 배점 일부, Otterly 25+ 요인", "36항목, 항목별 0~3 레벨 기준과 판정 근거 URL 공개", "우위"],
|
||||
["자동·수동 구분, 미검증 처리", "공개 사례 없음", "자동 22·반자동 5·수동 9, 미검증은 분모 제외", "우위"],
|
||||
["답변 실측 방식", "NATANA 주간 50~100문항 반복, 지오랭크 4엔진 매일 5회, 아워마케팅 12개국×7플랫폼 브라우저 수집, NNT 브리핑 스크린샷", "120문항(국문)·60문항(영문) × ChatGPT·Perplexity 공식 API, 현재 1회", "엔진 수·빈도 열위. 공식 API 방식은 약관 측면에서 보수적"],
|
||||
["답변 품질 판정", "NATANA 정확률, 팀하이·HubSpot 감성", "gpt-4.1 어조 판정(기준표 v0.3)", "동등"],
|
||||
["국내 표면", "네이버 AI 브리핑 측정: NNT, 오픈타임(베타), ZEST. 강남언니 언급: 조사 대상 0곳", "강남언니·네이버 플레이스·GBP를 진단 항목(D1·D2·D5)으로 실측. 네이버 AI 브리핑 답변은 측정 안 함", "강남언니 우위, 브리핑 열위"],
|
||||
["의료법 56조 처리 위치", "안내문(온더AI), 법률 자문(와이더뷰티), API 표현 점검(ZEST), 원장 승인 절차(BizSpring)", "발행 게이트가 금지 유형을 코드로 차단, 원장 의학 검토·개설자 승인 기록이 있어야 공개", "우위"],
|
||||
["실행 산출물", "초안 전달(NATANA), 서브도메인 자동 발행(온더AI), 블로그·카카오 배포(BizSpring), 홈페이지 제작(마하닥)", "병원 승인 원문 기반 서포터즈 사이트 자동 빌드(병원당 약 30분·$0.6). URL만으로는 60~70% 품질 구간 존재", "우위(품질 보완 필요)"],
|
||||
["성과 검증 가능성", "익명·가상 예시·자체 측정이 대부분. 실명은 성모치과의원(온더AI), 밝은눈안과(서치폴라리스) 정도", "뷰성형외과 실명 기준선(같은 질문 세트로 전후 비교 설계). 재측정 전", "잠재 우위(POC 결과 필요)"],
|
||||
["일간 대시보드·셀프서브", "SaaS 대부분 제공", "없음", "열위"],
|
||||
["가격 구조", "월 7.9만~890만 원, 환불 조건(지오랭크 50%)", "미확정(9/8 제안 초안: 진단 49만, 서포터즈 월 99만·199만)", "결정 필요"],
|
||||
], widths=[2.8, 6.0, 5.6, 2.6], size=8.5)
|
||||
|
||||
h3(doc, "4-10-6. 이 시장의 공통 패턴")
|
||||
bullets(doc, [
|
||||
("성과 수치는 공개하고 산출 방법은 공개하지 않습니다. ", "크리앤서 12%→78%, 서치폴라리스 +300%, 블루닷 +65%, 마하닥 사례가 해당합니다. 사용량 공식이나 원문 보관을 공개한 곳은 NNT Insights와 NATANA 두 곳입니다."),
|
||||
("무료 진단의 다음 단계가 연락처 수집입니다. ", "설문형(이움)과 사이트 감사형(지오랭크 Scanner) 무료 진단은 실제 AI 답변을 측정하지 않습니다."),
|
||||
("자사를 추천하는 콘텐츠를 스스로 게시합니다. ", "마하닥 FAQ, 서치폴라리스·Anymorph의 업체 순위 글, 지오랭크 홈의 ChatGPT 답변 캡처가 해당합니다."),
|
||||
("국내 표면 반영이 약합니다. ", "강남언니를 언급한 곳은 없고, 네이버 AI 브리핑을 측정하는 곳은 범용 SaaS 3곳뿐입니다. 병원 특화 업체에서는 확인되지 않았습니다."),
|
||||
("의료법 대응은 선언 수준입니다. ", "56조나 사전심의를 발행 공정에 넣었다고 밝힌 곳은 없습니다. 병원 레퍼런스는 대부분 익명이거나 예시입니다."),
|
||||
], size=10)
|
||||
|
||||
h3(doc, "4-10-7. 시사점")
|
||||
bullets(doc, [
|
||||
("INFINITH가 실제로 경쟁하는 곳은 이 시장입니다. ", "사업자는 많지만, 기준·방법론 공개와 의료법의 공정 반영에서 INFINITH보다 앞선 곳을 찾지 못했습니다. 플랫폼 경쟁(4-1~4-9)과 달리 출발점이 앞서 있습니다."),
|
||||
("뷰성형외과 POC의 가치는 실명 전후 측정 사례입니다. ", "경쟁사 레퍼런스가 대부분 익명·예시인 시장에서, 같은 질문 세트로 측정한 실명 결과는 드뭅니다. MOU 홍보 포인트를 '실측 기준선과 8주 재측정'으로 잡을 것을 권고합니다."),
|
||||
("보완할 약점은 세 가지입니다. ", "① 네이버 AI 브리핑 답변 측정(국내 병원이 먼저 묻는 표면), ② 반복 측정(현재 1회, 5회 반복으로 신뢰구간 보고), ③ 가격 확정."),
|
||||
("비교 광고 표현에 주의합니다. ", "브라우저 수집 방식이나 성과 산출법을 문제로 삼아 경쟁사를 지목하면 표시광고법상 비교·비방 광고 문제가 될 수 있습니다. 외부 자료에는 'INFINITH는 공식 API로 측정하고 기준을 공개합니다'처럼 우리 방식만 서술합니다."),
|
||||
("랜딩 문구 방향과 일치합니다. ", "'36개 항목, 항목마다 측정 근거를 붙여 진단'이 이 시장에서 확인된 차별점(기준 공개)과 맞습니다."),
|
||||
], size=10)
|
||||
note(doc, "조회일 2026-09-15. 가격·기능은 각 사 공개 페이지 기준이며 변경될 수 있습니다. Fuel Online은 본문 403, 블루닷 삼성서울병원 글은 404로 제목만 확인했습니다. MindScope GEO는 공식 사이트를 찾지 못했습니다.")
|
||||
|
||||
|
||||
SOURCES = [
|
||||
"이움코퍼레이션 https://www.eumcorporation.com/aeo-geo/",
|
||||
"마하닥-GEO https://hospitalgeo.com",
|
||||
"아워마케팅 GEO 진단 https://ourmarketing.co.kr/geo-diagnosis · https://ourmarketing.co.kr/service/expertise/geo · 출시 기사 http://www.bogunuilyo.com/news/503413 (2026-07-10)",
|
||||
"와이더뷰티 https://mdtoday.co.kr/news/view/1065602577857715 (2026-05-28)",
|
||||
"크리앤서 https://www.creanswer.com · 병원 GEO 체크리스트 https://creanswer.com/blog/geo-checklist-7/ (2026-05-04)",
|
||||
"NATANA https://natana-ai.com/clinic",
|
||||
"온더AI https://onthe.ai/pricing · 병원 GEO 가이드 https://blog.onthe.ai/geo-ai-search-hospital-marketing (2026-04-02)",
|
||||
"지오랭크 https://georank.co.kr · 병원 사례 https://georank.co.kr/case/hospital-clinic-geo · 비용 https://georank.co.kr/geo/geo-cost-pricing",
|
||||
"BizSpring GEO Care for Clinic https://blog.bizspring.co.kr (2026-05-28, 수정 2026-08-27)",
|
||||
"서치폴라리스 https://searchpolaris.com · 가격 https://searchpolaris.com/pricing",
|
||||
"에이넥트 https://ainnect.co.kr",
|
||||
"팀하이 https://teamhai.co.kr",
|
||||
"블루닷 https://bi.bluedot.so/consulting",
|
||||
"NNT Insights https://nnt-insights.com/pricing",
|
||||
"RanketAI https://www.ranketai.com/ko/faq · AI 가시성 측정 도구 비교 https://www.ranketai.com/ko/blog/ai-visibility-tools-comparison-2026-05-16",
|
||||
"FrostAI https://www.frostai.co",
|
||||
"오픈타임 https://ai.idearabbit.co.kr",
|
||||
"ZESTCOMPANY https://zestcompany.co.kr",
|
||||
"Anymorph https://anymorph.ai · 한국 GEO·AEO 서비스 TOP 5 https://anymorph.ai/contents/best/korea-geo-aeo-services-top-5-2026 (2026-08-18)",
|
||||
"OtterlyAI https://otterly.ai/pricing · GEO Audit https://otterly.ai/blog/generative-engine-optimization-audit/ (2025-08-04) · https://help.otterly.ai/what-does-the-geo-audit-do (2026-08-18)",
|
||||
"Profound https://www.tryprofound.com/pricing",
|
||||
"Peec AI https://peec.ai/pricing · 리뷰 https://generatemore.ai/blog/peec-ai-review (2026-08-28)",
|
||||
"HubSpot AEO https://www.hubspot.com/products/aeo · 대안 비교 https://blog.hubspot.com/marketing/peec-ai-alternatives",
|
||||
"Semrush AI Toolkit https://www.semrush.com/kb/1493-ai-toolkit · Ahrefs Brand Radar https://ahrefs.com/brand-radar",
|
||||
"AI Search Visibility Tools 비교 https://www.surmado.com/blog/best-ai-visibility-tools-2026",
|
||||
"Fuel Online https://fuelonline.com/plastic-surgery-marketing/rhinoplasty/ai-visibility-audit/",
|
||||
"ADvance Media https://advancemedia.com/services/ai-visibility-aeo-geo/",
|
||||
"Specialist Practice Excellence https://specialistpracticeexcellence.com/blog/prompt-to-audit-aeo-ai-visibility-for-plastic-surgeons/ (2026-07-25)",
|
||||
"Plastic SEO https://www.plasticseo.com/how-ai-search-is-changing-plastic-surgery-seo/ (2026-07-01)",
|
||||
"Percepture https://percepture.com/healthcare-insights/plastic-surgery-seo-services/ (2026-06-11)",
|
||||
]
|
||||
|
||||
|
||||
def main():
|
||||
doc = Document(TARGET)
|
||||
body = doc.element.body
|
||||
children = list(body.iterchildren())
|
||||
if any(c.tag.endswith("}p") and text_of(c).strip().startswith("4-10.") for c in children):
|
||||
print("이미 4-10 절이 있어 아무것도 하지 않았습니다.")
|
||||
return
|
||||
anchor = next((c for c in children if c.tag.endswith("}p") and text_of(c).strip() == ANCHOR), None)
|
||||
if anchor is None:
|
||||
sys.exit(f"기준 제목을 찾지 못했습니다: {ANCHOR}")
|
||||
|
||||
tmp = Document()
|
||||
section(tmp)
|
||||
for el in [e for e in tmp.element.body.iterchildren() if not e.tag.endswith("}sectPr")]:
|
||||
anchor.addprevious(copy.deepcopy(el))
|
||||
|
||||
src = Document()
|
||||
heading(src, "병원·의료 AEO/GEO 서비스 (4-10, 조회일 2026-09-15)", 2)
|
||||
for s in SOURCES:
|
||||
p = src.add_paragraph()
|
||||
p.paragraph_format.space_after = Pt(2)
|
||||
set_font(p.add_run(s), 9, False, GRAY)
|
||||
sect = body.find("{http://schemas.openxmlformats.org/wordprocessingml/2006/main}sectPr")
|
||||
for el in [e for e in src.element.body.iterchildren() if not e.tag.endswith("}sectPr")]:
|
||||
new = copy.deepcopy(el)
|
||||
if sect is not None:
|
||||
sect.addprevious(new)
|
||||
else:
|
||||
body.append(new)
|
||||
|
||||
doc.save(TARGET)
|
||||
print(f"삽입 완료: {TARGET.name}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@ -8,11 +8,9 @@ docs/reports/
|
||||
│ ├── 01_readiness_report/ AI Answer Readiness 진단 덱 (v1.0 정본 · v2_표준캔버스 · v1.1 정정판) + 빌더
|
||||
│ ├── 02_poc_proposal/ POC 제안서 덱 (정본 · v3 · v3_표준캔버스) + 빌더
|
||||
│ ├── 03_question_bank/ 질문 뱅크 120문항 · ChatGPT/Perplexity 실측 결과 · TOP50 엑셀
|
||||
│ ├── 04_supporters/ 서포터즈 GEO 개선 계획 · 2단계 콘텐츠 기획 · 서포터즈 vs 공식 AEO 감사 엑셀
|
||||
│ └── 05_global_en/ 영문 사이트(viewplasticsurgery.com) 외국인 관점 AEO/GEO 진단 (2026-09-10) · 영문 60문항 뱅크 · Perplexity/OpenAI(뉴욕) 실측 · HTML/PDF 보고서 + 빌더
|
||||
│ └── 04_supporters/ 서포터즈 GEO 개선 계획 · 2단계 콘텐츠 기획 · 서포터즈 vs 공식 AEO 감사 엑셀
|
||||
├── taeha/ 태하 AI Answer Readiness 리포트 (html · pdf · pptx) + 빌더
|
||||
├── ibk/ 기업은행 블로그 AEO/GEO 감사
|
||||
├── answer_engines/ 답변엔진 확장 검토 보고서(.docx, 120문항 비용 추정 · 후보별 약관 검토) + 빌더
|
||||
└── landing/ AI Discovery 랜딩 시안 (픽셀 정답 HTML) · 스크린샷 · 리포트 페이지 HTML 내보내기 스크립트
|
||||
```
|
||||
|
||||
|
||||
@ -1,367 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""답변엔진 확장 검토 보고서(.docx) 빌더.
|
||||
|
||||
질문 뱅크 120문항 기준으로 후보 답변엔진별 비용을 추정하고, 약관 검토 결과와 함께 워드 문서로 낸다.
|
||||
단가는 2026-09-15 각사 공식 가격표 확인값. 토큰량은 가정이며 4장 표에 근거를 적는다.
|
||||
|
||||
사용:
|
||||
python3 docs/reports/answer_engines/build_engine_review_docx.py
|
||||
"""
|
||||
from pathlib import Path
|
||||
|
||||
from docx import Document
|
||||
from docx.enum.table import WD_TABLE_ALIGNMENT
|
||||
from docx.enum.text import WD_ALIGN_PARAGRAPH
|
||||
from docx.oxml import OxmlElement
|
||||
from docx.oxml.ns import qn
|
||||
from docx.shared import Cm, Pt, RGBColor
|
||||
|
||||
OUT = Path(__file__).with_name("INFINITH_답변엔진_확장검토_2026-09-15.docx")
|
||||
|
||||
INDIGO = RGBColor(0x1D, 0x00, 0x24)
|
||||
VIOLET = RGBColor(0x4F, 0x1D, 0xA1)
|
||||
GRAY = RGBColor(0x55, 0x55, 0x60)
|
||||
FONT = "Pretendard"
|
||||
|
||||
KRW = 1345 # 2026-09-13 USD/KRW 1,343.56 기준 반올림
|
||||
QUESTIONS = 120
|
||||
REPEAT = 5
|
||||
|
||||
M = 1_000_000
|
||||
|
||||
# ── 후처리 단가(답변 1건당): 병원명 추출 gpt-4o-mini + 어조 판정 gpt-4.1 ──
|
||||
def post_cost(level):
|
||||
rubric_in = {"low": 4000, "mid": 5000, "high": 6000}[level]
|
||||
answer_in = {"low": 600, "mid": 1000, "high": 2900}[level]
|
||||
extract = (500 + answer_in) * 0.15 / M + 150 * 0.60 / M
|
||||
judge = (rubric_in + answer_in) * 2.00 / M + 300 * 8.00 / M
|
||||
return extract + judge
|
||||
|
||||
|
||||
# ── 엔진별 1문항 1회 호출 비용 모델 ──
|
||||
# searches: 호출당 검색 횟수, search_fee: 검색 1회 단가, tin/tout: 입력·출력 토큰, pin/pout: 1M 토큰 단가
|
||||
ENGINES = [
|
||||
dict(key="gpt4o", name="ChatGPT (현행)", setup="gpt-4o + web_search", status="운영 중",
|
||||
search_fee=0.025, pin=2.50, pout=10.00,
|
||||
low=dict(searches=1, tin=300, tout=400), mid=dict(searches=1, tin=300, tout=1000), high=dict(searches=1, tin=300, tout=2900),
|
||||
note="비추론 모델은 검색 호출 $25/1천 회, 검색 본문 토큰 무료. 출력은 실측 평균 962자·상위 10% 2,850자."),
|
||||
dict(key="gpt56", name="ChatGPT (최신화)", setup="GPT-5.6 Sol + web_search", status="모델 교체안",
|
||||
search_fee=0.010, pin=4.00, pout=20.00,
|
||||
low=dict(searches=1, tin=8000, tout=1500), mid=dict(searches=2, tin=20000, tout=3000), high=dict(searches=3, tin=40000, tout=5000),
|
||||
note="추론 모델은 검색 호출 $10/1천 회, 검색 본문 토큰을 모델 단가로 과금. 출력에 추론 토큰 포함."),
|
||||
dict(key="sonar", name="Perplexity (현행)", setup="sonar, 검색 컨텍스트 low", status="운영 중",
|
||||
search_fee=0.005, pin=1.00, pout=1.00,
|
||||
low=dict(searches=1, tin=50, tout=400), mid=dict(searches=1, tin=50, tout=650), high=dict(searches=1, tin=50, tout=1000),
|
||||
note="요청 수수료 $5/1천 회(low). 출력은 실측 평균 618자."),
|
||||
dict(key="sonarpro", name="Perplexity (상위)", setup="sonar-pro, 검색 컨텍스트 medium", status="모델 교체안",
|
||||
search_fee=0.010, pin=3.00, pout=15.00,
|
||||
low=dict(searches=1, tin=50, tout=700), mid=dict(searches=1, tin=50, tout=1000), high=dict(searches=1, tin=50, tout=1400),
|
||||
note="요청 수수료 $10/1천 회(medium)."),
|
||||
dict(key="sonnet5", name="Claude", setup="Claude Sonnet 5 + web_search", status="추가 권고",
|
||||
search_fee=0.010, pin=2.00, pout=10.00,
|
||||
low=dict(searches=1, tin=12000, tout=800), mid=dict(searches=2, tin=30000, tout=1500), high=dict(searches=3, tin=60000, tout=3000),
|
||||
note="검색 $10/1천 회. 검색 결과가 입력 토큰으로 과금되어 입력 토큰이 비용의 대부분."),
|
||||
dict(key="haiku45", name="Claude (저가)", setup="Claude Haiku 4.5 + web_search", status="대안",
|
||||
search_fee=0.010, pin=1.00, pout=5.00,
|
||||
low=dict(searches=1, tin=12000, tout=800), mid=dict(searches=2, tin=30000, tout=1500), high=dict(searches=3, tin=60000, tout=3000),
|
||||
note="Sonnet 5와 같은 토큰량 가정. 동적 필터링(입력 절감)은 4.6 이상 모델만 지원."),
|
||||
dict(key="gemini", name="Gemini", setup="Gemini 3.8 Flash + Google 검색 그라운딩", status="약관 확인 전 보류",
|
||||
search_fee=0.0, pin=0.75, pout=3.75,
|
||||
low=dict(searches=1, tin=300, tout=1000), mid=dict(searches=2, tin=300, tout=2000), high=dict(searches=3, tin=300, tout=4000),
|
||||
note="그라운딩 월 5,000회 무료(3.x 공통), 초과 시 $14/1천 회. 120문항 5회 반복은 최대 1,800회라 무료 범위 안."),
|
||||
dict(key="grok", name="Grok", setup="Grok 4.3 + web search", status="후순위",
|
||||
search_fee=0.005, pin=1.25, pout=2.50,
|
||||
low=dict(searches=1, tin=8000, tout=800), mid=dict(searches=2, tin=20000, tout=1500), high=dict(searches=3, tin=40000, tout=3000),
|
||||
note="검색 $5/1천 회는 제3자 정리 기준. 공식 가격표 재확인 필요."),
|
||||
dict(key="bing", name="Copilot 계열", setup="Azure Foundry + Grounding with Bing + gpt-4o", status="제외 (약관)",
|
||||
search_fee=0.014, pin=2.50, pout=10.00,
|
||||
low=dict(searches=1, tin=5000, tout=400), mid=dict(searches=1, tin=10000, tout=1000), high=dict(searches=2, tin=20000, tout=2900),
|
||||
note="참고용 산정. 결과 저장·데이터베이스화 금지 조항으로 측정에 쓸 수 없음."),
|
||||
dict(key="aio", name="Google AI Mode", setup="DataForSEO AI Mode SERP API", status="결정 필요",
|
||||
search_fee=0.004, pin=0.0, pout=0.0,
|
||||
low=dict(searches=1, tin=0, tout=0), mid=dict(searches=1, tin=0, tout=0), high=dict(searches=1, tin=0, tout=0),
|
||||
note="Live $0.004, Standard 대기열 $0.0012 / 요청. 모델 토큰 없음. 소비자 화면을 제3자가 수집하는 방식."),
|
||||
]
|
||||
|
||||
|
||||
def call_cost(e, level):
|
||||
p = e[level]
|
||||
return p["searches"] * e["search_fee"] + p["tin"] * e["pin"] / M + p["tout"] * e["pout"] / M
|
||||
|
||||
|
||||
def per_question(e, level, with_post=True):
|
||||
return call_cost(e, level) + (post_cost(level) if with_post else 0)
|
||||
|
||||
|
||||
def usd(x):
|
||||
return f"${x:,.2f}" if x >= 0.995 else f"${x:.3f}"
|
||||
|
||||
|
||||
def won(x):
|
||||
return f"₩{round(x * KRW, -1):,.0f}"
|
||||
|
||||
|
||||
# ── 문서 헬퍼 ──
|
||||
def set_font(run, size=11, bold=False, color=INDIGO):
|
||||
run.font.name = FONT
|
||||
run.font.size = Pt(size)
|
||||
run.font.bold = bold
|
||||
run.font.color.rgb = color
|
||||
rpr = run._element.get_or_add_rPr()
|
||||
rfonts = rpr.find(qn("w:rFonts"))
|
||||
if rfonts is None:
|
||||
rfonts = OxmlElement("w:rFonts")
|
||||
rpr.append(rfonts)
|
||||
for attr in ("w:ascii", "w:hAnsi", "w:cs", "w:eastAsia"):
|
||||
rfonts.set(qn(attr), FONT)
|
||||
|
||||
|
||||
def para(doc, text, size=11, bold=False, color=INDIGO, space_after=6, align=None):
|
||||
p = doc.add_paragraph()
|
||||
p.paragraph_format.space_after = Pt(space_after)
|
||||
p.paragraph_format.line_spacing = 1.35
|
||||
if align:
|
||||
p.alignment = align
|
||||
set_font(p.add_run(text), size, bold, color)
|
||||
return p
|
||||
|
||||
|
||||
def heading(doc, text, level=1):
|
||||
size = {0: 24, 1: 16, 2: 13}[level]
|
||||
p = doc.add_paragraph()
|
||||
p.paragraph_format.space_before = Pt(18 if level == 1 else 10)
|
||||
p.paragraph_format.space_after = Pt(8)
|
||||
p.paragraph_format.keep_with_next = True
|
||||
set_font(p.add_run(text), size, True, INDIGO if level != 2 else VIOLET)
|
||||
return p
|
||||
|
||||
|
||||
def bullets(doc, items, size=11):
|
||||
for t in items:
|
||||
p = doc.add_paragraph(style="List Bullet")
|
||||
p.paragraph_format.space_after = Pt(3)
|
||||
p.paragraph_format.line_spacing = 1.3
|
||||
if isinstance(t, tuple):
|
||||
set_font(p.add_run(t[0]), size, True)
|
||||
set_font(p.add_run(t[1]), size)
|
||||
else:
|
||||
set_font(p.add_run(t), size)
|
||||
|
||||
|
||||
def shade(cell, hex_fill):
|
||||
tcpr = cell._element.get_or_add_tcPr()
|
||||
shd = OxmlElement("w:shd")
|
||||
shd.set(qn("w:val"), "clear")
|
||||
shd.set(qn("w:color"), "auto")
|
||||
shd.set(qn("w:fill"), hex_fill)
|
||||
tcpr.append(shd)
|
||||
|
||||
|
||||
def table(doc, header, rows, widths=None, size=9.5, bold_first_col=True, highlight_rows=()):
|
||||
t = doc.add_table(rows=1, cols=len(header))
|
||||
t.style = "Table Grid"
|
||||
t.alignment = WD_TABLE_ALIGNMENT.CENTER
|
||||
for i, h in enumerate(header):
|
||||
c = t.rows[0].cells[i]
|
||||
c.text = ""
|
||||
set_font(c.paragraphs[0].add_run(h), size, True, RGBColor(0xFF, 0xFF, 0xFF))
|
||||
shade(c, "1D0024")
|
||||
trpr = t.rows[0]._tr.get_or_add_trPr()
|
||||
for tag in ("w:tblHeader", "w:cantSplit"):
|
||||
el = OxmlElement(tag)
|
||||
el.set(qn("w:val"), "true")
|
||||
trpr.append(el)
|
||||
for ri, r in enumerate(rows):
|
||||
cells = t.add_row().cells
|
||||
for i, v in enumerate(r):
|
||||
cells[i].text = ""
|
||||
bold = (i == 0 and bold_first_col) or ri in highlight_rows
|
||||
set_font(cells[i].paragraphs[0].add_run(str(v)), size, bold)
|
||||
if ri in highlight_rows:
|
||||
shade(cells[i], "F1ECFA")
|
||||
if widths:
|
||||
t.autofit = False
|
||||
for i, w in enumerate(widths):
|
||||
t.columns[i].width = Cm(w)
|
||||
for row in t.rows:
|
||||
for i, w in enumerate(widths):
|
||||
row.cells[i].width = Cm(w)
|
||||
doc.add_paragraph().paragraph_format.space_after = Pt(2)
|
||||
return t
|
||||
|
||||
|
||||
def build():
|
||||
E = {e["key"]: e for e in ENGINES}
|
||||
doc = Document()
|
||||
sec = doc.sections[0]
|
||||
sec.page_width, sec.page_height = Cm(21.0), Cm(29.7)
|
||||
sec.left_margin = sec.right_margin = Cm(2.0)
|
||||
sec.top_margin = sec.bottom_margin = Cm(2.0)
|
||||
|
||||
# 표지 블록
|
||||
para(doc, "INFINITH AI Discovery", 11, True, VIOLET, 4)
|
||||
heading(doc, "답변엔진 확장 검토 보고서", 0)
|
||||
para(doc, "질문 뱅크 120문항 기준 비용 추정과 후보 엔진별 약관 검토", 13, False, INDIGO, 4)
|
||||
para(doc, "작성일 2026-09-15 · 단가 확인일 2026-09-15 · 환율 1 USD = ₩1,345 가정", 10, False, GRAY, 14)
|
||||
|
||||
# 1. 요약
|
||||
heading(doc, "1. 요약")
|
||||
gpt4o, sonar, sonnet, gem = E["gpt4o"], E["sonar"], E["sonnet5"], E["gemini"]
|
||||
cur_q = per_question(gpt4o, "mid") + per_question(sonar, "mid")
|
||||
add_claude_q = per_question(sonnet, "mid")
|
||||
bullets(doc, [
|
||||
("현재 실측 엔진은 2개입니다. ", "ChatGPT(gpt-4o + 웹 검색)와 Perplexity(sonar)입니다. 36개 진단 항목은 AI 모델 없이 규칙 검사로 판정합니다."),
|
||||
("바로 추가할 수 있는 엔진은 Claude입니다. ", "공식 API에 웹 검색 도구가 있고, 결과 저장·분석을 막는 조항을 확인하지 못했습니다."),
|
||||
("Gemini와 Copilot은 약관이 측정 용도와 충돌합니다. ", "Gemini는 그라운딩 결과의 분석·자동 수집을, Bing 그라운딩은 결과 저장·데이터베이스화를 금지합니다."),
|
||||
("비용은 크지 않습니다. ",
|
||||
f"현행 2개 엔진의 120문항 1회 측정은 약 {usd(cur_q * QUESTIONS)}({won(cur_q * QUESTIONS)})이고, "
|
||||
f"Claude를 더하면 1회 약 {usd(add_claude_q * QUESTIONS)}({won(add_claude_q * QUESTIONS)})가 늘어납니다. 모두 중앙 추정치이며 후처리(병원명 추출·어조 판정) 비용을 포함합니다."),
|
||||
("네이버 AI 브리핑은 측정 API가 없습니다. ", "수동 스팟체크를 유지합니다."),
|
||||
])
|
||||
|
||||
# 2. 현재 구성
|
||||
heading(doc, "2. 현재 측정 구성")
|
||||
table(doc, ["역할", "모델 · 방식", "코드 위치"], [
|
||||
["답변엔진 실측 ①", "OpenAI gpt-4o + Responses API web_search (서울 고정, 영문판 뉴욕)", "scripts/run_question_bank_openai.py"],
|
||||
["답변엔진 실측 ②", "Perplexity sonar (웹 검색 내장)", "scripts/run_question_bank_openai.py"],
|
||||
["병원명·순위 추출", "OpenAI gpt-4o-mini (키 없으면 Gemini 2.5 Flash)", "scripts/run_question_bank_openai.py"],
|
||||
["어조 판정", "OpenAI gpt-4.1, temperature 0", "scripts/sentiment_qb.py"],
|
||||
["36개 항목 진단", "AI 모델 미사용. 크롤링 규칙 검사 + Wikidata 조회", "scripts/audit_aeo_geo.py"],
|
||||
], widths=[3.2, 8.6, 5.2])
|
||||
para(doc, "측정은 공식 API 답변 기준입니다. 소비자 화면(chatgpt.com 등) 자동 조회는 약관상 하지 않으므로, API 답변과 화면 답변은 다를 수 있습니다.", 10, False, GRAY)
|
||||
|
||||
# 3. 후보별 검토
|
||||
heading(doc, "3. 후보 엔진별 검토")
|
||||
table(doc, ["엔진", "공식 API", "약관상 측정 가능 여부", "판정"], [
|
||||
["Claude", "Messages API web_search 도구. user_location으로 도시·국가 지정", "저장·분석 금지 조항 미확인. 최종 사용자에게 보여줄 때 출처 표시 필요", "추가 권고"],
|
||||
["Gemini", "Grounding with Google Search. GEMINI_API_KEY 보유", "그라운딩 결과의 cache·analyze·자동 수집 금지. 질문한 사용자에게 보여주는 용도만 허용", "법무 확인 전 보류"],
|
||||
["Copilot 계열", "Azure Foundry Grounding with Bing Search", "결과의 store·cache·archive·데이터베이스화 금지. 검색 경험 앱에서만 사용", "제외"],
|
||||
["Grok", "xAI API web search 도구", "저장 금지 조항 별도 확인 안 함", "후순위 (국내 이용 비중 낮음)"],
|
||||
["Google AI Overview · AI Mode", "Google 공식 API 없음. DataForSEO·SerpApi가 결과 화면을 수집해 제공", "제3자가 소비자 화면을 수집하는 방식. 현재 수집 원칙과 불일치", "haewon 결정 필요"],
|
||||
["네이버 AI 브리핑", "없음. CLOVA Studio는 모델 API이며 브리핑 화면을 재현하지 못함", "해당 없음", "제외, 수동 스팟체크"],
|
||||
["DeepSeek · 뤼튼 · 라이너", "웹 검색이 붙은 공식 측정 API 미확인", "해당 없음", "제외"],
|
||||
], widths=[3.0, 5.2, 6.0, 2.8])
|
||||
|
||||
heading(doc, "3.1 엔진별 판단 근거", 2)
|
||||
bullets(doc, [
|
||||
("Claude. ", "검색 1,000회당 $10에 검색 결과가 입력 토큰으로 과금됩니다. 응답에 인용 URL과 인용 문장(최대 150자)이 구조화되어 와서 현재 추출 로직에 붙이기 쉽습니다. 국내 병원 고객의 이용 비중은 ChatGPT보다 낮습니다."),
|
||||
("Gemini. ", "기술 난이도는 가장 낮고 국내 비중은 Claude보다 높습니다. 다만 Gemini API 추가 약관이 Grounded Results를 \"cache, frame, syndicate, resell, analyze\" 하는 것을 금지하고, 자동 수집을 허용하지 않습니다. Vertex AI 경로의 약관이 다른지와, 진단을 요청한 고객에게 결과를 보여주는 용도로 인정되는지 법무 판단이 필요합니다."),
|
||||
("Copilot 계열. ", "Bing 그라운딩 약관이 결과를 \"Copy, store, cache, archive, or create a database of Output\" 하는 것을 금지합니다. 질문 뱅크 결과를 누적하는 방식과 양립하지 않습니다."),
|
||||
("Google AI Overview · AI Mode. ", "병원 검색에서 노출 비중이 큰 화면이고 비용도 가장 낮습니다. 대신 Google 검색 결과를 제3자 도구로 수집하는 구조라, \"소비자 화면을 자동 조회하지 않는다\"는 현재 원칙을 바꾸는 결정입니다."),
|
||||
("기존 엔진 모델 점검. ", "ChatGPT 소비자 화면은 gpt-4o보다 새 모델로 답합니다. 측정 모델을 최신 추론 모델로 바꾸면 화면 답변과의 차이가 줄지만, 문항당 비용이 약 3.5배 늘어납니다(5장 표)."),
|
||||
])
|
||||
|
||||
# 4. 비용 산정 기준
|
||||
heading(doc, "4. 비용 산정 기준")
|
||||
table(doc, ["항목", "가정", "근거"], [
|
||||
["문항 수", "120문항", "질문 뱅크 v0.1"],
|
||||
["반복", "1회, 5회 반복 두 가지", "답변 변동을 신뢰구간으로 보고하려면 5~7회 반복 필요"],
|
||||
["답변 길이", "ChatGPT 평균 962자(상위 10% 2,850자), Perplexity 평균 618자", "scripts/out 실측 결과 240건"],
|
||||
["한국어 토큰 환산", "1자 ≈ 1토큰 (보수적)", "모델별 토크나이저 차이는 범위(최소·최대)로 흡수"],
|
||||
["검색 결과 입력 토큰", "Claude 1.2만~6만, OpenAI 추론 모델 0.8만~4만, Grok 0.8만~4만", "검색 1~3회 × 결과 본문. 실측 전 가정"],
|
||||
["후처리", f"답변 1건당 약 {usd(post_cost('mid'))}", "gpt-4o-mini 추출 + gpt-4.1 어조 판정(기준표 프롬프트 약 5천 토큰)"],
|
||||
["환율", "1 USD = ₩1,345", "2026-09-13 USD/KRW 1,343.56"],
|
||||
["할인", "미적용", "Batch API 50% 할인은 적용 시 토큰 비용만 절감"],
|
||||
], widths=[3.2, 7.3, 6.5])
|
||||
|
||||
# 5. 엔진별 비용
|
||||
heading(doc, "5. 120문항 비용 추정 (엔진별)")
|
||||
para(doc, "문항당 단가는 엔진 호출 1회와 후처리 1건을 합한 값입니다. 범위는 최소·최대 가정, 굵은 값은 중앙 추정입니다.", 10, False, GRAY)
|
||||
rows = []
|
||||
for e in ENGINES:
|
||||
lo, mid, hi = (per_question(e, lv) for lv in ("low", "mid", "high"))
|
||||
rows.append([
|
||||
e["name"], e["setup"], e["status"], usd(mid),
|
||||
f"{usd(mid * QUESTIONS)}\n{won(mid * QUESTIONS)}",
|
||||
f"{usd(mid * QUESTIONS * REPEAT)}\n{won(mid * QUESTIONS * REPEAT)}",
|
||||
f"{usd(lo * QUESTIONS * REPEAT)} ~ {usd(hi * QUESTIONS * REPEAT)}",
|
||||
])
|
||||
table(doc, ["엔진", "구성", "상태", "문항당", "120문항 1회", "120문항 5회", "5회 범위"], rows,
|
||||
widths=[2.4, 3.9, 2.2, 1.6, 2.3, 2.3, 2.6], size=9, highlight_rows=(0, 2, 4))
|
||||
bullets(doc, [
|
||||
"Gemini 행은 그라운딩 무료 범위(월 5,000회) 안에서 계산했습니다. 무료 범위를 넘으면 검색 1,000회당 $14가 더해져 5회 반복 기준 최대 "
|
||||
f"{usd(1800 * 0.014)}가 늘어납니다.",
|
||||
"Copilot 계열은 약관상 사용할 수 없어 참고용으로만 적었습니다.",
|
||||
"Google AI Mode는 모델 호출이 없어 비용 대부분이 후처리입니다. Standard 대기열($0.0012)을 쓰면 수집 비용이 70% 줄어듭니다.",
|
||||
], size=10)
|
||||
heading(doc, "5.1 엔진별 산정 메모", 2)
|
||||
table(doc, ["엔진", "과금 구조"], [[e["name"], e["note"]] for e in ENGINES], widths=[3.2, 13.8], size=9)
|
||||
|
||||
# 6. 구성안
|
||||
heading(doc, "6. 구성안별 합계")
|
||||
plans = [
|
||||
("A. 현행 유지", ["gpt4o", "sonar"], "현재 운영 구성"),
|
||||
("B. 현행 + Claude", ["gpt4o", "sonar", "sonnet5"], "권고안. 랜딩에 ChatGPT·Perplexity·Claude 표기 가능"),
|
||||
("C. 모델 최신화 + Claude", ["gpt56", "sonarpro", "sonnet5"], "소비자 화면과의 차이를 줄이는 안"),
|
||||
("D. B + Gemini", ["gpt4o", "sonar", "sonnet5", "gemini"], "Gemini 약관 확인이 끝난 경우"),
|
||||
("E. D + Google AI Mode", ["gpt4o", "sonar", "sonnet5", "gemini", "aio"], "수집 원칙 변경을 결정한 경우"),
|
||||
]
|
||||
prow = []
|
||||
for name, keys, desc in plans:
|
||||
lo = sum(per_question(E[k], "low") for k in keys)
|
||||
mid = sum(per_question(E[k], "mid") for k in keys)
|
||||
hi = sum(per_question(E[k], "high") for k in keys)
|
||||
prow.append([name, f"{len(keys)}개", desc,
|
||||
f"{usd(mid * QUESTIONS)}\n{won(mid * QUESTIONS)}",
|
||||
f"{usd(mid * QUESTIONS * REPEAT)}\n{won(mid * QUESTIONS * REPEAT)}",
|
||||
f"{won(lo * QUESTIONS * REPEAT)} ~ {won(hi * QUESTIONS * REPEAT)}"])
|
||||
table(doc, ["구성안", "엔진", "설명", "120문항 1회", "120문항 5회", "5회 범위(원)"], prow,
|
||||
widths=[3.2, 1.3, 4.8, 2.5, 2.5, 3.0], size=9, highlight_rows=(1,))
|
||||
para(doc, "병원 1곳을 월 1회, 5회 반복으로 측정하면 월 비용은 \"120문항 5회\" 열과 같습니다. 병원 수에 비례해 늘어납니다.", 10, False, GRAY)
|
||||
|
||||
# 7. 권고
|
||||
heading(doc, "7. 권고와 결정 필요 사항")
|
||||
heading(doc, "7.1 권고 순서", 2)
|
||||
bullets(doc, [
|
||||
("1단계. Claude 엔진 추가 (구성안 B). ", "run_question_bank_openai.py에 엔진 클래스를 하나 추가합니다. 10문항 시험 실행으로 실제 토큰량을 확인한 뒤 120문항을 돌립니다."),
|
||||
("2단계. 기존 엔진 모델 점검. ", "10문항을 gpt-4o와 최신 추론 모델로 나란히 돌려 병원 언급률 차이를 봅니다. 차이가 크면 구성안 C로 올립니다."),
|
||||
("3단계. Gemini 약관 확인. ", "Vertex AI 약관과 AI Studio 약관을 비교하고 법무 판단을 받습니다."),
|
||||
("4단계. Google AI Mode 수집 여부 결정. ", "수집 원칙 변경이 필요합니다."),
|
||||
])
|
||||
heading(doc, "7.2 haewon 결정 필요", 2)
|
||||
table(doc, ["번호", "결정 사항", "선택지"], [
|
||||
["1", "Claude 엔진 추가와 10문항 시험 실행", "진행 / 보류"],
|
||||
["2", "측정 모델 최신화 시험 (gpt-4o 대비 최신 모델)", "진행 / 보류"],
|
||||
["3", "Gemini 약관 법무 검토 요청", "요청 / 보류"],
|
||||
["4", "Google AI Overview · AI Mode 제3자 수집 허용", "허용 / 불허"],
|
||||
["5", "Anthropic API 회사 키 발급", "발급 / 기존 키 사용"],
|
||||
], widths=[1.4, 10.0, 5.6])
|
||||
|
||||
# 8. 한계
|
||||
heading(doc, "8. 이 추정의 한계")
|
||||
bullets(doc, [
|
||||
"검색 결과 입력 토큰은 실측 전 가정입니다. Claude·OpenAI 추론 모델·Grok 비용은 이 값에 따라 2배 안팎 달라질 수 있습니다. 10문항 시험 실행 후 5장 표를 갱신합니다.",
|
||||
"Grok 검색 단가는 제3자 정리 기준이고, 약관 조항은 요약본을 읽은 것입니다. 도입 판단 전에 원문을 다시 확인해야 합니다.",
|
||||
"Gemini 3.x Flash 할인 단가는 2026-12-31까지이며, 2027-01-01부터 입력·출력 단가가 2배로 오릅니다.",
|
||||
"API 답변은 각 서비스의 소비자 앱 답변과 같지 않습니다. 엔진을 늘려도 보고서에는 \"공식 API 답변 기준\"으로 표기해야 합니다.",
|
||||
"환율 변동과 부가세는 반영하지 않았습니다.",
|
||||
], size=10)
|
||||
|
||||
# 9. 출처
|
||||
heading(doc, "9. 출처 (2026-09-15 확인)")
|
||||
for label, url in [
|
||||
("OpenAI API Pricing", "https://developers.openai.com/api/docs/pricing"),
|
||||
("Perplexity Sonar Pricing", "https://docs.perplexity.ai/getting-started/pricing"),
|
||||
("Claude Pricing", "https://platform.claude.com/docs/en/about-claude/pricing"),
|
||||
("Claude Web search tool", "https://platform.claude.com/docs/en/agents-and-tools/tool-use/web-search-tool"),
|
||||
("Gemini API Pricing", "https://ai.google.dev/gemini-api/docs/pricing"),
|
||||
("Gemini API Additional Terms", "https://ai.google.dev/gemini-api/terms"),
|
||||
("Grounding with Bing pricing", "https://www.microsoft.com/en-us/bing/apis/grounding-pricing"),
|
||||
("Grounding with Bing legal", "https://www.microsoft.com/en-us/bing/apis/grounding-legal"),
|
||||
("xAI models", "https://docs.x.ai/docs/models"),
|
||||
("xAI Grok API pricing (제3자 정리)", "https://mem0.ai/blog/xai-grok-api-pricing"),
|
||||
("DataForSEO Google AI Mode SERP API", "https://dataforseo.com/pricing/google-serp/google-ai-mode-serp-api"),
|
||||
("CLOVA Studio API 가이드", "https://api.ncloud-docs.com/docs/ai-naver-clovastudio-summary"),
|
||||
("원·달러 환율 (2026-09-13)", "https://ko.tradingeconomics.com/south-korea/currency"),
|
||||
]:
|
||||
p = doc.add_paragraph()
|
||||
p.paragraph_format.space_after = Pt(2)
|
||||
set_font(p.add_run(f"{label} "), 9.5, True)
|
||||
set_font(p.add_run(url), 9.5, False, GRAY)
|
||||
|
||||
doc.save(OUT)
|
||||
print(OUT)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
build()
|
||||
@ -1,7 +1,7 @@
|
||||
# 뷰성형외과 Question Bank 실측 결과: ChatGPT(OpenAI) x Perplexity x 네이버 AI 브리핑 통합
|
||||
# 뷰성형외과 Question Bank 실측 결과: ChatGPT(OpenAI) x Perplexity 통합
|
||||
|
||||
측정일: ChatGPT(OpenAI) 2026-09-01 · Perplexity 2026-09-01 · 네이버 AI 브리핑 2026-09-15
|
||||
측정 방식: OpenAI Responses API + web_search(gpt-4o, 서울 고정) / Perplexity API(sonar) / 네이버 AI 브리핑(SerpApi naver_ai_overview, 제3자 SERP API, 모바일). 답변엔진 소비자 UI 자동 조회는 약관 위반이므로 정식 API로 측정했다. 네이버는 공식 API가 없어 제3자 API를 썼으며, 고객 리포트 사용 전 법무 확인이 필요하다.
|
||||
측정일: 2026-09-01
|
||||
측정 방식: OpenAI Responses API + web_search(gpt-4o, 서울 고정) / Perplexity API(sonar). 답변엔진 소비자 UI 자동 조회는 약관 위반이므로 정식 API로 측정했다.
|
||||
|
||||
## 요약
|
||||
|
||||
@ -9,9 +9,6 @@
|
||||
|---|---|---|
|
||||
| ChatGPT(OpenAI) | 35/36 (97%) | 10/84 (11%) |
|
||||
| Perplexity | 36/36 (100%) | 3/84 (3%) |
|
||||
| 네이버 AI 브리핑 | 36/36 (100%) | 1/84 (1%) |
|
||||
|
||||
네이버 AI 브리핑 노출: 119/120문항. 브리핑이 뜨지 않은 문항은 언급 없음으로 셌다.
|
||||
|
||||
브랜드 질문은 질문 문장에 '뷰성형외과'가 들어간 문항이다. 승부처인 논브랜드 질문(추천·비교·시술 정보)에서의 언급률이 AI Discovery의 실제 성적이다.
|
||||
|
||||
@ -55,199 +52,179 @@
|
||||
| 원진성형외과 | 2 | 0 |
|
||||
| 노트성형외과 | 2 | 0 |
|
||||
|
||||
### 네이버 AI 브리핑: 논브랜드 질문에서 언급된 병원 상위 15
|
||||
|
||||
| 병원 | 언급 질문 수 | 1순위 등장 수 |
|
||||
|---|---|---|
|
||||
| 더성형외과 | 2 | 2 |
|
||||
| 나나 | 2 | 0 |
|
||||
| 위쉬 | 2 | 0 |
|
||||
| 노트성형외과 | 2 | 0 |
|
||||
| 디에이성형외과 | 2 | 1 |
|
||||
| 플레저성형외과의원 | 1 | 1 |
|
||||
| 강남오엘의원 | 1 | 0 |
|
||||
| 밴스성형외과의원 | 1 | 0 |
|
||||
| 업투유 | 1 | 1 |
|
||||
| 뷰성형외과 | 1 | 0 |
|
||||
| 아이니크성형외과의원 | 1 | 1 |
|
||||
| 청담여신성형외과의원 | 1 | 0 |
|
||||
| 아이앤지성형외과의원 | 1 | 0 |
|
||||
| 오인성형외과의원 | 1 | 0 |
|
||||
| 강남제일 성형외과 | 1 | 0 |
|
||||
|
||||
## 문항별 결과
|
||||
|
||||
GPT/PPLX/NAVER 컬럼: O(순위) = 뷰성형외과 언급과 답변 내 등장 순번, X = 미언급, 브리핑 없음 = 네이버가 AI 브리핑을 띄우지 않음.
|
||||
GPT/PPLX 컬럼: O(순위) = 뷰성형외과 언급과 답변 내 등장 순번, X = 미언급.
|
||||
|
||||
### A. 브랜드 직접
|
||||
|
||||
| # | 질문 | 사전판정 | GPT | GPT 상위 병원 | PPLX | PPLX 상위 병원 | NAVER | NAVER 상위 병원 |
|
||||
|---|---|---|---|---|---|---|---|---|
|
||||
| A-01 | 뷰성형외과 어때? 믿을만한 병원이야? | △ | O (1위) | 뷰성형외과 | O (1위) | 뷰성형외과 | O (1위) | 뷰성형외과 |
|
||||
| A-02 | 뷰성형외과는 어떤 병원이야? 소개해줘 | △ | O (1위) | 뷰성형외과 | O (1위) | 뷰성형외과 | O (1위) | 뷰성형외과 |
|
||||
| A-03 | 뷰성형외과는 무슨 수술을 주로 해? | △ | O (1위) | 뷰성형외과 | O (1위) | 뷰성형외과 | O (1위) | 뷰성형외과 |
|
||||
| A-04 | 뷰성형외과 최순우 원장 어떤 의사야? | △ | O (1위) | 뷰성형외과 | O (1위) | 뷰성형외과, 서울대학교병원 성형외과 | O (1위) | 뷰성형외과 |
|
||||
| A-05 | 뷰성형외과 의료진 몇 명이야? 다 전문의야? | △ | O (1위) | 뷰성형외과 | O (1위) | 뷰성형외과의원 | O (1위) | 뷰성형외과의원 |
|
||||
| A-06 | 뷰성형외과가 렛미인 나온 병원 맞아? | ✕ | O (1위) | 뷰성형외과 | O (1위) | 뷰성형외과 | O (1위) | 뷰성형외과 |
|
||||
| A-07 | 뷰성형외과 언제 생긴 병원이야? 오래됐어? | ✕ | O (1위) | 뷰성형외과 | O (1위) | 뷰성형외과 | O (1위) | 뷰성형외과 |
|
||||
| A-08 | 뷰성형외과 건물이 통째로 병원이라던데 맞아? | △ | O (1위) | 뷰성형외과 | O (1위) | 뷰성형외과, 스마일뷰치과의원 | O (1위) | 뷰성형외과의원 |
|
||||
| A-09 | 뷰성형외과 강남언니 평점 몇 점이야? | ◯ | O (1위) | 뷰성형외과 | O (1위) | 뷰성형외과의원 | O (1위) | 뷰성형외과의원 |
|
||||
| A-10 | 뷰성형외과 유튜브 채널 있어? | ◯ | O (1위) | 뷰성형외과 | O (1위) | 뷰성형외과 | O (1위) | 뷰성형외과, 레아트성형외과의원 |
|
||||
| A-11 | 뷰성형외과랑 다른 강남 대형 성형외과랑 뭐가 달라? | ✕ | O (1위) | 뷰성형외과 | O (1위) | 뷰성형외과 | O (1위) | 뷰성형외과 |
|
||||
| A-12 | 뷰성형외과 단점이나 주의할 점 있어? | ✕ | O (1위) | 뷰성형외과 | O (1위) | 뷰성형외과 | O (1위) | 뷰성형외과 |
|
||||
| # | 질문 | 사전판정 | GPT | GPT 상위 병원 | PPLX | PPLX 상위 병원 |
|
||||
|---|---|---|---|---|---|---|
|
||||
| A-01 | 뷰성형외과 어때? 믿을만한 병원이야? | △ | O (1위) | 뷰성형외과 | O (1위) | 뷰성형외과 |
|
||||
| A-02 | 뷰성형외과는 어떤 병원이야? 소개해줘 | △ | O (1위) | 뷰성형외과 | O (1위) | 뷰성형외과 |
|
||||
| A-03 | 뷰성형외과는 무슨 수술을 주로 해? | △ | O (1위) | 뷰성형외과 | O (1위) | 뷰성형외과 |
|
||||
| A-04 | 뷰성형외과 최순우 원장 어떤 의사야? | △ | O (1위) | 뷰성형외과 | O (1위) | 뷰성형외과, 서울대학교병원 성형외과 |
|
||||
| A-05 | 뷰성형외과 의료진 몇 명이야? 다 전문의야? | △ | O (1위) | 뷰성형외과 | O (1위) | 뷰성형외과의원 |
|
||||
| A-06 | 뷰성형외과가 렛미인 나온 병원 맞아? | ✕ | O (1위) | 뷰성형외과 | O (1위) | 뷰성형외과 |
|
||||
| A-07 | 뷰성형외과 언제 생긴 병원이야? 오래됐어? | ✕ | O (1위) | 뷰성형외과 | O (1위) | 뷰성형외과 |
|
||||
| A-08 | 뷰성형외과 건물이 통째로 병원이라던데 맞아? | △ | O (1위) | 뷰성형외과 | O (1위) | 뷰성형외과, 스마일뷰치과의원 |
|
||||
| A-09 | 뷰성형외과 강남언니 평점 몇 점이야? | ◯ | O (1위) | 뷰성형외과 | O (1위) | 뷰성형외과의원 |
|
||||
| A-10 | 뷰성형외과 유튜브 채널 있어? | ◯ | O (1위) | 뷰성형외과 | O (1위) | 뷰성형외과 |
|
||||
| A-11 | 뷰성형외과랑 다른 강남 대형 성형외과랑 뭐가 달라? | ✕ | O (1위) | 뷰성형외과 | O (1위) | 뷰성형외과 |
|
||||
| A-12 | 뷰성형외과 단점이나 주의할 점 있어? | ✕ | O (1위) | 뷰성형외과 | O (1위) | 뷰성형외과 |
|
||||
### B. 위치·운영
|
||||
|
||||
| # | 질문 | 사전판정 | GPT | GPT 상위 병원 | PPLX | PPLX 상위 병원 | NAVER | NAVER 상위 병원 |
|
||||
|---|---|---|---|---|---|---|---|---|
|
||||
| B-01 | 뷰성형외과 주소 어디야? | ◯ | O (1위) | 뷰성형외과 | O (1위) | 뷰성형외과 | O (1위) | 뷰성형외과의원 |
|
||||
| B-02 | 뷰성형외과 신논현역에서 얼마나 걸려? | ◯ | O (1위) | 뷰성형외과 | O (1위) | 뷰성형외과 | O (1위) | 뷰성형외과의원 |
|
||||
| B-03 | 뷰성형외과 전화번호 알려줘 | △ | O (1위) | 뷰성형외과 | O (1위) | 뷰성형외과 | O (1위) | 뷰성형외과 |
|
||||
| B-04 | 뷰성형외과 진료시간 어떻게 돼? | △ | O (1위) | 뷰성형외과 | O (1위) | 뷰성형외과 | O (1위) | 뷰성형외과의원 |
|
||||
| B-05 | 뷰성형외과 일요일이나 공휴일에도 해? | ✕ | O (1위) | 뷰성형외과 | O (1위) | 뷰성형외과 | O (1위) | 뷰성형외과의원 |
|
||||
| B-06 | 뷰성형외과 주차 되나? | ✕ | O (1위) | 뷰성형외과, 아이비성형외과 | O (1위) | 뷰성형외과 | O (1위) | 뷰성형외과 |
|
||||
| B-07 | 뷰성형외과 상담 예약 어떻게 해? | △ | O (1위) | 뷰성형외과 | O (1위) | 뷰성형외과 | O (1위) | 뷰성형외과 |
|
||||
| B-08 | 뷰성형외과 카톡으로 상담돼? | △ | O (1위) | 뷰성형외과 | O (1위) | 뷰성형외과 | O (1위) | 뷰성형외과 |
|
||||
| B-09 | 뷰성형외과 상담받으러 가면 몇 층으로 가? | △ | O (1위) | 뷰성형외과 | O (1위) | 뷰성형외과 | O (1위) | 뷰성형외과, 뷰성형외과의원 |
|
||||
| B-10 | 신논현역 3번 출구 근처 성형외과 뭐 있어? | ◯ | O (1위) | VIEW Plastic Surgery, 스타라인성형외과, 포레성형외과의원, 링크성형외과, 플레저성형외과 | O (8위) | 우아인성형외과, 플레저성형외과, 링크성형외과, 나나성형외과, 밴스성형외과(신논현점) | X | 플레저성형외과의원, 강남오엘의원, 밴스성형외과의원 |
|
||||
| # | 질문 | 사전판정 | GPT | GPT 상위 병원 | PPLX | PPLX 상위 병원 |
|
||||
|---|---|---|---|---|---|---|
|
||||
| B-01 | 뷰성형외과 주소 어디야? | ◯ | O (1위) | 뷰성형외과 | O (1위) | 뷰성형외과 |
|
||||
| B-02 | 뷰성형외과 신논현역에서 얼마나 걸려? | ◯ | O (1위) | 뷰성형외과 | O (1위) | 뷰성형외과 |
|
||||
| B-03 | 뷰성형외과 전화번호 알려줘 | △ | O (1위) | 뷰성형외과 | O (1위) | 뷰성형외과 |
|
||||
| B-04 | 뷰성형외과 진료시간 어떻게 돼? | △ | O (1위) | 뷰성형외과 | O (1위) | 뷰성형외과 |
|
||||
| B-05 | 뷰성형외과 일요일이나 공휴일에도 해? | ✕ | O (1위) | 뷰성형외과 | O (1위) | 뷰성형외과 |
|
||||
| B-06 | 뷰성형외과 주차 되나? | ✕ | O (1위) | 뷰성형외과, 아이비성형외과 | O (1위) | 뷰성형외과 |
|
||||
| B-07 | 뷰성형외과 상담 예약 어떻게 해? | △ | O (1위) | 뷰성형외과 | O (1위) | 뷰성형외과 |
|
||||
| B-08 | 뷰성형외과 카톡으로 상담돼? | △ | O (1위) | 뷰성형외과 | O (1위) | 뷰성형외과 |
|
||||
| B-09 | 뷰성형외과 상담받으러 가면 몇 층으로 가? | △ | O (1위) | 뷰성형외과 | O (1위) | 뷰성형외과 |
|
||||
| B-10 | 신논현역 3번 출구 근처 성형외과 뭐 있어? | ◯ | O (1위) | VIEW Plastic Surgery, 스타라인성형외과, 포레성형외과의원, 링크성형외과, 플레저성형외과 | O (8위) | 우아인성형외과, 플레저성형외과, 링크성형외과, 나나성형외과, 밴스성형외과(신논현점) |
|
||||
### C. 지역 추천·비교
|
||||
|
||||
| # | 질문 | 사전판정 | GPT | GPT 상위 병원 | PPLX | PPLX 상위 병원 | NAVER | NAVER 상위 병원 |
|
||||
|---|---|---|---|---|---|---|---|---|
|
||||
| C-01 | 강남 성형외과 추천해줘 | ✕ | O (5위) | Honesty Plastic Surgery, Kokoline Clinic, Sketch Plastic Surgery, Shimmian Clinic, The Door Plastic Surgery | X | 바노바기성형외과, 디에이성형외과, 1mm성형외과, 에디션성형외과의원, 시크릿성형외과·피부과 | O (1위) | 업투유, 뷰성형외과 |
|
||||
| C-02 | 강남에서 가슴성형 잘하는 병원 어디야? | ✕ | O (2위) | 우아성형외과, 뷰성형외과의원, 아이디병원, 바탕성형외과, 위쉬성형외과 | X | 엄나구모 성형외과, 유앤유성형외과, 윈느성형외과, 바노바기성형외과, 마블성형외과 | X | (병원 언급 없음) |
|
||||
| C-03 | 강남 안면윤곽 유명한 병원 알려줘 | ✕ | X | 플랜에스의원 강남, 한미인의원 강남본점, 오르타의원 신관, 팽팽의원 강남, Bondi Plastic Surgery | X | 아이디병원, 삼사오성형외과, 오늘성형외과, 유노성형외과, 현대미학성형외과 | X | 아이니크성형외과의원, 청담여신성형외과의원, 아이앤지성형외과의원, 오인성형외과의원, 강남제일 성형외과 |
|
||||
| C-04 | 코성형 잘하는 강남 병원 추천해줘 | ✕ | O (4위) | 기린성형외과 서초, 어린공주성형외과 강남, ID병원 강남, 뷰성형외과 강남, 프리미엄 리벤스 이비인후과의원 | X | 코원성형외과의원, 링크성형외과, 오엠성형외과, 코비쥬의원, 베리굿 성형외과 | X | (병원 언급 없음) |
|
||||
| C-05 | 눈 재수술 잘하는 병원 어디야? | ✕ | X | (병원 언급 없음) | X | 더스완성형외과, 강남서연성형외과, 팝성형외과, JK성형외과 | X | (병원 언급 없음) |
|
||||
| C-06 | 강남 성형외과 톱5 알려줘 | ✕ | O (2위) | The Door Plastic Surgery, View Plastic Surgery, Rohsanghoon Plastic Surgery, Gangnam Oz Plastic Surgery, The Plastic Surgery | X | 아이디병원, 원진성형외과, JK성형외과, 바노바기성형외과, 그랜드성형외과 | X | 탑클래스성형외과의원, 강남성형외과 |
|
||||
| C-07 | 가슴성형 병원 3곳만 골라서 비교해줘 | ✕ | X | 강남 세란성형외과, 병원ABC, 병원XYZ | X | 땡큐성형외과의원, 밴스성형외과의원, 티제이성형외과의원 | X | 더성형외과, 나나, 위쉬, 조이플, 노트성형외과 |
|
||||
| C-08 | 마취과 전문의가 상주하는 강남 성형외과 어디야? | △ | O (3위) | 디에이성형외과, 윈느성형외과, 뷰성형외과의원, 더성형외과, 제이스성형외과 | X | 디에이성형외과, 현대미학성형외과, 노트성형외과, 에이비성형외과, 우리성형외과 | X | 디에이성형외과, 현대미학성형외과, V&MJ피부과의원, 서울페이스21, 강남수병원 |
|
||||
| C-09 | 수술실 CCTV 있는 성형외과 추천해줘 | △ | X | 리브리 성형외과, 브라운 성형외과, 스케치 성형외과, 이룸 성형외과, 에이펙스 성형외과 | X | 이룸성형외과, BK성형외과, 라이크성형외과, 티와이성형외과 | X | 마인성형외과의원, 옐로우성형외과의원, 특정 강남역 성형외과 |
|
||||
| C-10 | 여의사한테 수술받을 수 있는 성형외과 있어? | △ | X | (병원 언급 없음) | X | 조이플성형외과의원, 시크릿성형외과, 홍승현성형외과의원 | X | (병원 언급 없음) |
|
||||
| C-11 | 후기 제일 많은 강남 성형외과 어디야? | △ | O (5위) | 한미인의원 강남본점, 쁨글로벌의원 강남점, 팽팽의원 강남, 아비쥬의원 강남점, 릴리브의원 압구정 | X | 우리성형외과의원, 어린공주성형외과의원, 반니성형외과의원 | X | (병원 언급 없음) |
|
||||
| C-12 | 강남언니에서 평점 높은 성형외과 알려줘 | △ | O (6위) | 디에이성형외과의원, 팝성형외과의원, 나나성형외과의원, 에이트성형외과의원, 아이디병원‑본원 | O (6위) | 인트로성형외과의원, 그날성형외과의원, 우리성형외과의원, 팝성형외과의원, 삼사오성형외과의원 | 브리핑 없음 | - |
|
||||
| C-13 | 20년 넘게 운영한 강남 성형외과 있어? | ✕ | X | 제림성형외과, 김종서 성형외과, JK 성형외과, 아이디 병원, JW 성형외과 | X | JK성형외과, 드림성형외과, 강남서연성형외과, 글램성형외과, 마이라인성형외과의원 | X | 함희정성형외과의원, JK성형외과의원, 더올림성형외과의원 |
|
||||
| C-14 | 리프팅 잘하는 강남 병원 추천해줘 | ✕ | O (None위) | 한미인의원 강남본점, 오르타의원 신관, 팽팽의원, 더클리닉 강남점, 청담 라비앙로즈 | X | 온리프의원, 리팅성형외과의원, 윌비성형외과의원, 톡스앤필 강남본점, 리베리의원 강남점 | X | (병원 언급 없음) |
|
||||
| C-15 | 의사 여러 명 있는 대형 성형외과가 나아? 어디 추천해? | ✕ | X | (병원 언급 없음) | X | 디에이성형외과의원, 미인성형외과, 베리굿 성형외과, 삼성서울병원 성형외과 | X | (병원 언급 없음) |
|
||||
| # | 질문 | 사전판정 | GPT | GPT 상위 병원 | PPLX | PPLX 상위 병원 |
|
||||
|---|---|---|---|---|---|---|
|
||||
| C-01 | 강남 성형외과 추천해줘 | ✕ | O (5위) | Honesty Plastic Surgery, Kokoline Clinic, Sketch Plastic Surgery, Shimmian Clinic, The Door Plastic Surgery | X | 바노바기성형외과, 디에이성형외과, 1mm성형외과, 에디션성형외과의원, 시크릿성형외과·피부과 |
|
||||
| C-02 | 강남에서 가슴성형 잘하는 병원 어디야? | ✕ | O (2위) | 우아성형외과, 뷰성형외과의원, 아이디병원, 바탕성형외과, 위쉬성형외과 | X | 엄나구모 성형외과, 유앤유성형외과, 윈느성형외과, 바노바기성형외과, 마블성형외과 |
|
||||
| C-03 | 강남 안면윤곽 유명한 병원 알려줘 | ✕ | X | 플랜에스의원 강남, 한미인의원 강남본점, 오르타의원 신관, 팽팽의원 강남, Bondi Plastic Surgery | X | 아이디병원, 삼사오성형외과, 오늘성형외과, 유노성형외과, 현대미학성형외과 |
|
||||
| C-04 | 코성형 잘하는 강남 병원 추천해줘 | ✕ | O (4위) | 기린성형외과 서초, 어린공주성형외과 강남, ID병원 강남, 뷰성형외과 강남, 프리미엄 리벤스 이비인후과의원 | X | 코원성형외과의원, 링크성형외과, 오엠성형외과, 코비쥬의원, 베리굿 성형외과 |
|
||||
| C-05 | 눈 재수술 잘하는 병원 어디야? | ✕ | X | (병원 언급 없음) | X | 더스완성형외과, 강남서연성형외과, 팝성형외과, JK성형외과 |
|
||||
| C-06 | 강남 성형외과 톱5 알려줘 | ✕ | O (2위) | The Door Plastic Surgery, View Plastic Surgery, Rohsanghoon Plastic Surgery, Gangnam Oz Plastic Surgery, The Plastic Surgery | X | 아이디병원, 원진성형외과, JK성형외과, 바노바기성형외과, 그랜드성형외과 |
|
||||
| C-07 | 가슴성형 병원 3곳만 골라서 비교해줘 | ✕ | X | 강남 세란성형외과, 병원ABC, 병원XYZ | X | 땡큐성형외과의원, 밴스성형외과의원, 티제이성형외과의원 |
|
||||
| C-08 | 마취과 전문의가 상주하는 강남 성형외과 어디야? | △ | O (3위) | 디에이성형외과, 윈느성형외과, 뷰성형외과의원, 더성형외과, 제이스성형외과 | X | 디에이성형외과, 현대미학성형외과, 노트성형외과, 에이비성형외과, 우리성형외과 |
|
||||
| C-09 | 수술실 CCTV 있는 성형외과 추천해줘 | △ | X | 리브리 성형외과, 브라운 성형외과, 스케치 성형외과, 이룸 성형외과, 에이펙스 성형외과 | X | 이룸성형외과, BK성형외과, 라이크성형외과, 티와이성형외과 |
|
||||
| C-10 | 여의사한테 수술받을 수 있는 성형외과 있어? | △ | X | (병원 언급 없음) | X | 조이플성형외과의원, 시크릿성형외과, 홍승현성형외과의원 |
|
||||
| C-11 | 후기 제일 많은 강남 성형외과 어디야? | △ | O (5위) | 한미인의원 강남본점, 쁨글로벌의원 강남점, 팽팽의원 강남, 아비쥬의원 강남점, 릴리브의원 압구정 | X | 우리성형외과의원, 어린공주성형외과의원, 반니성형외과의원 |
|
||||
| C-12 | 강남언니에서 평점 높은 성형외과 알려줘 | △ | O (6위) | 디에이성형외과의원, 팝성형외과의원, 나나성형외과의원, 에이트성형외과의원, 아이디병원‑본원 | O (6위) | 인트로성형외과의원, 그날성형외과의원, 우리성형외과의원, 팝성형외과의원, 삼사오성형외과의원 |
|
||||
| C-13 | 20년 넘게 운영한 강남 성형외과 있어? | ✕ | X | 제림성형외과, 김종서 성형외과, JK 성형외과, 아이디 병원, JW 성형외과 | X | JK성형외과, 드림성형외과, 강남서연성형외과, 글램성형외과, 마이라인성형외과의원 |
|
||||
| C-14 | 리프팅 잘하는 강남 병원 추천해줘 | ✕ | O (None위) | 한미인의원 강남본점, 오르타의원 신관, 팽팽의원, 더클리닉 강남점, 청담 라비앙로즈 | X | 온리프의원, 리팅성형외과의원, 윌비성형외과의원, 톡스앤필 강남본점, 리베리의원 강남점 |
|
||||
| C-15 | 의사 여러 명 있는 대형 성형외과가 나아? 어디 추천해? | ✕ | X | (병원 언급 없음) | X | 디에이성형외과의원, 미인성형외과, 베리굿 성형외과, 삼성서울병원 성형외과 |
|
||||
### D1. 가슴성형
|
||||
|
||||
| # | 질문 | 사전판정 | GPT | GPT 상위 병원 | PPLX | PPLX 상위 병원 | NAVER | NAVER 상위 병원 |
|
||||
|---|---|---|---|---|---|---|---|---|
|
||||
| D1-01 | 뷰성형외과 가슴성형 잘해? | △ | O (1위) | 뷰성형외과 | O (1위) | 뷰성형외과 | O (1위) | 뷰성형외과 |
|
||||
| D1-02 | 가슴 보형물 종류 뭐가 있어? | ✕ | X | (병원 언급 없음) | X | (병원 언급 없음) | X | (병원 언급 없음) |
|
||||
| D1-03 | 모티바랑 멘토 보형물 차이가 뭐야? | ✕ | X | (병원 언급 없음) | X | (병원 언급 없음) | X | (병원 언급 없음) |
|
||||
| D1-04 | 가슴성형 수술시간 얼마나 걸려? | ✕ | X | (병원 언급 없음) | X | (병원 언급 없음) | X | (병원 언급 없음) |
|
||||
| D1-05 | 가슴성형 회복기간 어느 정도야? 언제 출근해? | ✕ | X | (병원 언급 없음) | X | (병원 언급 없음) | X | (병원 언급 없음) |
|
||||
| D1-06 | 가슴성형 흉터 어디에 남아? | ✕ | X | (병원 언급 없음) | X | (병원 언급 없음) | X | (병원 언급 없음) |
|
||||
| D1-07 | 구형구축이 뭐야? 예방할 수 있어? | ✕ | X | (병원 언급 없음) | X | (병원 언급 없음) | X | (병원 언급 없음) |
|
||||
| D1-08 | 가슴 지방이식이랑 보형물 중에 뭐가 나아? | ✕ | X | (병원 언급 없음) | X | (병원 언급 없음) | X | (병원 언급 없음) |
|
||||
| D1-09 | 가슴 재수술은 언제 해야 해? | ✕ | X | (병원 언급 없음) | X | (병원 언급 없음) | X | (병원 언급 없음) |
|
||||
| D1-10 | 가슴성형하면 수유에 영향 있어? | ✕ | X | (병원 언급 없음) | X | (병원 언급 없음) | X | (병원 언급 없음) |
|
||||
| D1-11 | 가슴성형 후 정기검진 뭐 받아야 해? | ✕ | X | (병원 언급 없음) | X | (병원 언급 없음) | X | (병원 언급 없음) |
|
||||
| D1-12 | 가슴성형 과정 잘 설명해주는 영상 있어? | △ | X | (병원 언급 없음) | X | (병원 언급 없음) | X | (병원 언급 없음) |
|
||||
| # | 질문 | 사전판정 | GPT | GPT 상위 병원 | PPLX | PPLX 상위 병원 |
|
||||
|---|---|---|---|---|---|---|
|
||||
| D1-01 | 뷰성형외과 가슴성형 잘해? | △ | O (1위) | 뷰성형외과 | O (1위) | 뷰성형외과 |
|
||||
| D1-02 | 가슴 보형물 종류 뭐가 있어? | ✕ | X | (병원 언급 없음) | X | (병원 언급 없음) |
|
||||
| D1-03 | 모티바랑 멘토 보형물 차이가 뭐야? | ✕ | X | (병원 언급 없음) | X | (병원 언급 없음) |
|
||||
| D1-04 | 가슴성형 수술시간 얼마나 걸려? | ✕ | X | (병원 언급 없음) | X | (병원 언급 없음) |
|
||||
| D1-05 | 가슴성형 회복기간 어느 정도야? 언제 출근해? | ✕ | X | (병원 언급 없음) | X | (병원 언급 없음) |
|
||||
| D1-06 | 가슴성형 흉터 어디에 남아? | ✕ | X | (병원 언급 없음) | X | (병원 언급 없음) |
|
||||
| D1-07 | 구형구축이 뭐야? 예방할 수 있어? | ✕ | X | (병원 언급 없음) | X | (병원 언급 없음) |
|
||||
| D1-08 | 가슴 지방이식이랑 보형물 중에 뭐가 나아? | ✕ | X | (병원 언급 없음) | X | (병원 언급 없음) |
|
||||
| D1-09 | 가슴 재수술은 언제 해야 해? | ✕ | X | (병원 언급 없음) | X | (병원 언급 없음) |
|
||||
| D1-10 | 가슴성형하면 수유에 영향 있어? | ✕ | X | (병원 언급 없음) | X | (병원 언급 없음) |
|
||||
| D1-11 | 가슴성형 후 정기검진 뭐 받아야 해? | ✕ | X | (병원 언급 없음) | X | (병원 언급 없음) |
|
||||
| D1-12 | 가슴성형 과정 잘 설명해주는 영상 있어? | △ | X | (병원 언급 없음) | X | (병원 언급 없음) |
|
||||
### D2. 눈성형
|
||||
|
||||
| # | 질문 | 사전판정 | GPT | GPT 상위 병원 | PPLX | PPLX 상위 병원 | NAVER | NAVER 상위 병원 |
|
||||
|---|---|---|---|---|---|---|---|---|
|
||||
| D2-01 | 뷰성형외과 눈성형 어때? | △ | O (1위) | 뷰성형외과 | O (1위) | 뷰성형외과 | O (1위) | 뷰성형외과 |
|
||||
| D2-02 | 쌍수 절개랑 매몰 차이가 뭐야? | ✕ | X | (병원 언급 없음) | X | (병원 언급 없음) | X | (병원 언급 없음) |
|
||||
| D2-03 | 자연유착 쌍수가 뭐야? | ✕ | X | (병원 언급 없음) | X | (병원 언급 없음) | X | (병원 언급 없음) |
|
||||
| D2-04 | 눈매교정은 쌍수랑 같이 해야 해? | ✕ | X | (병원 언급 없음) | X | (병원 언급 없음) | X | (병원 언급 없음) |
|
||||
| D2-05 | 앞트임 뒤트임 밑트임 뭐가 달라? | ✕ | X | (병원 언급 없음) | X | (병원 언급 없음) | X | (병원 언급 없음) |
|
||||
| D2-06 | 쌍수 회복기간 얼마나 걸려? | ✕ | X | (병원 언급 없음) | X | (병원 언급 없음) | X | (병원 언급 없음) |
|
||||
| D2-07 | 눈 재수술은 첫 수술 후 언제부터 가능해? | ✕ | X | (병원 언급 없음) | X | (병원 언급 없음) | X | (병원 언급 없음) |
|
||||
| D2-08 | 앞트임하면 잘 어울리는 얼굴이 따로 있어? | △ | X | (병원 언급 없음) | X | (병원 언급 없음) | X | (병원 언급 없음) |
|
||||
| D2-09 | 쌍수 붓기 빨리 빼는 법 있어? | ✕ | X | (병원 언급 없음) | X | (병원 언급 없음) | X | (병원 언급 없음) |
|
||||
| # | 질문 | 사전판정 | GPT | GPT 상위 병원 | PPLX | PPLX 상위 병원 |
|
||||
|---|---|---|---|---|---|---|
|
||||
| D2-01 | 뷰성형외과 눈성형 어때? | △ | O (1위) | 뷰성형외과 | O (1위) | 뷰성형외과 |
|
||||
| D2-02 | 쌍수 절개랑 매몰 차이가 뭐야? | ✕ | X | (병원 언급 없음) | X | (병원 언급 없음) |
|
||||
| D2-03 | 자연유착 쌍수가 뭐야? | ✕ | X | (병원 언급 없음) | X | (병원 언급 없음) |
|
||||
| D2-04 | 눈매교정은 쌍수랑 같이 해야 해? | ✕ | X | (병원 언급 없음) | X | (병원 언급 없음) |
|
||||
| D2-05 | 앞트임 뒤트임 밑트임 뭐가 달라? | ✕ | X | (병원 언급 없음) | X | (병원 언급 없음) |
|
||||
| D2-06 | 쌍수 회복기간 얼마나 걸려? | ✕ | X | (병원 언급 없음) | X | (병원 언급 없음) |
|
||||
| D2-07 | 눈 재수술은 첫 수술 후 언제부터 가능해? | ✕ | X | (병원 언급 없음) | X | (병원 언급 없음) |
|
||||
| D2-08 | 앞트임하면 잘 어울리는 얼굴이 따로 있어? | △ | X | (병원 언급 없음) | X | (병원 언급 없음) |
|
||||
| D2-09 | 쌍수 붓기 빨리 빼는 법 있어? | ✕ | X | (병원 언급 없음) | X | (병원 언급 없음) |
|
||||
### D3. 코성형
|
||||
|
||||
| # | 질문 | 사전판정 | GPT | GPT 상위 병원 | PPLX | PPLX 상위 병원 | NAVER | NAVER 상위 병원 |
|
||||
|---|---|---|---|---|---|---|---|---|
|
||||
| D3-01 | 뷰성형외과 코성형 어때? | △ | O (1위) | 뷰성형외과 | O (1위) | 뷰성형외과 | O (1위) | VIEW성형외과 |
|
||||
| D3-02 | 코성형 재료 뭐뭐 있어? | ✕ | X | (병원 언급 없음) | X | (병원 언급 없음) | X | (병원 언급 없음) |
|
||||
| D3-03 | 실리콘이랑 자가연골 중에 뭐가 나아? | ✕ | X | (병원 언급 없음) | X | (병원 언급 없음) | X | (병원 언급 없음) |
|
||||
| D3-04 | 콧대만 할까 코끝까지 같이 할까? | ✕ | X | (병원 언급 없음) | X | (병원 언급 없음) | X | (병원 언급 없음) |
|
||||
| D3-05 | 코성형 회복기간 얼마나 돼? | ✕ | X | (병원 언급 없음) | X | (병원 언급 없음) | X | (병원 언급 없음) |
|
||||
| D3-06 | 코 재수술은 언제부터 할 수 있어? | ✕ | X | (병원 언급 없음) | X | (병원 언급 없음) | X | (병원 언급 없음) |
|
||||
| D3-07 | 휜코 교정도 성형외과에서 해? | ✕ | X | (병원 언급 없음) | X | (병원 언급 없음) | X | (병원 언급 없음) |
|
||||
| D3-08 | 복코 수술 어떻게 하는 거야? | ✕ | X | (병원 언급 없음) | X | (병원 언급 없음) | X | (병원 언급 없음) |
|
||||
| D3-09 | 내 얼굴형에 맞는 코 모양 어떻게 정해? | △ | X | (병원 언급 없음) | X | (병원 언급 없음) | X | (병원 언급 없음) |
|
||||
| # | 질문 | 사전판정 | GPT | GPT 상위 병원 | PPLX | PPLX 상위 병원 |
|
||||
|---|---|---|---|---|---|---|
|
||||
| D3-01 | 뷰성형외과 코성형 어때? | △ | O (1위) | 뷰성형외과 | O (1위) | 뷰성형외과 |
|
||||
| D3-02 | 코성형 재료 뭐뭐 있어? | ✕ | X | (병원 언급 없음) | X | (병원 언급 없음) |
|
||||
| D3-03 | 실리콘이랑 자가연골 중에 뭐가 나아? | ✕ | X | (병원 언급 없음) | X | (병원 언급 없음) |
|
||||
| D3-04 | 콧대만 할까 코끝까지 같이 할까? | ✕ | X | (병원 언급 없음) | X | (병원 언급 없음) |
|
||||
| D3-05 | 코성형 회복기간 얼마나 돼? | ✕ | X | (병원 언급 없음) | X | (병원 언급 없음) |
|
||||
| D3-06 | 코 재수술은 언제부터 할 수 있어? | ✕ | X | (병원 언급 없음) | X | (병원 언급 없음) |
|
||||
| D3-07 | 휜코 교정도 성형외과에서 해? | ✕ | X | (병원 언급 없음) | X | (병원 언급 없음) |
|
||||
| D3-08 | 복코 수술 어떻게 하는 거야? | ✕ | X | (병원 언급 없음) | X | (병원 언급 없음) |
|
||||
| D3-09 | 내 얼굴형에 맞는 코 모양 어떻게 정해? | △ | X | (병원 언급 없음) | X | (병원 언급 없음) |
|
||||
### D4. 안면윤곽·양악
|
||||
|
||||
| # | 질문 | 사전판정 | GPT | GPT 상위 병원 | PPLX | PPLX 상위 병원 | NAVER | NAVER 상위 병원 |
|
||||
|---|---|---|---|---|---|---|---|---|
|
||||
| D4-01 | 뷰성형외과 안면윤곽 잘하는 곳 맞아? | △ | O (1위) | 뷰성형외과 | O (1위) | 뷰성형외과 | O (1위) | 뷰성형외과 |
|
||||
| D4-02 | 광대축소 수술 어떻게 하는 거야? | ✕ | X | (병원 언급 없음) | X | (병원 언급 없음) | X | (병원 언급 없음) |
|
||||
| D4-03 | V라인 사각턱 수술 부작용 없어? | ✕ | X | (병원 언급 없음) | X | (병원 언급 없음) | X | (병원 언급 없음) |
|
||||
| D4-04 | 양악수술은 어떤 경우에 해야 해? | ✕ | X | (병원 언급 없음) | X | (병원 언급 없음) | X | (병원 언급 없음) |
|
||||
| D4-05 | 윤곽수술 신경 손상 위험 얼마나 돼? | ✕ | X | (병원 언급 없음) | X | (병원 언급 없음) | X | (병원 언급 없음) |
|
||||
| D4-06 | 윤곽수술 회복기간 얼마나 걸려? | ✕ | X | (병원 언급 없음) | X | (병원 언급 없음) | X | (병원 언급 없음) |
|
||||
| D4-07 | 윤곽수술 붓기 언제 빠져? | ✕ | X | (병원 언급 없음) | X | (병원 언급 없음) | X | (병원 언급 없음) |
|
||||
| D4-08 | 윤곽이랑 코성형 같이 해도 돼? | ✕ | X | (병원 언급 없음) | X | (병원 언급 없음) | X | (병원 언급 없음) |
|
||||
| D4-09 | 안면윤곽으로 상 받은 병원 있어? | ✕ | X | 강남 오앤의원 | X | 서울페이스21치과병원, 디에이성형외과의원, 1퍼센트성형외과 | X | (병원 언급 없음) |
|
||||
| # | 질문 | 사전판정 | GPT | GPT 상위 병원 | PPLX | PPLX 상위 병원 |
|
||||
|---|---|---|---|---|---|---|
|
||||
| D4-01 | 뷰성형외과 안면윤곽 잘하는 곳 맞아? | △ | O (1위) | 뷰성형외과 | O (1위) | 뷰성형외과 |
|
||||
| D4-02 | 광대축소 수술 어떻게 하는 거야? | ✕ | X | (병원 언급 없음) | X | (병원 언급 없음) |
|
||||
| D4-03 | V라인 사각턱 수술 부작용 없어? | ✕ | X | (병원 언급 없음) | X | (병원 언급 없음) |
|
||||
| D4-04 | 양악수술은 어떤 경우에 해야 해? | ✕ | X | (병원 언급 없음) | X | (병원 언급 없음) |
|
||||
| D4-05 | 윤곽수술 신경 손상 위험 얼마나 돼? | ✕ | X | (병원 언급 없음) | X | (병원 언급 없음) |
|
||||
| D4-06 | 윤곽수술 회복기간 얼마나 걸려? | ✕ | X | (병원 언급 없음) | X | (병원 언급 없음) |
|
||||
| D4-07 | 윤곽수술 붓기 언제 빠져? | ✕ | X | (병원 언급 없음) | X | (병원 언급 없음) |
|
||||
| D4-08 | 윤곽이랑 코성형 같이 해도 돼? | ✕ | X | (병원 언급 없음) | X | (병원 언급 없음) |
|
||||
| D4-09 | 안면윤곽으로 상 받은 병원 있어? | ✕ | X | 강남 오앤의원 | X | 서울페이스21치과병원, 디에이성형외과의원, 1퍼센트성형외과 |
|
||||
### D5. 지방성형
|
||||
|
||||
| # | 질문 | 사전판정 | GPT | GPT 상위 병원 | PPLX | PPLX 상위 병원 | NAVER | NAVER 상위 병원 |
|
||||
|---|---|---|---|---|---|---|---|---|
|
||||
| D5-01 | 지방흡입 부위별로 뭐가 달라? | ✕ | X | (병원 언급 없음) | X | (병원 언급 없음) | X | (병원 언급 없음) |
|
||||
| D5-02 | 얼굴 지방이식하면 자연스러워? | ✕ | X | (병원 언급 없음) | X | (병원 언급 없음) | X | (병원 언급 없음) |
|
||||
| D5-03 | 지방이식 생착률 얼마나 돼? | ✕ | X | (병원 언급 없음) | X | (병원 언급 없음) | X | (병원 언급 없음) |
|
||||
| D5-04 | 복부 지방흡입 회복 얼마나 걸려? | ✕ | X | (병원 언급 없음) | X | (병원 언급 없음) | X | (병원 언급 없음) |
|
||||
| D5-05 | 뷰성형외과에서 지방성형도 해? | △ | O (1위) | 뷰성형외과 | O (1위) | 뷰성형외과 | O (1위) | 뷰성형외과 |
|
||||
| D5-06 | 지방흡입 흉터 남아? | ✕ | X | (병원 언급 없음) | X | (병원 언급 없음) | X | (병원 언급 없음) |
|
||||
| # | 질문 | 사전판정 | GPT | GPT 상위 병원 | PPLX | PPLX 상위 병원 |
|
||||
|---|---|---|---|---|---|---|
|
||||
| D5-01 | 지방흡입 부위별로 뭐가 달라? | ✕ | X | (병원 언급 없음) | X | (병원 언급 없음) |
|
||||
| D5-02 | 얼굴 지방이식하면 자연스러워? | ✕ | X | (병원 언급 없음) | X | (병원 언급 없음) |
|
||||
| D5-03 | 지방이식 생착률 얼마나 돼? | ✕ | X | (병원 언급 없음) | X | (병원 언급 없음) |
|
||||
| D5-04 | 복부 지방흡입 회복 얼마나 걸려? | ✕ | X | (병원 언급 없음) | X | (병원 언급 없음) |
|
||||
| D5-05 | 뷰성형외과에서 지방성형도 해? | △ | O (1위) | 뷰성형외과 | O (1위) | 뷰성형외과 |
|
||||
| D5-06 | 지방흡입 흉터 남아? | ✕ | X | (병원 언급 없음) | X | (병원 언급 없음) |
|
||||
### D6. 피부·리프팅
|
||||
|
||||
| # | 질문 | 사전판정 | GPT | GPT 상위 병원 | PPLX | PPLX 상위 병원 | NAVER | NAVER 상위 병원 |
|
||||
|---|---|---|---|---|---|---|---|---|
|
||||
| D6-01 | 리프팅 종류별로 뭐가 달라? | ✕ | X | (병원 언급 없음) | X | (병원 언급 없음) | X | (병원 언급 없음) |
|
||||
| D6-02 | 실리프팅이랑 안면거상 중에 뭐 해야 해? | ✕ | X | (병원 언급 없음) | X | (병원 언급 없음) | X | (병원 언급 없음) |
|
||||
| D6-03 | 울쎄라 효과 진짜 있어? | ✕ | X | (병원 언급 없음) | X | (병원 언급 없음) | X | (병원 언급 없음) |
|
||||
| D6-04 | 리프팅 효과 얼마나 유지돼? | ✕ | X | (병원 언급 없음) | X | (병원 언급 없음) | X | (병원 언급 없음) |
|
||||
| D6-05 | 40대인데 안티에이징 뭐부터 시작해? | ✕ | X | (병원 언급 없음) | X | (병원 언급 없음) | X | (병원 언급 없음) |
|
||||
| D6-06 | 뷰성형외과에서 리프팅도 받을 수 있어? | △ | O (1위) | 뷰성형외과 | O (1위) | 뷰성형외과 | O (1위) | 뷰성형외과 |
|
||||
| D6-07 | 수술 흉터 관리 어떻게 해? | ✕ | X | (병원 언급 없음) | X | (병원 언급 없음) | X | (병원 언급 없음) |
|
||||
| # | 질문 | 사전판정 | GPT | GPT 상위 병원 | PPLX | PPLX 상위 병원 |
|
||||
|---|---|---|---|---|---|---|
|
||||
| D6-01 | 리프팅 종류별로 뭐가 달라? | ✕ | X | (병원 언급 없음) | X | (병원 언급 없음) |
|
||||
| D6-02 | 실리프팅이랑 안면거상 중에 뭐 해야 해? | ✕ | X | (병원 언급 없음) | X | (병원 언급 없음) |
|
||||
| D6-03 | 울쎄라 효과 진짜 있어? | ✕ | X | (병원 언급 없음) | X | (병원 언급 없음) |
|
||||
| D6-04 | 리프팅 효과 얼마나 유지돼? | ✕ | X | (병원 언급 없음) | X | (병원 언급 없음) |
|
||||
| D6-05 | 40대인데 안티에이징 뭐부터 시작해? | ✕ | X | (병원 언급 없음) | X | (병원 언급 없음) |
|
||||
| D6-06 | 뷰성형외과에서 리프팅도 받을 수 있어? | △ | O (1위) | 뷰성형외과 | O (1위) | 뷰성형외과 |
|
||||
| D6-07 | 수술 흉터 관리 어떻게 해? | ✕ | X | (병원 언급 없음) | X | (병원 언급 없음) |
|
||||
### D7. 재수술
|
||||
|
||||
| # | 질문 | 사전판정 | GPT | GPT 상위 병원 | PPLX | PPLX 상위 병원 | NAVER | NAVER 상위 병원 |
|
||||
|---|---|---|---|---|---|---|---|---|
|
||||
| D7-01 | 재수술 전문 병원은 뭐가 달라? | ✕ | X | (병원 언급 없음) | X | (병원 언급 없음) | X | (병원 언급 없음) |
|
||||
| D7-02 | 뷰성형외과 재수술 잘해? | △ | O (1위) | 뷰성형외과 | O (1위) | 뷰성형외과 | O (1위) | 뷰성형외과 |
|
||||
| D7-03 | 성형 재수술은 왜 하게 되는 거야? | ✕ | X | (병원 언급 없음) | X | (병원 언급 없음) | X | (병원 언급 없음) |
|
||||
| D7-04 | 재수술은 처음보다 비용 더 비싸? | ✕ | X | (병원 언급 없음) | X | (병원 언급 없음) | X | (병원 언급 없음) |
|
||||
| D7-05 | 재수술 하려면 첫 수술 후 얼마나 기다려야 해? | ✕ | X | (병원 언급 없음) | X | (병원 언급 없음) | X | (병원 언급 없음) |
|
||||
| D7-06 | 다른 병원에서 한 수술도 재수술 상담해줘? | ✕ | X | (병원 언급 없음) | X | (병원 언급 없음) | X | (병원 언급 없음) |
|
||||
| # | 질문 | 사전판정 | GPT | GPT 상위 병원 | PPLX | PPLX 상위 병원 |
|
||||
|---|---|---|---|---|---|---|
|
||||
| D7-01 | 재수술 전문 병원은 뭐가 달라? | ✕ | X | (병원 언급 없음) | X | (병원 언급 없음) |
|
||||
| D7-02 | 뷰성형외과 재수술 잘해? | △ | O (1위) | 뷰성형외과 | O (1위) | 뷰성형외과 |
|
||||
| D7-03 | 성형 재수술은 왜 하게 되는 거야? | ✕ | X | (병원 언급 없음) | X | (병원 언급 없음) |
|
||||
| D7-04 | 재수술은 처음보다 비용 더 비싸? | ✕ | X | (병원 언급 없음) | X | (병원 언급 없음) |
|
||||
| D7-05 | 재수술 하려면 첫 수술 후 얼마나 기다려야 해? | ✕ | X | (병원 언급 없음) | X | (병원 언급 없음) |
|
||||
| D7-06 | 다른 병원에서 한 수술도 재수술 상담해줘? | ✕ | X | (병원 언급 없음) | X | (병원 언급 없음) |
|
||||
### E. 신뢰·안전
|
||||
|
||||
| # | 질문 | 사전판정 | GPT | GPT 상위 병원 | PPLX | PPLX 상위 병원 | NAVER | NAVER 상위 병원 |
|
||||
|---|---|---|---|---|---|---|---|---|
|
||||
| E-01 | 성형외과 고를 때 뭘 확인해야 해? | ✕ | X | (병원 언급 없음) | X | (병원 언급 없음) | X | (병원 언급 없음) |
|
||||
| E-02 | 성형수술 마취 사고 안 나려면 어떤 병원 가야 해? | ✕ | X | (병원 언급 없음) | X | (병원 언급 없음) | X | (병원 언급 없음) |
|
||||
| E-03 | 수술실 CCTV 있는지 어떻게 확인해? | ✕ | X | (병원 언급 없음) | X | (병원 언급 없음) | X | (병원 언급 없음) |
|
||||
| E-04 | 전신마취랑 수면마취 차이가 뭐야? | ✕ | X | (병원 언급 없음) | X | (병원 언급 없음) | X | (병원 언급 없음) |
|
||||
| E-05 | 성형외과 전문의인지 어떻게 확인해? | ✕ | X | (병원 언급 없음) | X | (병원 언급 없음) | X | (병원 언급 없음) |
|
||||
| E-06 | 응급상황 대응 시스템 갖춘 성형외과 어디야? | △ | X | 압구정서울성형외과, 아인병원 성형외과, PDM 박동만 성형외과, 리프트리성형외과, 엄나구모 성형외과 | X | 우리성형외과, 소다성형외과, Wannap 성형외과, 원진성형외과, 플레저 성형외과 | X | 압구정서울성형외과, 마침표성형외과, 디에이성형외과 |
|
||||
| E-07 | 성형 부작용 생기면 어떻게 해야 해? | ✕ | X | (병원 언급 없음) | X | (병원 언급 없음) | X | (병원 언급 없음) |
|
||||
| E-08 | 상담실장 말고 의사가 직접 상담해주는 병원 있어? | ✕ | X | (병원 언급 없음) | X | (병원 언급 없음) | X | (병원 언급 없음) |
|
||||
| E-09 | 뷰성형외과 안전 시스템 어때? | △ | O (1위) | 뷰성형외과 | O (1위) | 뷰성형외과 | O (1위) | 뷰성형외과 |
|
||||
| # | 질문 | 사전판정 | GPT | GPT 상위 병원 | PPLX | PPLX 상위 병원 |
|
||||
|---|---|---|---|---|---|---|
|
||||
| E-01 | 성형외과 고를 때 뭘 확인해야 해? | ✕ | X | (병원 언급 없음) | X | (병원 언급 없음) |
|
||||
| E-02 | 성형수술 마취 사고 안 나려면 어떤 병원 가야 해? | ✕ | X | (병원 언급 없음) | X | (병원 언급 없음) |
|
||||
| E-03 | 수술실 CCTV 있는지 어떻게 확인해? | ✕ | X | (병원 언급 없음) | X | (병원 언급 없음) |
|
||||
| E-04 | 전신마취랑 수면마취 차이가 뭐야? | ✕ | X | (병원 언급 없음) | X | (병원 언급 없음) |
|
||||
| E-05 | 성형외과 전문의인지 어떻게 확인해? | ✕ | X | (병원 언급 없음) | X | (병원 언급 없음) |
|
||||
| E-06 | 응급상황 대응 시스템 갖춘 성형외과 어디야? | △ | X | 압구정서울성형외과, 아인병원 성형외과, PDM 박동만 성형외과, 리프트리성형외과, 엄나구모 성형외과 | X | 우리성형외과, 소다성형외과, Wannap 성형외과, 원진성형외과, 플레저 성형외과 |
|
||||
| E-07 | 성형 부작용 생기면 어떻게 해야 해? | ✕ | X | (병원 언급 없음) | X | (병원 언급 없음) |
|
||||
| E-08 | 상담실장 말고 의사가 직접 상담해주는 병원 있어? | ✕ | X | (병원 언급 없음) | X | (병원 언급 없음) |
|
||||
| E-09 | 뷰성형외과 안전 시스템 어때? | △ | O (1위) | 뷰성형외과 | O (1위) | 뷰성형외과 |
|
||||
### F. 가격·이벤트
|
||||
|
||||
| # | 질문 | 사전판정 | GPT | GPT 상위 병원 | PPLX | PPLX 상위 병원 | NAVER | NAVER 상위 병원 |
|
||||
|---|---|---|---|---|---|---|---|---|
|
||||
| F-01 | 강남 가슴성형 평균 비용 얼마야? | ✕ | X | Gangnam Beauty Guide, Seoul Plastic Surgery Guide, SeoulBeautyTalk, ClinicSeoul.net, Gangnam Plastic Surgeon Cost Guide | X | (병원 언급 없음) | X | (병원 언급 없음) |
|
||||
| F-02 | 뷰성형외과 가슴성형 얼마야? | △ | O (1위) | 뷰성형외과 | O (1위) | 뷰성형외과 | O (1위) | 뷰성형외과 |
|
||||
| F-03 | 쌍수 가격 보통 얼마 해? | ✕ | X | (병원 언급 없음) | X | (병원 언급 없음) | X | (병원 언급 없음) |
|
||||
| F-04 | 코성형 견적 어느 정도 잡아야 해? | ✕ | X | (병원 언급 없음) | X | (병원 언급 없음) | X | (병원 언급 없음) |
|
||||
| F-05 | 뷰성형외과 지금 하는 이벤트 있어? | △ | O (1위) | 뷰성형외과 | O (1위) | 뷰성형외과 | O (1위) | 뷰성형외과 |
|
||||
| F-06 | 성형외과 이벤트가 믿어도 돼? 싼 데는 왜 싸? | ✕ | X | (병원 언급 없음) | X | (병원 언급 없음) | X | (병원 언급 없음) |
|
||||
| F-07 | 성형 비용 카드 할부나 분할 돼? | ✕ | X | (병원 언급 없음) | X | (병원 언급 없음) | X | (병원 언급 없음) |
|
||||
| F-08 | 성형 견적 여러 병원 비교하려면 어떻게 해? | ✕ | X | (병원 언급 없음) | X | (병원 언급 없음) | X | (병원 언급 없음) |
|
||||
| # | 질문 | 사전판정 | GPT | GPT 상위 병원 | PPLX | PPLX 상위 병원 |
|
||||
|---|---|---|---|---|---|---|
|
||||
| F-01 | 강남 가슴성형 평균 비용 얼마야? | ✕ | X | Gangnam Beauty Guide, Seoul Plastic Surgery Guide, SeoulBeautyTalk, ClinicSeoul.net, Gangnam Plastic Surgeon Cost Guide | X | (병원 언급 없음) |
|
||||
| F-02 | 뷰성형외과 가슴성형 얼마야? | △ | O (1위) | 뷰성형외과 | O (1위) | 뷰성형외과 |
|
||||
| F-03 | 쌍수 가격 보통 얼마 해? | ✕ | X | (병원 언급 없음) | X | (병원 언급 없음) |
|
||||
| F-04 | 코성형 견적 어느 정도 잡아야 해? | ✕ | X | (병원 언급 없음) | X | (병원 언급 없음) |
|
||||
| F-05 | 뷰성형외과 지금 하는 이벤트 있어? | △ | O (1위) | 뷰성형외과 | O (1위) | 뷰성형외과 |
|
||||
| F-06 | 성형외과 이벤트가 믿어도 돼? 싼 데는 왜 싸? | ✕ | X | (병원 언급 없음) | X | (병원 언급 없음) |
|
||||
| F-07 | 성형 비용 카드 할부나 분할 돼? | ✕ | X | (병원 언급 없음) | X | (병원 언급 없음) |
|
||||
| F-08 | 성형 견적 여러 병원 비교하려면 어떻게 해? | ✕ | X | (병원 언급 없음) | X | (병원 언급 없음) |
|
||||
### G. 후기·평판
|
||||
|
||||
| # | 질문 | 사전판정 | GPT | GPT 상위 병원 | PPLX | PPLX 상위 병원 | NAVER | NAVER 상위 병원 |
|
||||
|---|---|---|---|---|---|---|---|---|
|
||||
| G-01 | 뷰성형외과 실제 후기 어때? | △ | O (1위) | 뷰성형외과 | O (1위) | 뷰성형외과 | O (1위) | 뷰성형외과 |
|
||||
| G-02 | 뷰성형외과 부작용 후기나 안 좋은 얘기 있어? | ✕ | O (1위) | 뷰성형외과 | O (1위) | 뷰성형외과 | O (1위) | 뷰성형외과 |
|
||||
| G-03 | 강남언니 후기 믿을만해? | ✕ | X | (병원 언급 없음) | X | (병원 언급 없음) | X | (병원 언급 없음) |
|
||||
| G-04 | 뷰성형외과 수술 전후 사진 어디서 봐? | △ | X | (병원 언급 없음) | O (1위) | 뷰성형외과 | O (1위) | 뷰성형외과 |
|
||||
| G-05 | 성형 후기는 어디서 봐야 정확해? | ✕ | X | (병원 언급 없음) | X | (병원 언급 없음) | X | (병원 언급 없음) |
|
||||
| G-06 | 뷰성형외과 인스타그램 있어? | △ | O (1위) | 뷰성형외과 | O (1위) | 뷰성형외과 | O (1위) | 뷰성형외과 |
|
||||
| G-07 | 뷰성형외과는 뭘로 유명한 병원이야? | △ | O (1위) | 뷰성형외과 | O (1위) | 뷰성형외과 | O (1위) | 뷰성형외과 |
|
||||
| G-08 | 가슴성형 후기 많은 병원 어디야? | △ | O (5위) | WOOA Plastic Surgery Clinic, JW Plastic Surgery Center, SAERO Plastic Surgery, Dite Plastic Surgery, TJ Plastic Surgery | O (1위) | 뷰성형외과의원, 아이디병원-본원, 우아성형외과의원, 노트성형외과의원, 엄나구모성형외과의원 | X | 더성형외과, 노트성형외과, 위쉬성형외과, 나나성형외과, 물방울성형외과 |
|
||||
| # | 질문 | 사전판정 | GPT | GPT 상위 병원 | PPLX | PPLX 상위 병원 |
|
||||
|---|---|---|---|---|---|---|
|
||||
| G-01 | 뷰성형외과 실제 후기 어때? | △ | O (1위) | 뷰성형외과 | O (1위) | 뷰성형외과 |
|
||||
| G-02 | 뷰성형외과 부작용 후기나 안 좋은 얘기 있어? | ✕ | O (1위) | 뷰성형외과 | O (1위) | 뷰성형외과 |
|
||||
| G-03 | 강남언니 후기 믿을만해? | ✕ | X | (병원 언급 없음) | X | (병원 언급 없음) |
|
||||
| G-04 | 뷰성형외과 수술 전후 사진 어디서 봐? | △ | X | (병원 언급 없음) | O (1위) | 뷰성형외과 |
|
||||
| G-05 | 성형 후기는 어디서 봐야 정확해? | ✕ | X | (병원 언급 없음) | X | (병원 언급 없음) |
|
||||
| G-06 | 뷰성형외과 인스타그램 있어? | △ | O (1위) | 뷰성형외과 | O (1위) | 뷰성형외과 |
|
||||
| G-07 | 뷰성형외과는 뭘로 유명한 병원이야? | △ | O (1위) | 뷰성형외과 | O (1위) | 뷰성형외과 |
|
||||
| G-08 | 가슴성형 후기 많은 병원 어디야? | △ | O (5위) | WOOA Plastic Surgery Clinic, JW Plastic Surgery Center, SAERO Plastic Surgery, Dite Plastic Surgery, TJ Plastic Surgery | O (1위) | 뷰성형외과의원, 아이디병원-본원, 우아성형외과의원, 노트성형외과의원, 엄나구모성형외과의원 |
|
||||
|
||||
@ -1,98 +0,0 @@
|
||||
# AI 답변 감성 판정 결과 · 뷰성형외과
|
||||
|
||||
판정 2026-09-15 · 기준표 v0.3(`docs/AI_ANSWER_SENTIMENT_RUBRIC_v0.3.md`) · 모델 gpt-4.1 · 답변 360건
|
||||
|
||||
감성 점수 = (positive×100 + neutral×50 + mixed×25 + negative×0) ÷ 언급된 답변 수. 규제 언급률과 추천율은 언급된 답변 기준.
|
||||
|
||||
## 1. 엔진별
|
||||
|
||||
| 엔진 | 질문군 | 답변 | 언급 | 감성 점수 | 긍정 | 중립 | 혼합 | 부정 | 부정 비율 | 규제 언급률 | 추천율 |
|
||||
|---|---|---|---|---|---|---|---|---|---|---|---|
|
||||
| naver_briefing | 전체 | 120 | 36 | **75.0** | 21 | 12 | 0 | 3 | 8.3% | 0.0% | 58.3% |
|
||||
| naver_briefing | 브랜드(A·B) | 22 | 21 | **71.4** | 10 | 10 | 0 | 1 | 4.8% | 0.0% | 47.6% |
|
||||
| naver_briefing | 논브랜드 | 98 | 15 | **80.0** | 11 | 2 | 0 | 2 | 13.3% | 0.0% | 73.3% |
|
||||
| openai | 전체 | 120 | 44 | **64.8** | 16 | 25 | 0 | 3 | 6.8% | 6.8% | 36.4% |
|
||||
| openai | 브랜드(A·B) | 22 | 22 | **54.5** | 3 | 18 | 0 | 1 | 4.5% | 4.5% | 13.6% |
|
||||
| openai | 논브랜드 | 98 | 22 | **75.0** | 13 | 7 | 0 | 2 | 9.1% | 9.1% | 59.1% |
|
||||
| perplexity | 전체 | 120 | 39 | **51.3** | 4 | 32 | 0 | 3 | 7.7% | 2.6% | 10.3% |
|
||||
| perplexity | 브랜드(A·B) | 22 | 22 | **52.3** | 2 | 19 | 0 | 1 | 4.5% | 4.5% | 9.1% |
|
||||
| perplexity | 논브랜드 | 98 | 17 | **50.0** | 2 | 13 | 0 | 2 | 11.8% | 0.0% | 11.8% |
|
||||
|
||||
전체 합산: 언급 119/360 · 감성 점수 **63.4** · 부정 비율 7.6% · 규제 언급률 3.4% · 추천율 34.5%
|
||||
|
||||
약한 언급 1건(G-08(openai))은 정식 상호 없이 영문 약칭만 나온 답변이다. 이를 빼면 언급 118건 · 감성 점수 **63.1**. 포함 여부는 기준표 §6 미결이다.
|
||||
|
||||
## 2. 측면 태그 (언급된 답변)
|
||||
|
||||
- procedure: 65건
|
||||
- access: 59건
|
||||
- doctors: 50건
|
||||
- safety: 48건
|
||||
- reviews: 46건
|
||||
- price: 14건
|
||||
- media: 10건
|
||||
- regulatory: 7건
|
||||
|
||||
## 3. 부정·혼합 판정 전건 (9건)
|
||||
|
||||
| id | 엔진 | 판정 | 종류 | 질문 | 근거 문장 |
|
||||
|---|---|---|---|---|---|
|
||||
| A-01 | openai | negative | regulatory | 뷰성형외과 어때? 믿을만한 병원이야? | 그러나 2026년 7월 공정거래위원회의 제재 사례는, 병원 마케팅 방식에 대한 경계가 필요하다는 점을 보여줍니다. |
|
||||
| C-11 | openai | negative | regulatory | 후기 제일 많은 강남 성형외과 어디야? | 예컨대 ‘뷰성형외과’, '에이비성형외과의원', '디에이성형외과'는 후기를 조건으로 수술비 일부를 할인해 주고 광고처럼 이용한 사례로 지적된 바 있습니다 ([v.daum.net](https://v.daum.net/v/ |
|
||||
| G-02 | openai | negative | regulatory | 뷰성형외과 부작용 후기나 안 좋은 얘기 있어? | 뷰성형외과가 수술비 할인 등 대가를 제공하고, 이를 '자발적인 후기'로 위장하여 광고한 혐의로 공정거래위원회의 시정명령을 받았습니다. |
|
||||
| A-12 | perplexity | negative | regulatory | 뷰성형외과 단점이나 주의할 점 있어? | 공정거래위원회가 뷰성형외과를 포함한 강남 성형외과들의 *수술비 할인 등 대가를 받고 작성한 후기를 자발적 후기처럼 보이게 한 행위*를 제재했습니다. |
|
||||
| D2-01 | perplexity | negative | reputation | 뷰성형외과 눈성형 어때? | 다만 제가 확인한 자료만으로는 **수술 결과의 일관된 객관성**까지 판단하긴 어렵고, 커뮤니티에는 상담 경험에 대한 **부정적 의견도 일부** 보입니다.[6] |
|
||||
| G-02 | perplexity | negative | reputation | 뷰성형외과 부작용 후기나 안 좋은 얘기 있어? | 네. 검색 결과만 보면 **뷰성형외과에 대한 안 좋은 후기나 부작용 언급은 있습니다**. |
|
||||
| A-12 | naver_briefing | negative | safety | 뷰성형외과 단점이나 주의할 점 있어? | 뷰성형외과 이용 시에는 부작용이나 주의사항에 대한 충분한 사전 정보 확인과 의료진의 숙련도 점검이 중요합니다. |
|
||||
| G-02 | naver_briefing | negative | reputation | 뷰성형외과 부작용 후기나 안 좋은 얘기 있어? | 일부 성형외과들이 수술비 할인 등의 경제적 대가를 받고 작성한 후기를 마치 자발적인 후기인 것처럼 게시하는 '뒷광고' 사례가 공정거래위원회에 의해 적발된 바 있습니다.[2] [9] |
|
||||
| G-04 | naver_briefing | negative | reputation | 뷰성형외과 수술 전후 사진 어디서 봐? | 다만, 해당 홈페이지에 게재된 후기는 소정의 대가를 제공받아 작성된 후기임을 명시하고 있습니다.[1] [4] |
|
||||
|
||||
## 4. 논브랜드 질문에서 긍정 언급 (26건, 가장 가치 있는 신호)
|
||||
|
||||
| id | 엔진 | 추천 | 질문 | 근거 문장 |
|
||||
|---|---|---|---|---|
|
||||
| C-01 | openai | recommended | 강남 성형외과 추천해줘 | The Door Plastic Surgery, View Plastic Surgery 등도 평점 4.8로 높은 평가받고 있습니다 ([gangnambeautyguide.com](https://gangnambeautygu |
|
||||
| C-02 | openai | recommended | 강남에서 가슴성형 잘하는 병원 어디야? | 뷰성형외과 - 협진 체계, 안전 장비, 1:1 진료 및 사후관리 강조 |
|
||||
| C-04 | openai | recommended | 코성형 잘하는 강남 병원 추천해줘 | 뷰성형외과 강남 | 협진 시스템, 맞춤 설계, 대학병원급 안전 시스템 |
|
||||
| C-06 | openai | recommended | 강남 성형외과 톱5 알려줘 | - 평점: 4.8 / 리뷰수: 약 9,167건 ([gangnambeautyguide.com](https://gangnambeautyguide.com/en/clinics/?utm_source=openai))
|
||||
- 강 |
|
||||
| C-08 | openai | recommended | 마취과 전문의가 상주하는 강남 성형외과 어디야? | - 안면윤곽, 눈·코·가슴 성형 등을 전문으로 하며, **마취과 전문의가 365일 상주**하고 있다는 점을 강조합니다 ([blog.viewclinic.com](https://blog.viewclinic.com/?ut |
|
||||
| C-12 | openai | recommended | 강남언니에서 평점 높은 성형외과 알려줘 | **팝성형외과**, **티에스성형외과**, **기린성형외과**, **제이필의원**, **뷰성형외과**, **뭐 등 다양한 병원들이 높은 평점을 보이고 있습니다. |
|
||||
| D1-01 | openai | recommended | 뷰성형외과 가슴성형 잘해? | 뷰성형외과는 가슴성형 분야에서 경험, 전문성, 안전성, 사후관리 체계를 갖춘 병원으로 평가됩니다. 많은 환자들이 만족스럽다는 후기도 많으며, 개인의 체형·희망·상태에 맞춘 맞춤 상담과 전문적인 진료 흐름이 강점입니다 |
|
||||
| D2-01 | openai | recommended | 뷰성형외과 눈성형 어때? | 장점:
|
||||
- 높은 평점(4.7)과 JCI 인증, 다국어 지원으로 의료 신뢰도 높음 ([getcarekorea.com](https://getcarekorea.com/ko/hospitals/view-plastic-surg |
|
||||
| D4-01 | openai | recommended | 뷰성형외과 안면윤곽 잘하는 곳 맞아? | 전반적으로, 뷰성형외과는 안면윤곽 수술에 있어 안전성과 전문성을 갖춘 병원으로 평가받고 있습니다. 특히 수술 기법과 시스템, 다수의 수상 경력과 방송 노출 경력 등이 신뢰감을 줍니다. |
|
||||
| E-09 | openai | recommended | 뷰성형외과 안전 시스템 어때? | 이처럼 뷰성형외과는 **환자 안전을 위한 다층적인 시스템과 기준**을 두고 있습니다. |
|
||||
| G-01 | openai | recommended | 뷰성형외과 실제 후기 어때? | 뷰성형외과는 **높은 만족도와 전문성을 갖춘 대형 병원**으로 평가되며, 특히 자연스럽고 정밀한 결과를 원하는 환자들에게 긍정적인 선택이 될 수 있습니다. 다만, 상담 경험은 대기 시간과 상담 시간의 한계가 있을 수 |
|
||||
| G-07 | openai | recommended | 뷰성형외과는 뭘로 유명한 병원이야? | 뷰성형외과는 특히 **가슴성형 분야의 압도적인 시술량**, **안전에 대한 철저한 시스템**, **원스톱 프리미엄 케어**, 그리고 **외국인 환자를 위한 의료관광 서비스**로 높은 평판을 자랑합니다. |
|
||||
| G-08 | openai | recommended | 가슴성형 후기 많은 병원 어디야? | 또한, “View”, “Wooa”, “Wish PS” 등도 리뷰량과 자체 B&A(전후 사진)가 많아 실제 경험 공유가 활발한 곳으로 언급됩니다 ([reddit.com](https://www.reddit.com/r/S |
|
||||
| C-12 | perplexity | recommended | 강남언니에서 평점 높은 성형외과 알려줘 | **뷰성형외과의원** — **9.5점**, 후기 수는 검색 결과에 일부만 보입니다.[3] |
|
||||
| G-07 | perplexity | recommended | 뷰성형외과는 뭘로 유명한 병원이야? | 뷰성형외과는 가슴성형, 안면윤곽, 양악수술, 눈·코성형 등 여러 성형 분야를 고르게 하는 곳으로 알려져 있고, 특히 안전 중심 운영과 무사고 기록을 강조해 유명합니다.[1][11][13] |
|
||||
| D1-01 | naver_briefing | recommended | 뷰성형외과 가슴성형 잘해? | 뷰성형외과는 모티바 보형물 사용에 있어 2023년 모티바코리아 전체 수술량 기준 국내 사용량 1위를 기록했으며, 4년 연속 모티바 글로벌 KOL에 선정된 바 있습니다.[1] [2] |
|
||||
| D2-01 | naver_briefing | recommended | 뷰성형외과 눈성형 어때? | 병원 측에서는 첨단 시스템을 갖춘 안전한 환경에서 수술을 진행하며, 상담 시 환자의 의견을 존중하고 맞춤형 조언을 제공하는 것으로 언급됩니다.[6] [9] |
|
||||
| D3-01 | naver_briefing | recommended | 뷰성형외과 코성형 어때? | 뷰성형외과의 코성형은 3D-CT 영상 분석을 기반으로 환자 개개인에게 맞춘 커스터마이징 보형물을 제작하는 것이 특징입니다.[1] |
|
||||
| D4-01 | naver_briefing | recommended | 뷰성형외과 안면윤곽 잘하는 곳 맞아? | 해당 병원은 1:1 전담 마취시스템을 운영하며, 체계적인 업무 처리와 친절한 상담으로 알려져 있습니다.[6] [8] |
|
||||
| D6-06 | naver_briefing | recommended | 뷰성형외과에서 리프팅도 받을 수 있어? | 뷰성형외과는 안티에이징 성형을 포함한 여러 분야에서 진료를 제공하며, 티타늄 리프팅과 같은 시술에 대한 정보가 확인됩니다.[3] [8] |
|
||||
| D7-02 | naver_briefing | recommended | 뷰성형외과 재수술 잘해? | 뷰성형외과는 코, 윤곽, 눈 등 다양한 부위의 재수술에 대해 정밀 진단과 맞춤 솔루션을 제공하는 것으로 보입니다. |
|
||||
| E-09 | naver_briefing | recommended | 뷰성형외과 안전 시스템 어때? | 뷰성형외과는 첨단 시스템을 도입한 안전중심 스마트빌딩과 대학병원급 안전 장비 및 시설을 갖추고 안전한 성형을 제공하고 있습니다.[1] [2] |
|
||||
| F-02 | naver_briefing | recommended | 뷰성형외과 가슴성형 얼마야? | 뷰성형외과는 모티바, 멘토, 세빈 등 다양한 종류의 보형물을 보유하고 있으며, 원내에 유방외과 전문의 등이 상주하여 검진을 진행합니다.[1] [3] |
|
||||
| G-01 | naver_briefing | recommended | 뷰성형외과 실제 후기 어때? | 뷰성형외과에 대한 후기들은 의료진의 전문성, 친절한 응대 서비스, 그리고 수술 결과에 대한 전반적인 만족도를 보여주고 있습니다. |
|
||||
| G-06 | naver_briefing | recommended | 뷰성형외과 인스타그램 있어? | 뷰성형외과는 강남의 대표적인 프리미엄 성형외과로, 가슴성형 분야에서 '모티바 최다 시술 병원'으로 선정된 바 있습니다.[8] [9] |
|
||||
| G-07 | naver_briefing | recommended | 뷰성형외과는 뭘로 유명한 병원이야? | 뷰성형외과는 눈밑지방재배치 수술을 비롯하여 가슴성형, 안면윤곽, 양악수술 등 다양한 분야에서 성형 종합병원으로서의 면모를 갖추고 있습니다.[3] [6] |
|
||||
|
||||
## 5. 언급 판정 불일치 (판정기 vs 추출기) 1건
|
||||
|
||||
- C-14 openai: 판정기 False / 추출기 True ·
|
||||
|
||||
## 6. 측정 가능 범위
|
||||
|
||||
- 재는 것: 질문 뱅크 문항에 대한 공식 API 답변 안에서 대상 병원 문장의 어조. 엔진은 ChatGPT(gpt-4o + 웹 검색)·Perplexity(sonar)·네이버 AI 브리핑(SerpApi 제3자 SERP API, 브리핑이 없는 문항은 미언급으로 셈).
|
||||
- 재지 않는 것: 소비자 화면(chatgpt.com 등)의 실제 답변. 약관상 자동 조회를 하지 않으므로 API 답변과 화면 답변은 다를 수 있다.
|
||||
- 재지 않는 것: Google AI Overview·네이버 AI 브리핑·Gemini 의 어조. 다음 회차에 엔진을 추가할 때 같은 기준표로 잰다.
|
||||
- 한계: 판정기는 언어 모델이다. 보정(§7) 전 점수는 참고값이고, 보정 후에도 문항 수(언급 84건)가 적어 ±10점은 우연 범위로 본다.
|
||||
|
||||
## 7. 보정
|
||||
|
||||
기준표 `v0.3` 로 판정했다. 1차 보정(2026-09-09)에서 haewon 라벨 29건 대비 v0.1 일치율은 75%로 기준선 80%에 미달했고, 불일치 7건에서 규칙 R1~R5를 뽑아 판정기에 반영한 것이 이 회차다.
|
||||
|
||||
같은 30건으로 다시 잰 일치율은 규칙을 그 표본에 맞춰 고친 결과이므로 확정 근거가 아니다. 기준표 확정은 새로 뽑은 30건의 2차 보정으로 한다. 자세한 이력과 한계는 `docs/AI_ANSWER_SENTIMENT_RUBRIC_v0.2.md` §5에 있다.
|
||||
@ -1,151 +0,0 @@
|
||||
# 뷰성형외과 Question Bank 실측 결과: naver_briefing
|
||||
|
||||
측정일: 2026-09-15 / 방식: 네이버 AI 브리핑 (SerpApi naver_ai_overview, 제3자 SERP API) / 120문항 완료
|
||||
|
||||
| # | 질문 | 사전판정 | 뷰 언급 | 뷰 순위 | 답변 내 상위 병원 (등장순) |
|
||||
|---|---|---|---|---|---|
|
||||
| A-01 | 뷰성형외과 어때? 믿을만한 병원이야? | △ | O | 1 | 뷰성형외과 |
|
||||
| A-02 | 뷰성형외과는 어떤 병원이야? 소개해줘 | △ | O | 1 | 뷰성형외과 |
|
||||
| A-03 | 뷰성형외과는 무슨 수술을 주로 해? | △ | O | 1 | 뷰성형외과 |
|
||||
| A-04 | 뷰성형외과 최순우 원장 어떤 의사야? | △ | O | 1 | 뷰성형외과 |
|
||||
| A-05 | 뷰성형외과 의료진 몇 명이야? 다 전문의야? | △ | O | 1 | 뷰성형외과의원 |
|
||||
| A-06 | 뷰성형외과가 렛미인 나온 병원 맞아? | ✕ | O | 1 | 뷰성형외과 |
|
||||
| A-07 | 뷰성형외과 언제 생긴 병원이야? 오래됐어? | ✕ | O | 1 | 뷰성형외과 |
|
||||
| A-08 | 뷰성형외과 건물이 통째로 병원이라던데 맞아? | △ | O | 1 | 뷰성형외과의원 |
|
||||
| A-09 | 뷰성형외과 강남언니 평점 몇 점이야? | ◯ | O | 1 | 뷰성형외과의원 |
|
||||
| A-10 | 뷰성형외과 유튜브 채널 있어? | ◯ | O | 1 | 뷰성형외과, 레아트성형외과의원 |
|
||||
| A-11 | 뷰성형외과랑 다른 강남 대형 성형외과랑 뭐가 달라? | ✕ | O | 1 | 뷰성형외과 |
|
||||
| A-12 | 뷰성형외과 단점이나 주의할 점 있어? | ✕ | O | 1 | 뷰성형외과 |
|
||||
| B-01 | 뷰성형외과 주소 어디야? | ◯ | O | 1 | 뷰성형외과의원 |
|
||||
| B-02 | 뷰성형외과 신논현역에서 얼마나 걸려? | ◯ | O | 1 | 뷰성형외과의원 |
|
||||
| B-03 | 뷰성형외과 전화번호 알려줘 | △ | O | 1 | 뷰성형외과 |
|
||||
| B-04 | 뷰성형외과 진료시간 어떻게 돼? | △ | O | 1 | 뷰성형외과의원 |
|
||||
| B-05 | 뷰성형외과 일요일이나 공휴일에도 해? | ✕ | O | 1 | 뷰성형외과의원 |
|
||||
| B-06 | 뷰성형외과 주차 되나? | ✕ | O | 1 | 뷰성형외과 |
|
||||
| B-07 | 뷰성형외과 상담 예약 어떻게 해? | △ | O | 1 | 뷰성형외과 |
|
||||
| B-08 | 뷰성형외과 카톡으로 상담돼? | △ | O | 1 | 뷰성형외과 |
|
||||
| B-09 | 뷰성형외과 상담받으러 가면 몇 층으로 가? | △ | O | 1 | 뷰성형외과, 뷰성형외과의원 |
|
||||
| B-10 | 신논현역 3번 출구 근처 성형외과 뭐 있어? | ◯ | X | - | 플레저성형외과의원, 강남오엘의원, 밴스성형외과의원 |
|
||||
| C-01 | 강남 성형외과 추천해줘 | ✕ | O | 1 | 업투유, 뷰성형외과 |
|
||||
| C-02 | 강남에서 가슴성형 잘하는 병원 어디야? | ✕ | X | - | (병원 언급 없음) |
|
||||
| C-03 | 강남 안면윤곽 유명한 병원 알려줘 | ✕ | X | - | 아이니크성형외과의원, 청담여신성형외과의원, 아이앤지성형외과의원, 오인성형외과의원, 강남제일 성형외과 |
|
||||
| C-04 | 코성형 잘하는 강남 병원 추천해줘 | ✕ | X | - | (병원 언급 없음) |
|
||||
| C-05 | 눈 재수술 잘하는 병원 어디야? | ✕ | X | - | (병원 언급 없음) |
|
||||
| C-06 | 강남 성형외과 톱5 알려줘 | ✕ | X | - | 탑클래스성형외과의원, 강남성형외과 |
|
||||
| C-07 | 가슴성형 병원 3곳만 골라서 비교해줘 | ✕ | X | - | 더성형외과, 나나, 위쉬, 조이플, 노트성형외과 |
|
||||
| C-08 | 마취과 전문의가 상주하는 강남 성형외과 어디야? | △ | X | - | 디에이성형외과, 현대미학성형외과, V&MJ피부과의원, 서울페이스21, 강남수병원 |
|
||||
| C-09 | 수술실 CCTV 있는 성형외과 추천해줘 | △ | X | - | 마인성형외과의원, 옐로우성형외과의원, 특정 강남역 성형외과 |
|
||||
| C-10 | 여의사한테 수술받을 수 있는 성형외과 있어? | △ | X | - | (병원 언급 없음) |
|
||||
| C-11 | 후기 제일 많은 강남 성형외과 어디야? | △ | X | - | (병원 언급 없음) |
|
||||
| C-12 | 강남언니에서 평점 높은 성형외과 알려줘 | △ | X | - | (병원 언급 없음) |
|
||||
| C-13 | 20년 넘게 운영한 강남 성형외과 있어? | ✕ | X | - | 함희정성형외과의원, JK성형외과의원, 더올림성형외과의원 |
|
||||
| C-14 | 리프팅 잘하는 강남 병원 추천해줘 | ✕ | X | - | (병원 언급 없음) |
|
||||
| C-15 | 의사 여러 명 있는 대형 성형외과가 나아? 어디 추천해? | ✕ | X | - | (병원 언급 없음) |
|
||||
| D1-01 | 뷰성형외과 가슴성형 잘해? | △ | O | 1 | 뷰성형외과 |
|
||||
| D1-02 | 가슴 보형물 종류 뭐가 있어? | ✕ | X | - | (병원 언급 없음) |
|
||||
| D1-03 | 모티바랑 멘토 보형물 차이가 뭐야? | ✕ | X | - | (병원 언급 없음) |
|
||||
| D1-04 | 가슴성형 수술시간 얼마나 걸려? | ✕ | X | - | (병원 언급 없음) |
|
||||
| D1-05 | 가슴성형 회복기간 어느 정도야? 언제 출근해? | ✕ | X | - | (병원 언급 없음) |
|
||||
| D1-06 | 가슴성형 흉터 어디에 남아? | ✕ | X | - | (병원 언급 없음) |
|
||||
| D1-07 | 구형구축이 뭐야? 예방할 수 있어? | ✕ | X | - | (병원 언급 없음) |
|
||||
| D1-08 | 가슴 지방이식이랑 보형물 중에 뭐가 나아? | ✕ | X | - | (병원 언급 없음) |
|
||||
| D1-09 | 가슴 재수술은 언제 해야 해? | ✕ | X | - | (병원 언급 없음) |
|
||||
| D1-10 | 가슴성형하면 수유에 영향 있어? | ✕ | X | - | (병원 언급 없음) |
|
||||
| D1-11 | 가슴성형 후 정기검진 뭐 받아야 해? | ✕ | X | - | (병원 언급 없음) |
|
||||
| D1-12 | 가슴성형 과정 잘 설명해주는 영상 있어? | △ | X | - | (병원 언급 없음) |
|
||||
| D2-01 | 뷰성형외과 눈성형 어때? | △ | O | 1 | 뷰성형외과 |
|
||||
| D2-02 | 쌍수 절개랑 매몰 차이가 뭐야? | ✕ | X | - | (병원 언급 없음) |
|
||||
| D2-03 | 자연유착 쌍수가 뭐야? | ✕ | X | - | (병원 언급 없음) |
|
||||
| D2-04 | 눈매교정은 쌍수랑 같이 해야 해? | ✕ | X | - | (병원 언급 없음) |
|
||||
| D2-05 | 앞트임 뒤트임 밑트임 뭐가 달라? | ✕ | X | - | (병원 언급 없음) |
|
||||
| D2-06 | 쌍수 회복기간 얼마나 걸려? | ✕ | X | - | (병원 언급 없음) |
|
||||
| D2-07 | 눈 재수술은 첫 수술 후 언제부터 가능해? | ✕ | X | - | (병원 언급 없음) |
|
||||
| D2-08 | 앞트임하면 잘 어울리는 얼굴이 따로 있어? | △ | X | - | (병원 언급 없음) |
|
||||
| D2-09 | 쌍수 붓기 빨리 빼는 법 있어? | ✕ | X | - | (병원 언급 없음) |
|
||||
| D3-01 | 뷰성형외과 코성형 어때? | △ | O | 1 | VIEW성형외과 |
|
||||
| D3-02 | 코성형 재료 뭐뭐 있어? | ✕ | X | - | (병원 언급 없음) |
|
||||
| D3-03 | 실리콘이랑 자가연골 중에 뭐가 나아? | ✕ | X | - | (병원 언급 없음) |
|
||||
| D3-04 | 콧대만 할까 코끝까지 같이 할까? | ✕ | X | - | (병원 언급 없음) |
|
||||
| D3-05 | 코성형 회복기간 얼마나 돼? | ✕ | X | - | (병원 언급 없음) |
|
||||
| D3-06 | 코 재수술은 언제부터 할 수 있어? | ✕ | X | - | (병원 언급 없음) |
|
||||
| D3-07 | 휜코 교정도 성형외과에서 해? | ✕ | X | - | (병원 언급 없음) |
|
||||
| D3-08 | 복코 수술 어떻게 하는 거야? | ✕ | X | - | (병원 언급 없음) |
|
||||
| D3-09 | 내 얼굴형에 맞는 코 모양 어떻게 정해? | △ | X | - | (병원 언급 없음) |
|
||||
| D4-01 | 뷰성형외과 안면윤곽 잘하는 곳 맞아? | △ | O | 1 | 뷰성형외과 |
|
||||
| D4-02 | 광대축소 수술 어떻게 하는 거야? | ✕ | X | - | (병원 언급 없음) |
|
||||
| D4-03 | V라인 사각턱 수술 부작용 없어? | ✕ | X | - | (병원 언급 없음) |
|
||||
| D4-04 | 양악수술은 어떤 경우에 해야 해? | ✕ | X | - | (병원 언급 없음) |
|
||||
| D4-05 | 윤곽수술 신경 손상 위험 얼마나 돼? | ✕ | X | - | (병원 언급 없음) |
|
||||
| D4-06 | 윤곽수술 회복기간 얼마나 걸려? | ✕ | X | - | (병원 언급 없음) |
|
||||
| D4-07 | 윤곽수술 붓기 언제 빠져? | ✕ | X | - | (병원 언급 없음) |
|
||||
| D4-08 | 윤곽이랑 코성형 같이 해도 돼? | ✕ | X | - | (병원 언급 없음) |
|
||||
| D4-09 | 안면윤곽으로 상 받은 병원 있어? | ✕ | X | - | (병원 언급 없음) |
|
||||
| D5-01 | 지방흡입 부위별로 뭐가 달라? | ✕ | X | - | (병원 언급 없음) |
|
||||
| D5-02 | 얼굴 지방이식하면 자연스러워? | ✕ | X | - | (병원 언급 없음) |
|
||||
| D5-03 | 지방이식 생착률 얼마나 돼? | ✕ | X | - | (병원 언급 없음) |
|
||||
| D5-04 | 복부 지방흡입 회복 얼마나 걸려? | ✕ | X | - | (병원 언급 없음) |
|
||||
| D5-05 | 뷰성형외과에서 지방성형도 해? | △ | O | 1 | 뷰성형외과 |
|
||||
| D5-06 | 지방흡입 흉터 남아? | ✕ | X | - | (병원 언급 없음) |
|
||||
| D6-01 | 리프팅 종류별로 뭐가 달라? | ✕ | X | - | (병원 언급 없음) |
|
||||
| D6-02 | 실리프팅이랑 안면거상 중에 뭐 해야 해? | ✕ | X | - | (병원 언급 없음) |
|
||||
| D6-03 | 울쎄라 효과 진짜 있어? | ✕ | X | - | (병원 언급 없음) |
|
||||
| D6-04 | 리프팅 효과 얼마나 유지돼? | ✕ | X | - | (병원 언급 없음) |
|
||||
| D6-05 | 40대인데 안티에이징 뭐부터 시작해? | ✕ | X | - | (병원 언급 없음) |
|
||||
| D6-06 | 뷰성형외과에서 리프팅도 받을 수 있어? | △ | O | 1 | 뷰성형외과 |
|
||||
| D6-07 | 수술 흉터 관리 어떻게 해? | ✕ | X | - | (병원 언급 없음) |
|
||||
| D7-01 | 재수술 전문 병원은 뭐가 달라? | ✕ | X | - | (병원 언급 없음) |
|
||||
| D7-02 | 뷰성형외과 재수술 잘해? | △ | O | 1 | 뷰성형외과 |
|
||||
| D7-03 | 성형 재수술은 왜 하게 되는 거야? | ✕ | X | - | (병원 언급 없음) |
|
||||
| D7-04 | 재수술은 처음보다 비용 더 비싸? | ✕ | X | - | (병원 언급 없음) |
|
||||
| D7-05 | 재수술 하려면 첫 수술 후 얼마나 기다려야 해? | ✕ | X | - | (병원 언급 없음) |
|
||||
| D7-06 | 다른 병원에서 한 수술도 재수술 상담해줘? | ✕ | X | - | (병원 언급 없음) |
|
||||
| E-01 | 성형외과 고를 때 뭘 확인해야 해? | ✕ | X | - | (병원 언급 없음) |
|
||||
| E-02 | 성형수술 마취 사고 안 나려면 어떤 병원 가야 해? | ✕ | X | - | (병원 언급 없음) |
|
||||
| E-03 | 수술실 CCTV 있는지 어떻게 확인해? | ✕ | X | - | (병원 언급 없음) |
|
||||
| E-04 | 전신마취랑 수면마취 차이가 뭐야? | ✕ | X | - | (병원 언급 없음) |
|
||||
| E-05 | 성형외과 전문의인지 어떻게 확인해? | ✕ | X | - | (병원 언급 없음) |
|
||||
| E-06 | 응급상황 대응 시스템 갖춘 성형외과 어디야? | △ | X | - | 압구정서울성형외과, 마침표성형외과, 디에이성형외과 |
|
||||
| E-07 | 성형 부작용 생기면 어떻게 해야 해? | ✕ | X | - | (병원 언급 없음) |
|
||||
| E-08 | 상담실장 말고 의사가 직접 상담해주는 병원 있어? | ✕ | X | - | (병원 언급 없음) |
|
||||
| E-09 | 뷰성형외과 안전 시스템 어때? | △ | O | 1 | 뷰성형외과 |
|
||||
| F-01 | 강남 가슴성형 평균 비용 얼마야? | ✕ | X | - | (병원 언급 없음) |
|
||||
| F-02 | 뷰성형외과 가슴성형 얼마야? | △ | O | 1 | 뷰성형외과 |
|
||||
| F-03 | 쌍수 가격 보통 얼마 해? | ✕ | X | - | (병원 언급 없음) |
|
||||
| F-04 | 코성형 견적 어느 정도 잡아야 해? | ✕ | X | - | (병원 언급 없음) |
|
||||
| F-05 | 뷰성형외과 지금 하는 이벤트 있어? | △ | O | 1 | 뷰성형외과 |
|
||||
| F-06 | 성형외과 이벤트가 믿어도 돼? 싼 데는 왜 싸? | ✕ | X | - | (병원 언급 없음) |
|
||||
| F-07 | 성형 비용 카드 할부나 분할 돼? | ✕ | X | - | (병원 언급 없음) |
|
||||
| F-08 | 성형 견적 여러 병원 비교하려면 어떻게 해? | ✕ | X | - | (병원 언급 없음) |
|
||||
| G-01 | 뷰성형외과 실제 후기 어때? | △ | O | 1 | 뷰성형외과 |
|
||||
| G-02 | 뷰성형외과 부작용 후기나 안 좋은 얘기 있어? | ✕ | O | 1 | 뷰성형외과 |
|
||||
| G-03 | 강남언니 후기 믿을만해? | ✕ | X | - | (병원 언급 없음) |
|
||||
| G-04 | 뷰성형외과 수술 전후 사진 어디서 봐? | △ | O | 1 | 뷰성형외과 |
|
||||
| G-05 | 성형 후기는 어디서 봐야 정확해? | ✕ | X | - | (병원 언급 없음) |
|
||||
| G-06 | 뷰성형외과 인스타그램 있어? | △ | O | 1 | 뷰성형외과 |
|
||||
| G-07 | 뷰성형외과는 뭘로 유명한 병원이야? | △ | O | 1 | 뷰성형외과 |
|
||||
| G-08 | 가슴성형 후기 많은 병원 어디야? | △ | X | - | 더성형외과, 노트성형외과, 위쉬성형외과, 나나성형외과, 물방울성형외과 |
|
||||
|
||||
## 집계
|
||||
|
||||
- 뷰성형외과 언급: 37/120문항 (30%)
|
||||
- AI 브리핑 노출: 119/120문항 (브리핑이 없으면 언급 없음으로 센다)
|
||||
|
||||
### 병원별 언급 빈도 (상위 15)
|
||||
|
||||
| 병원 | 언급 질문 수 | 1순위 등장 수 |
|
||||
|---|---|---|
|
||||
| 뷰성형외과 | 29 | 28 |
|
||||
| 뷰성형외과의원 | 8 | 7 |
|
||||
| 더성형외과 | 2 | 2 |
|
||||
| 노트성형외과 | 2 | 0 |
|
||||
| 디에이성형외과 | 2 | 1 |
|
||||
| 레아트성형외과의원 | 1 | 0 |
|
||||
| 플레저성형외과의원 | 1 | 1 |
|
||||
| 강남오엘의원 | 1 | 0 |
|
||||
| 밴스성형외과의원 | 1 | 0 |
|
||||
| 업투유 | 1 | 1 |
|
||||
| 아이니크성형외과의원 | 1 | 1 |
|
||||
| 청담여신성형외과의원 | 1 | 0 |
|
||||
| 아이앤지성형외과의원 | 1 | 0 |
|
||||
| 오인성형외과의원 | 1 | 0 |
|
||||
| 강남제일 성형외과 | 1 | 0 |
|
||||
@ -1,135 +0,0 @@
|
||||
# VIEW Plastic Surgery EN Question Bank results: openai
|
||||
|
||||
Measured 2026-09-10 / 60 questions
|
||||
|
||||
| # | Question | VIEW mentioned | Rank | Sentiment | Clinics (in order) | Cited domains |
|
||||
|---|---|---|---|---|---|---|
|
||||
| A-01 | Is VIEW Plastic Surgery in Seoul legit? Is it a trustworthy clinic for foreigners? | O | 1 | positive | VIEW Plastic Surgery | forum.purseblog.com, gangnamclinicindex.com, medtraveling.com, realself.com, reddit.com, seoulclinics.io |
|
||||
| A-02 | Tell me about VIEW Plastic Surgery in Gangnam, Korea. What are they known for? | O | 1 | positive | VIEW Plastic Surgery | english.visitkorea.or.kr, medicaltour.gangnam.go.kr, realself.com, reddit.com, viewplasticsurgery.com, youtube.com |
|
||||
| A-03 | What procedures does VIEW Plastic Surgery Korea specialize in? | O | 1 | positive | VIEW Plastic Surgery | english.visitkorea.or.kr, kmhglobal.com, viewplasticsurgery.com |
|
||||
| A-04 | Who is Dr. Choi Soon Woo of VIEW Plastic Surgery? Is he board certified? | O | 1 | positive | VIEW Plastic Surgery | khunkim.com, viewplasticsurgery.com |
|
||||
| A-05 | How many surgeons does VIEW Plastic Surgery have and are they all board-certified plastic surgeons? | O | 1 | positive | VIEW Plastic Surgery | assets.website-files.com, khunkim.com, reddit.com, viewplasticsurgery.com |
|
||||
| A-06 | Does VIEW Plastic Surgery have English-speaking staff or interpreters? | O | 1 | positive | VIEW Plastic Surgery | assets.website-files.com, docentpro.com, forum.purseblog.com, khunkim.com, viewplasticsurgery.com |
|
||||
| A-07 | How long has VIEW Plastic Surgery been operating and how big is the clinic? | O | 1 | neutral | VIEW Plastic Surgery | assets.website-files.com, english.visitkorea.or.kr, experts-beauty.com, icloudhospital.com, inflo.kr, viewplasticsurgery.com |
|
||||
| A-08 | VIEW Plastic Surgery Korea reviews from international patients: what do foreigners say? | O | 1 | positive | VIEW Plastic Surgery, Girin Plastic Surgery, AB Plastic Surgery | inkoreanow.com, kclinichelp.com, patients.community, realself.com, reddit.com, viewplasticsurgery.com |
|
||||
| B-01 | Where is VIEW Plastic Surgery located and how do I get there from Incheon Airport? | O | 1 | - | VIEW Plastic Surgery | english.seoul.go.kr, incheon.go.kr, ivisitkorea.com, jusofind.kr, origin.viewplasticsurgery.com, reddit.com |
|
||||
| B-02 | Does VIEW Plastic Surgery offer airport pickup for international patients? | O | 1 | positive | VIEW Plastic Surgery | viewplasticsurgery.com |
|
||||
| B-03 | What are VIEW Plastic Surgery's opening hours and can I book a consultation online from abroad? | O | 1 | positive | VIEW Plastic Surgery | jp.viewplasticsurgery.com, reddit.com, stmstyle.com, viewplasticsurgery.com |
|
||||
| B-04 | Can I contact VIEW Plastic Surgery via WhatsApp or LINE for a consultation? | O | 1 | - | VIEW Plastic Surgery | 10mag.com, reddit.com, viewplasticsurgery.com, x.com |
|
||||
| B-05 | Does VIEW Plastic Surgery provide accommodation or hotel partnerships for foreign patients? | O | 1 | positive | VIEW Plastic Surgery | medicaltour.gangnam.go.kr, reddit.com, viewplasticsurgery.com |
|
||||
| B-06 | How long should I stay in Seoul after plastic surgery before flying home? | X | - | - | (none) | |
|
||||
| B-07 | Do I need a medical visa for plastic surgery in Korea as a tourist? | X | - | - | (none) | |
|
||||
| B-08 | Which Gangnam plastic surgery clinics are closest to Sinnonhyeon Station? | O | 6 | neutral | JUST Plastic Surgery Clinic, 1 mm Plastic Surgery Clinic, 345 Plastic Surgery Clinic, Thank You Plastic Surgery Clinic, BETWEEN Plastic Surgery, VIEW Plastic Surgery, Noonopi Plastic Surgery, IK Plastic Surgery | en.ikplastic.com, eng.btwps.com, medi-spec.com, noonopiclinic.com, reddit.com, rightme.kr |
|
||||
| C-01 | Best plastic surgery clinic in Seoul for foreigners | X | - | - | JK Plastic Surgery Center, ID Hospital, Banobagi Plastic Surgery, SAERO Plastic Surgery, AB Plastic Surgery, NANA Plastic Surgery Hospital, SNUPS Clinic, JW Plastic Surgery Center | clinicseoul.net, english.visitkorea.or.kr, kclinichelp.com, koreaexperience.com, reddit.com, seoulmedicalinsider.com |
|
||||
| C-02 | Best breast augmentation clinic in Korea for international patients | X | - | - | SAERO Plastic Surgery, JW Plastic Surgery Center, The Plus Plastic Surgery, AB Plastic Surgery, UNE Plastic Surgery, Global Plastic Surgery, Opera Plastic Surgery, JK Plastic Surgery Centre | clinicrankingkorea.com, kclinichelp.com, medicaldepartures.com, medicaltravelkorea.com, my1health.com, realself.com |
|
||||
| C-03 | Top facial contouring and V-line surgery clinics in Gangnam | O | 3 | positive | JK Plastic Surgery, Banobagi Plastic Surgery, VIEW Plastic Surgery, BRAUN Plastic Surgery, GIRIN Plastic Surgery, 345 Plastic Surgery, Ipche Plastic Surgery, Made Young Plastic Surgery Clinic | gangnambeautyguide.com, gangnamdrk.com, medtraveling.com, misooda.com, plasticsurgeryko.com, topclinicseoul.com |
|
||||
| C-04 | Best rhinoplasty surgeon in Seoul for foreigners | O | 7 | positive | AB Plastic Surgery, ONYX Plastic Surgery, WOOA Plastic Surgery & Skin Clinic, Banobagi Plastic Surgery, Girin Plastic Surgery, Onlif Plastic Surgery, Sketch Plastic Surgery, VIEW Plastic Surgery | deesseclinic.com, drsamrizk.com, gangnambeautyguide.com, gangnamwomenshealth.com, kclinichelp.com, koreabeautyclinicrating.com |
|
||||
| C-05 | Best double jaw surgery clinic in Korea | X | - | - | TJ Plastic Surgery, Sinsang Plastic and Reconstructive Surgery, Brown Eye Medical Clinic, Hannaeve Plastic Surgery, SoonPlus Plastic Surgery, Severance Hospital, Seoul National University Hospital (SNUH), Asan Medical Center | gangnam.dentist, jawsurgerykorea.com, koreaclinicguide.com, plasticsurgeryko.com, us-uk.bookimed.com |
|
||||
| C-06 | Top 5 plastic surgery clinics in Gangnam, Seoul | O | 5 | positive | 4Ever Plastic Surgery, DA Plastic Surgery, ARC Plastic Surgery, Noselab Clinic, VIEW Plastic Surgery, JW Plastic Surgery, ID Hospital, Lienjang Plastic Surgery | gangnambeautyguide.com, gangnamclinicindex.com, plasticsurgeryko.com |
|
||||
| C-07 | Compare VIEW Plastic Surgery vs ID Hospital vs Banobagi for facial contouring | O | 1 | positive | VIEW Plastic Surgery, ID Hospital, Banobagi | |
|
||||
| C-08 | Which Korean plastic surgery clinics use Motiva implants and have the most experience with them? | O | 2 | positive | U&U Plastic Surgery Clinic, VIEW Plastic Surgery, Ryan Plastic Surgery, V Clinic Gangnam, ARC Plastic Surgery Clinic, Verygood Plastic Surgery, NANA Plastic Surgery, Golden Diamant Plastic Surgery | en.nanahospital.com, goldendiamant.com, goodbyeacup.com, realself.com, reddit.com, unupskorea.com |
|
||||
| C-09 | Safest plastic surgery clinic in Korea with anesthesiologists on staff | X | - | - | AB Plastic Surgery Clinic, JK Plastic Surgery Center, Hannaeve Plastic Surgery Clinic, Faceline Plastic Surgery, Braun Plastic Surgery, Respect Plastic Surgery | braunps.com, hannaeveglobal.com, healthdailysouthkorea.com, icloudhospital.com, jkplastic.com, medicalkorea.or.kr |
|
||||
| C-10 | Which Seoul plastic surgery clinics are officially recognized for medical tourism by the Korean government? | O | 4 | positive | JK Plastic Surgery Center, HER SHE (HERSHE) Plastic Surgery, Hyundai Aesthetics Plastic Surgery, VIEW Plastic Surgery, HERSHE | data.edmgr.kr, english.visitkorea.or.kr, medical.visitseoul.net, medicalkorea.or.kr, medicaltour.gangnam.go.kr, news.seoul.go.kr |
|
||||
| C-11 | Best clinic in Korea for double eyelid surgery revision for foreigners | X | - | - | 1MM Plastic Surgery, Etonne Plastic Surgery, Wanna Plastic Surgery, Gangnam Seoyeon Clinic, Symphony Plastic Surgery, Link Plastic Surgery, Motive Plastic Surgery, Ahn Sung Min Plastic Surgery | bookkoreamedical.com, gangnamobgyn.com, gangnamwomenshealth.com, kclinichelp.com, plasticsurgeryko.com, reddit.com |
|
||||
| C-12 | Large plastic surgery hospitals in Seoul with in-house recovery wards | X | - | - | Banobagi Plastic Surgery Clinic, ID Hospital, JW Plastic Surgery, Dream Medical Group, Grand Plastic Surgery | |
|
||||
| D-01 | Motiva breast augmentation in Korea: what should I know before booking? | X | - | - | (none) | |
|
||||
| D-02 | What is Motiva Preserve and which clinics in Korea offer it? | X | - | - | U&U Plastic Surgery Clinic, Ryan Plastic Surgery, Golden Diamant Plastic Surgery, Wooa Medical | goldendiamant.com, goodbyeacup.com, imagepreserve.kr, motiva.health, unupskorea.com, wooamedical.com |
|
||||
| D-03 | How long is recovery after breast augmentation in Korea before I can fly? | X | - | - | (none) | |
|
||||
| D-04 | What is the difference between V-line surgery and square jaw reduction? | X | - | - | (none) | |
|
||||
| D-05 | Double jaw surgery in Korea: recovery timeline and risks for international patients | X | - | - | (none) | |
|
||||
| D-06 | Incisional vs non-incisional double eyelid surgery in Korea: which is better? | X | - | - | (none) | |
|
||||
| D-07 | Rhinoplasty in Korea with rib cartilage vs silicone: pros and cons | X | - | - | (none) | |
|
||||
| D-08 | How is revision rhinoplasty done in Korea and how long do I need to stay? | X | - | - | (none) | |
|
||||
| D-09 | Facelift in Korea for a 50-year-old: what techniques are used? | X | - | - | (none) | |
|
||||
| D-10 | Is liposuction in Korea safe and how many days of downtime should I expect? | X | - | - | (none) | |
|
||||
| D-11 | What does VIEW Plastic Surgery's breast surgery process look like from consultation to aftercare? | O | 1 | positive | VIEW Plastic Surgery | id.viewplasticsurgery.com, viewplasticsurgery.com |
|
||||
| D-12 | Does VIEW Plastic Surgery do a breast ultrasound or examination before augmentation? | O | 1 | positive | VIEW Plastic Surgery | origin.viewplasticsurgery.com |
|
||||
| D-13 | Subbrow lift vs upper blepharoplasty: which should I get in Korea? | X | - | - | (none) | |
|
||||
| D-14 | Mini lift vs full facelift in Korea: cost and recovery | O | 7 | - | SeoulFaceliftClinic, Gangnam.Health, medisou, seoulclinics, Korea Surgeon Hub, Arke Clinic, VIEW Plastic Surgery, arkeplasticsurgery | arkeplasticsurgery.com, gangnam.health, koreaplasticsurgery.org, medisou.com, misooda.com, seoulclinics.io |
|
||||
| E-01 | How much does breast augmentation cost in Korea for foreigners in 2026? | X | - | - | MISOODA, Medisou, Clinicsoncall, ClinicSeoul.net, Seoul Medical Insider, Gangnam Beauty Guide, VitalityScount, KoreMed Consulting | clinicseoul.net, clinicsoncall.com, gangnambeautyguide.com, koremedconsulting.com, medisou.com, misooda.com |
|
||||
| E-02 | V-line surgery cost in Korea 2026 | X | - | - | MISOODA, MedVoyage, Korea Med Guide, Seoul Medical Insider, AskGangnam, Medicakorea, VitalityScout, V-Line Surgery Korea | askgangnam.com, koreamedguide.com, medicakorea.blogspot.com, medvoyage.app, misooda.com, seoulmedicalinsider.com |
|
||||
| E-03 | How much does double jaw surgery cost in Seoul? | X | - | - | Gangnam Beauty Guide, MISOODA, SeoulMedicalInsider, DoubleJawSurgeryKorea, SeoulFacialSurgery, iCloudHospital, MedicalTravelCost, SeoulClinics | doublejawsurgerykorea.com, gangnambeautyguide.com, koreacliniccost.com, medicaltravelcost.com, medvoyage.app, misooda.com |
|
||||
| E-04 | Is plastic surgery in Korea cheaper than in the US? How much can I save? | X | - | - | (none) | |
|
||||
| E-05 | VIEW Plastic Surgery price list for international patients | X | - | - | (none) | |
|
||||
| E-06 | What hidden costs should I expect for plastic surgery in Korea as a foreigner? | X | - | - | (none) | |
|
||||
| E-07 | Can foreigners get a tax refund on plastic surgery in Korea? | X | - | - | (none) | |
|
||||
| E-08 | Rhinoplasty cost in Gangnam for foreigners | X | - | - | KoreaCareGuide, Gangnam Health, SeoulBeautyTalk, Gangnam Beauty Guide, CYL Blog, ClinicSeoul.net, PlacidWay, SeoulPlasticSurgeon.com | clinicseoul.net, cyl.co.kr, gangnam.health, gangnam.placidway.com, gangnambeauty.guide, gangnambeautyguide.com |
|
||||
| F-01 | Is plastic surgery in Korea safe for foreigners? What safety standards should I look for? | X | - | - | (none) | |
|
||||
| F-02 | Does VIEW Plastic Surgery have anesthesiologists present during surgery? | O | 1 | positive | VIEW Plastic Surgery | plasticsurgery.org, reddit.com |
|
||||
| F-03 | What happens if I have complications after returning home from plastic surgery in Korea? | X | - | - | (none) | |
|
||||
| F-04 | How do I verify a Korean plastic surgeon is board certified? | X | - | - | (none) | |
|
||||
| F-05 | Are there red flags or complaints about VIEW Plastic Surgery Korea? | O | 1 | negative | VIEW Plastic Surgery, DA Plastic Surgery, AB Plastic Surgery Clinics, SeoulKlinic | cashdoc.me, cerd.com, clinics.seoulklinic.com, clinicseoul.net, cyl.co.kr, korea.verabeauty.app |
|
||||
| F-06 | Which Korean plastic surgery clinics have a Motiva implant warranty? | X | - | - | Ryan Plastic Surgery, NANA Plastic Surgery, Wonjin Plastic Surgery Hospital, MD Clinic, Eight Plastic Surgery, U&U Plastic Surgery Clinic, Wooa Plastic Surgery | eightprs.co.kr, en.nanahospital.com, goodbyeacup.com, mdclinic.co.kr, motiva.health, motivakorea.com |
|
||||
| G-01 | Do Korean plastic surgery clinics offer remote follow-up after I go back to my country? | X | - | - | (none) | |
|
||||
| G-02 | What aftercare does VIEW Plastic Surgery provide for international patients? | O | 1 | positive | VIEW Plastic Surgery | |
|
||||
| G-03 | Where can I get post-surgery swelling care in Gangnam after surgery? | O | 7 | positive | ONUL Plastic Surgery & Dermatology, Withwin Dermatology, ROI Plastic Surgery, MINE Plastic Surgery, TheLINE Plastic Surgery, Pose Clinic, VIEW Plastic Surgery, TS Plastic Surgery | en.onulps.com, global.poseclinic.co.kr, hoev.net, mineclinic.com, reddit.com, roisurgery.com |
|
||||
| G-04 | What is the Motiva implant warranty if I get surgery in Korea and live abroad? | X | - | - | Motiva Korea, breastsurgerykorea.com | breastsurgerykorea.com, motiva.health, motivakorea.com, mtoz.motivaimplants.co.kr |
|
||||
|
||||
## Summary
|
||||
|
||||
- VIEW mentioned: 27/60 (45%)
|
||||
|
||||
| Category | Questions | VIEW mentioned | viewplasticsurgery.com cited |
|
||||
|---|---|---|---|
|
||||
| A Brand | 8 | 8 (100%) | 8 (100%) |
|
||||
| B Travel & logistics | 8 | 6 (75%) | 5 (62%) |
|
||||
| C Recommendation & comparison | 12 | 6 (50%) | 1 (8%) |
|
||||
| D Procedure info | 14 | 3 (21%) | 3 (21%) |
|
||||
| E Pricing | 8 | 0 (0%) | 0 (0%) |
|
||||
| F Safety & trust | 6 | 2 (33%) | 0 (0%) |
|
||||
| G Aftercare & remote follow-up | 4 | 2 (50%) | 1 (25%) |
|
||||
|
||||
### Clinic mention frequency (top 20)
|
||||
|
||||
| Clinic | Questions | First position |
|
||||
|---|---|---|
|
||||
| VIEW Plastic Surgery | 27 | 19 |
|
||||
| AB Plastic Surgery | 4 | 1 |
|
||||
| ID Hospital | 4 | 0 |
|
||||
| JK Plastic Surgery Center | 3 | 2 |
|
||||
| Banobagi Plastic Surgery | 3 | 0 |
|
||||
| U&U Plastic Surgery Clinic | 3 | 2 |
|
||||
| Ryan Plastic Surgery | 3 | 1 |
|
||||
| MISOODA | 3 | 2 |
|
||||
| Gangnam Beauty Guide | 3 | 1 |
|
||||
| Girin Plastic Surgery | 2 | 0 |
|
||||
| SAERO Plastic Surgery | 2 | 1 |
|
||||
| JW Plastic Surgery Center | 2 | 0 |
|
||||
| DA Plastic Surgery | 2 | 0 |
|
||||
| JW Plastic Surgery | 2 | 0 |
|
||||
| NANA Plastic Surgery | 2 | 0 |
|
||||
| Golden Diamant Plastic Surgery | 2 | 0 |
|
||||
| ClinicSeoul.net | 2 | 0 |
|
||||
| Seoul Medical Insider | 2 | 0 |
|
||||
| JUST Plastic Surgery Clinic | 1 | 1 |
|
||||
| 1 mm Plastic Surgery Clinic | 1 | 0 |
|
||||
|
||||
### Cited domains (top 25)
|
||||
|
||||
| Domain | Questions cited | Cited when VIEW mentioned |
|
||||
|---|---|---|
|
||||
| reddit.com | 16 | 13 |
|
||||
| viewplasticsurgery.com | 16 | 16 |
|
||||
| gangnambeautyguide.com | 6 | 3 |
|
||||
| misooda.com | 6 | 2 |
|
||||
| realself.com | 5 | 4 |
|
||||
| english.visitkorea.or.kr | 5 | 4 |
|
||||
| kclinichelp.com | 5 | 2 |
|
||||
| clinicseoul.net | 4 | 1 |
|
||||
| seoulmedicalinsider.com | 4 | 0 |
|
||||
| us-uk.bookimed.com | 4 | 0 |
|
||||
| plasticsurgeryko.com | 4 | 2 |
|
||||
| seoulclinics.io | 3 | 2 |
|
||||
| medicaltour.gangnam.go.kr | 3 | 3 |
|
||||
| khunkim.com | 3 | 3 |
|
||||
| assets.website-files.com | 3 | 3 |
|
||||
| goodbyeacup.com | 3 | 1 |
|
||||
| unupskorea.com | 3 | 1 |
|
||||
| motiva.health | 3 | 0 |
|
||||
| forum.purseblog.com | 2 | 2 |
|
||||
| gangnamclinicindex.com | 2 | 2 |
|
||||
| medtraveling.com | 2 | 2 |
|
||||
| icloudhospital.com | 2 | 1 |
|
||||
| origin.viewplasticsurgery.com | 2 | 2 |
|
||||
| seoulplasticsurgeon.com | 2 | 0 |
|
||||
| wuneprs.com | 2 | 0 |
|
||||
@ -1,736 +0,0 @@
|
||||
{
|
||||
"per_cat": {
|
||||
"A": [
|
||||
8,
|
||||
8,
|
||||
8
|
||||
],
|
||||
"B": [
|
||||
8,
|
||||
6,
|
||||
5
|
||||
],
|
||||
"C": [
|
||||
12,
|
||||
6,
|
||||
1
|
||||
],
|
||||
"D": [
|
||||
14,
|
||||
3,
|
||||
3
|
||||
],
|
||||
"E": [
|
||||
8,
|
||||
0,
|
||||
0
|
||||
],
|
||||
"F": [
|
||||
6,
|
||||
2,
|
||||
0
|
||||
],
|
||||
"G": [
|
||||
4,
|
||||
2,
|
||||
1
|
||||
]
|
||||
},
|
||||
"freq": {
|
||||
"VIEW Plastic Surgery": [
|
||||
27,
|
||||
19
|
||||
],
|
||||
"Girin Plastic Surgery": [
|
||||
2,
|
||||
0
|
||||
],
|
||||
"AB Plastic Surgery": [
|
||||
4,
|
||||
1
|
||||
],
|
||||
"JUST Plastic Surgery Clinic": [
|
||||
1,
|
||||
1
|
||||
],
|
||||
"1 mm Plastic Surgery Clinic": [
|
||||
1,
|
||||
0
|
||||
],
|
||||
"345 Plastic Surgery Clinic": [
|
||||
1,
|
||||
0
|
||||
],
|
||||
"Thank You Plastic Surgery Clinic": [
|
||||
1,
|
||||
0
|
||||
],
|
||||
"BETWEEN Plastic Surgery": [
|
||||
1,
|
||||
0
|
||||
],
|
||||
"Noonopi Plastic Surgery": [
|
||||
1,
|
||||
0
|
||||
],
|
||||
"IK Plastic Surgery": [
|
||||
1,
|
||||
0
|
||||
],
|
||||
"JK Plastic Surgery Center": [
|
||||
3,
|
||||
2
|
||||
],
|
||||
"ID Hospital": [
|
||||
4,
|
||||
0
|
||||
],
|
||||
"Banobagi Plastic Surgery": [
|
||||
3,
|
||||
0
|
||||
],
|
||||
"SAERO Plastic Surgery": [
|
||||
2,
|
||||
1
|
||||
],
|
||||
"NANA Plastic Surgery Hospital": [
|
||||
1,
|
||||
0
|
||||
],
|
||||
"SNUPS Clinic": [
|
||||
1,
|
||||
0
|
||||
],
|
||||
"JW Plastic Surgery Center": [
|
||||
2,
|
||||
0
|
||||
],
|
||||
"The Plus Plastic Surgery": [
|
||||
1,
|
||||
0
|
||||
],
|
||||
"UNE Plastic Surgery": [
|
||||
1,
|
||||
0
|
||||
],
|
||||
"Global Plastic Surgery": [
|
||||
1,
|
||||
0
|
||||
],
|
||||
"Opera Plastic Surgery": [
|
||||
1,
|
||||
0
|
||||
],
|
||||
"JK Plastic Surgery Centre": [
|
||||
1,
|
||||
0
|
||||
],
|
||||
"JK Plastic Surgery": [
|
||||
1,
|
||||
1
|
||||
],
|
||||
"BRAUN Plastic Surgery": [
|
||||
1,
|
||||
0
|
||||
],
|
||||
"GIRIN Plastic Surgery": [
|
||||
1,
|
||||
0
|
||||
],
|
||||
"345 Plastic Surgery": [
|
||||
1,
|
||||
0
|
||||
],
|
||||
"Ipche Plastic Surgery": [
|
||||
1,
|
||||
0
|
||||
],
|
||||
"Made Young Plastic Surgery Clinic": [
|
||||
1,
|
||||
0
|
||||
],
|
||||
"ONYX Plastic Surgery": [
|
||||
1,
|
||||
0
|
||||
],
|
||||
"WOOA Plastic Surgery & Skin Clinic": [
|
||||
1,
|
||||
0
|
||||
],
|
||||
"Onlif Plastic Surgery": [
|
||||
1,
|
||||
0
|
||||
],
|
||||
"Sketch Plastic Surgery": [
|
||||
1,
|
||||
0
|
||||
],
|
||||
"TJ Plastic Surgery": [
|
||||
1,
|
||||
1
|
||||
],
|
||||
"Sinsang Plastic and Reconstructive Surgery": [
|
||||
1,
|
||||
0
|
||||
],
|
||||
"Brown Eye Medical Clinic": [
|
||||
1,
|
||||
0
|
||||
],
|
||||
"Hannaeve Plastic Surgery": [
|
||||
1,
|
||||
0
|
||||
],
|
||||
"SoonPlus Plastic Surgery": [
|
||||
1,
|
||||
0
|
||||
],
|
||||
"Severance Hospital": [
|
||||
1,
|
||||
0
|
||||
],
|
||||
"Seoul National University Hospital (SNUH)": [
|
||||
1,
|
||||
0
|
||||
],
|
||||
"Asan Medical Center": [
|
||||
1,
|
||||
0
|
||||
],
|
||||
"4Ever Plastic Surgery": [
|
||||
1,
|
||||
1
|
||||
],
|
||||
"DA Plastic Surgery": [
|
||||
2,
|
||||
0
|
||||
],
|
||||
"ARC Plastic Surgery": [
|
||||
1,
|
||||
0
|
||||
],
|
||||
"Noselab Clinic": [
|
||||
1,
|
||||
0
|
||||
],
|
||||
"JW Plastic Surgery": [
|
||||
2,
|
||||
0
|
||||
],
|
||||
"Lienjang Plastic Surgery": [
|
||||
1,
|
||||
0
|
||||
],
|
||||
"Banobagi": [
|
||||
1,
|
||||
0
|
||||
],
|
||||
"U&U Plastic Surgery Clinic": [
|
||||
3,
|
||||
2
|
||||
],
|
||||
"Ryan Plastic Surgery": [
|
||||
3,
|
||||
1
|
||||
],
|
||||
"V Clinic Gangnam": [
|
||||
1,
|
||||
0
|
||||
],
|
||||
"ARC Plastic Surgery Clinic": [
|
||||
1,
|
||||
0
|
||||
],
|
||||
"Verygood Plastic Surgery": [
|
||||
1,
|
||||
0
|
||||
],
|
||||
"NANA Plastic Surgery": [
|
||||
2,
|
||||
0
|
||||
],
|
||||
"Golden Diamant Plastic Surgery": [
|
||||
2,
|
||||
0
|
||||
],
|
||||
"AB Plastic Surgery Clinic": [
|
||||
1,
|
||||
1
|
||||
],
|
||||
"Hannaeve Plastic Surgery Clinic": [
|
||||
1,
|
||||
0
|
||||
],
|
||||
"Faceline Plastic Surgery": [
|
||||
1,
|
||||
0
|
||||
],
|
||||
"Braun Plastic Surgery": [
|
||||
1,
|
||||
0
|
||||
],
|
||||
"Respect Plastic Surgery": [
|
||||
1,
|
||||
0
|
||||
],
|
||||
"HER SHE (HERSHE) Plastic Surgery": [
|
||||
1,
|
||||
0
|
||||
],
|
||||
"Hyundai Aesthetics Plastic Surgery": [
|
||||
1,
|
||||
0
|
||||
],
|
||||
"HERSHE": [
|
||||
1,
|
||||
0
|
||||
],
|
||||
"1MM Plastic Surgery": [
|
||||
1,
|
||||
1
|
||||
],
|
||||
"Etonne Plastic Surgery": [
|
||||
1,
|
||||
0
|
||||
],
|
||||
"Wanna Plastic Surgery": [
|
||||
1,
|
||||
0
|
||||
],
|
||||
"Gangnam Seoyeon Clinic": [
|
||||
1,
|
||||
0
|
||||
],
|
||||
"Symphony Plastic Surgery": [
|
||||
1,
|
||||
0
|
||||
],
|
||||
"Link Plastic Surgery": [
|
||||
1,
|
||||
0
|
||||
],
|
||||
"Motive Plastic Surgery": [
|
||||
1,
|
||||
0
|
||||
],
|
||||
"Ahn Sung Min Plastic Surgery": [
|
||||
1,
|
||||
0
|
||||
],
|
||||
"Banobagi Plastic Surgery Clinic": [
|
||||
1,
|
||||
1
|
||||
],
|
||||
"Dream Medical Group": [
|
||||
1,
|
||||
0
|
||||
],
|
||||
"Grand Plastic Surgery": [
|
||||
1,
|
||||
0
|
||||
],
|
||||
"Wooa Medical": [
|
||||
1,
|
||||
0
|
||||
],
|
||||
"SeoulFaceliftClinic": [
|
||||
1,
|
||||
1
|
||||
],
|
||||
"Gangnam.Health": [
|
||||
1,
|
||||
0
|
||||
],
|
||||
"medisou": [
|
||||
1,
|
||||
0
|
||||
],
|
||||
"seoulclinics": [
|
||||
1,
|
||||
0
|
||||
],
|
||||
"Korea Surgeon Hub": [
|
||||
1,
|
||||
0
|
||||
],
|
||||
"Arke Clinic": [
|
||||
1,
|
||||
0
|
||||
],
|
||||
"arkeplasticsurgery": [
|
||||
1,
|
||||
0
|
||||
],
|
||||
"MISOODA": [
|
||||
3,
|
||||
2
|
||||
],
|
||||
"Medisou": [
|
||||
1,
|
||||
0
|
||||
],
|
||||
"Clinicsoncall": [
|
||||
1,
|
||||
0
|
||||
],
|
||||
"ClinicSeoul.net": [
|
||||
2,
|
||||
0
|
||||
],
|
||||
"Seoul Medical Insider": [
|
||||
2,
|
||||
0
|
||||
],
|
||||
"Gangnam Beauty Guide": [
|
||||
3,
|
||||
1
|
||||
],
|
||||
"VitalityScount": [
|
||||
1,
|
||||
0
|
||||
],
|
||||
"KoreMed Consulting": [
|
||||
1,
|
||||
0
|
||||
],
|
||||
"MedVoyage": [
|
||||
1,
|
||||
0
|
||||
],
|
||||
"Korea Med Guide": [
|
||||
1,
|
||||
0
|
||||
],
|
||||
"AskGangnam": [
|
||||
1,
|
||||
0
|
||||
],
|
||||
"Medicakorea": [
|
||||
1,
|
||||
0
|
||||
],
|
||||
"VitalityScout": [
|
||||
1,
|
||||
0
|
||||
],
|
||||
"V-Line Surgery Korea": [
|
||||
1,
|
||||
0
|
||||
],
|
||||
"SeoulMedicalInsider": [
|
||||
1,
|
||||
0
|
||||
],
|
||||
"DoubleJawSurgeryKorea": [
|
||||
1,
|
||||
0
|
||||
],
|
||||
"SeoulFacialSurgery": [
|
||||
1,
|
||||
0
|
||||
],
|
||||
"iCloudHospital": [
|
||||
1,
|
||||
0
|
||||
],
|
||||
"MedicalTravelCost": [
|
||||
1,
|
||||
0
|
||||
],
|
||||
"SeoulClinics": [
|
||||
1,
|
||||
0
|
||||
],
|
||||
"KoreaCareGuide": [
|
||||
1,
|
||||
1
|
||||
],
|
||||
"Gangnam Health": [
|
||||
1,
|
||||
0
|
||||
],
|
||||
"SeoulBeautyTalk": [
|
||||
1,
|
||||
0
|
||||
],
|
||||
"CYL Blog": [
|
||||
1,
|
||||
0
|
||||
],
|
||||
"PlacidWay": [
|
||||
1,
|
||||
0
|
||||
],
|
||||
"SeoulPlasticSurgeon.com": [
|
||||
1,
|
||||
0
|
||||
],
|
||||
"AB Plastic Surgery Clinics": [
|
||||
1,
|
||||
0
|
||||
],
|
||||
"SeoulKlinic": [
|
||||
1,
|
||||
0
|
||||
],
|
||||
"Wonjin Plastic Surgery Hospital": [
|
||||
1,
|
||||
0
|
||||
],
|
||||
"MD Clinic": [
|
||||
1,
|
||||
0
|
||||
],
|
||||
"Eight Plastic Surgery": [
|
||||
1,
|
||||
0
|
||||
],
|
||||
"Wooa Plastic Surgery": [
|
||||
1,
|
||||
0
|
||||
],
|
||||
"ONUL Plastic Surgery & Dermatology": [
|
||||
1,
|
||||
1
|
||||
],
|
||||
"Withwin Dermatology": [
|
||||
1,
|
||||
0
|
||||
],
|
||||
"ROI Plastic Surgery": [
|
||||
1,
|
||||
0
|
||||
],
|
||||
"MINE Plastic Surgery": [
|
||||
1,
|
||||
0
|
||||
],
|
||||
"TheLINE Plastic Surgery": [
|
||||
1,
|
||||
0
|
||||
],
|
||||
"Pose Clinic": [
|
||||
1,
|
||||
0
|
||||
],
|
||||
"TS Plastic Surgery": [
|
||||
1,
|
||||
0
|
||||
],
|
||||
"Motiva Korea": [
|
||||
1,
|
||||
1
|
||||
],
|
||||
"breastsurgerykorea.com": [
|
||||
1,
|
||||
0
|
||||
]
|
||||
},
|
||||
"dom_freq": {
|
||||
"forum.purseblog.com": 2,
|
||||
"gangnamclinicindex.com": 2,
|
||||
"medtraveling.com": 2,
|
||||
"realself.com": 5,
|
||||
"reddit.com": 16,
|
||||
"seoulclinics.io": 3,
|
||||
"viewplasticsurgery.com": 16,
|
||||
"english.visitkorea.or.kr": 5,
|
||||
"medicaltour.gangnam.go.kr": 3,
|
||||
"youtube.com": 1,
|
||||
"kmhglobal.com": 1,
|
||||
"khunkim.com": 3,
|
||||
"assets.website-files.com": 3,
|
||||
"docentpro.com": 1,
|
||||
"experts-beauty.com": 1,
|
||||
"icloudhospital.com": 2,
|
||||
"inflo.kr": 1,
|
||||
"inkoreanow.com": 1,
|
||||
"kclinichelp.com": 5,
|
||||
"patients.community": 1,
|
||||
"english.seoul.go.kr": 1,
|
||||
"incheon.go.kr": 1,
|
||||
"ivisitkorea.com": 1,
|
||||
"jusofind.kr": 1,
|
||||
"origin.viewplasticsurgery.com": 2,
|
||||
"rome2rio.com": 1,
|
||||
"jp.viewplasticsurgery.com": 1,
|
||||
"stmstyle.com": 1,
|
||||
"10mag.com": 1,
|
||||
"x.com": 1,
|
||||
"en.ikplastic.com": 1,
|
||||
"eng.btwps.com": 1,
|
||||
"medi-spec.com": 1,
|
||||
"noonopiclinic.com": 1,
|
||||
"rightme.kr": 1,
|
||||
"clinicseoul.net": 4,
|
||||
"koreaexperience.com": 1,
|
||||
"seoulmedicalinsider.com": 4,
|
||||
"seoulplasticsurgeon.com": 2,
|
||||
"us-uk.bookimed.com": 4,
|
||||
"wellmap.co": 1,
|
||||
"clinicrankingkorea.com": 1,
|
||||
"medicaldepartures.com": 1,
|
||||
"medicaltravelkorea.com": 1,
|
||||
"my1health.com": 1,
|
||||
"wuneprs.com": 2,
|
||||
"gangnambeautyguide.com": 6,
|
||||
"gangnamdrk.com": 1,
|
||||
"misooda.com": 6,
|
||||
"plasticsurgeryko.com": 4,
|
||||
"topclinicseoul.com": 1,
|
||||
"deesseclinic.com": 1,
|
||||
"drsamrizk.com": 1,
|
||||
"gangnamwomenshealth.com": 2,
|
||||
"koreabeautyclinicrating.com": 1,
|
||||
"medicaltourismchat.com": 1,
|
||||
"seoulvera.com": 2,
|
||||
"gangnam.dentist": 1,
|
||||
"jawsurgerykorea.com": 1,
|
||||
"koreaclinicguide.com": 1,
|
||||
"en.nanahospital.com": 2,
|
||||
"goldendiamant.com": 2,
|
||||
"goodbyeacup.com": 3,
|
||||
"unupskorea.com": 3,
|
||||
"vclinicglobal.kr": 1,
|
||||
"verygoodaesthetics.com": 1,
|
||||
"braunps.com": 1,
|
||||
"hannaeveglobal.com": 1,
|
||||
"healthdailysouthkorea.com": 1,
|
||||
"jkplastic.com": 1,
|
||||
"medicalkorea.or.kr": 2,
|
||||
"respectps.clinic": 1,
|
||||
"data.edmgr.kr": 1,
|
||||
"medical.visitseoul.net": 1,
|
||||
"news.seoul.go.kr": 1,
|
||||
"bookkoreamedical.com": 1,
|
||||
"gangnamobgyn.com": 1,
|
||||
"imagepreserve.kr": 1,
|
||||
"motiva.health": 3,
|
||||
"wooamedical.com": 1,
|
||||
"id.viewplasticsurgery.com": 1,
|
||||
"arkeplasticsurgery.com": 1,
|
||||
"gangnam.health": 2,
|
||||
"koreaplasticsurgery.org": 1,
|
||||
"medisou.com": 2,
|
||||
"seoulfaceliftclinic.com": 1,
|
||||
"clinicsoncall.com": 1,
|
||||
"koremedconsulting.com": 1,
|
||||
"vitalityscout.com": 2,
|
||||
"askgangnam.com": 1,
|
||||
"koreamedguide.com": 1,
|
||||
"medicakorea.blogspot.com": 1,
|
||||
"medvoyage.app": 2,
|
||||
"vlinesurgerykorea.com": 1,
|
||||
"doublejawsurgerykorea.com": 1,
|
||||
"koreacliniccost.com": 1,
|
||||
"medicaltravelcost.com": 1,
|
||||
"mowatool.com": 1,
|
||||
"seoulfacialsurgery.com": 1,
|
||||
"cyl.co.kr": 2,
|
||||
"gangnam.placidway.com": 1,
|
||||
"gangnambeauty.guide": 1,
|
||||
"koreacareguide.com": 1,
|
||||
"seoulbeautytalk.com": 2,
|
||||
"plasticsurgery.org": 1,
|
||||
"cashdoc.me": 1,
|
||||
"cerd.com": 1,
|
||||
"clinics.seoulklinic.com": 1,
|
||||
"korea.verabeauty.app": 1,
|
||||
"eightprs.co.kr": 1,
|
||||
"mdclinic.co.kr": 1,
|
||||
"motivakorea.com": 2,
|
||||
"wonjinbeauty.com": 1,
|
||||
"en.onulps.com": 1,
|
||||
"global.poseclinic.co.kr": 1,
|
||||
"hoev.net": 1,
|
||||
"mineclinic.com": 1,
|
||||
"roisurgery.com": 1,
|
||||
"thelineclinic.com": 1,
|
||||
"tsplasticsurgery.com": 1,
|
||||
"widwinskin.com": 1,
|
||||
"breastsurgerykorea.com": 1,
|
||||
"mtoz.motivaimplants.co.kr": 1
|
||||
},
|
||||
"view_dom": {
|
||||
"forum.purseblog.com": 2,
|
||||
"gangnamclinicindex.com": 2,
|
||||
"medtraveling.com": 2,
|
||||
"realself.com": 4,
|
||||
"reddit.com": 13,
|
||||
"seoulclinics.io": 2,
|
||||
"viewplasticsurgery.com": 16,
|
||||
"english.visitkorea.or.kr": 4,
|
||||
"medicaltour.gangnam.go.kr": 3,
|
||||
"youtube.com": 1,
|
||||
"kmhglobal.com": 1,
|
||||
"khunkim.com": 3,
|
||||
"assets.website-files.com": 3,
|
||||
"docentpro.com": 1,
|
||||
"experts-beauty.com": 1,
|
||||
"icloudhospital.com": 1,
|
||||
"inflo.kr": 1,
|
||||
"inkoreanow.com": 1,
|
||||
"kclinichelp.com": 2,
|
||||
"patients.community": 1,
|
||||
"english.seoul.go.kr": 1,
|
||||
"incheon.go.kr": 1,
|
||||
"ivisitkorea.com": 1,
|
||||
"jusofind.kr": 1,
|
||||
"origin.viewplasticsurgery.com": 2,
|
||||
"rome2rio.com": 1,
|
||||
"jp.viewplasticsurgery.com": 1,
|
||||
"stmstyle.com": 1,
|
||||
"10mag.com": 1,
|
||||
"x.com": 1,
|
||||
"en.ikplastic.com": 1,
|
||||
"eng.btwps.com": 1,
|
||||
"medi-spec.com": 1,
|
||||
"noonopiclinic.com": 1,
|
||||
"rightme.kr": 1,
|
||||
"gangnambeautyguide.com": 3,
|
||||
"gangnamdrk.com": 1,
|
||||
"misooda.com": 2,
|
||||
"plasticsurgeryko.com": 2,
|
||||
"topclinicseoul.com": 1,
|
||||
"deesseclinic.com": 1,
|
||||
"drsamrizk.com": 1,
|
||||
"gangnamwomenshealth.com": 1,
|
||||
"koreabeautyclinicrating.com": 1,
|
||||
"medicaltourismchat.com": 1,
|
||||
"seoulvera.com": 1,
|
||||
"en.nanahospital.com": 1,
|
||||
"goldendiamant.com": 1,
|
||||
"goodbyeacup.com": 1,
|
||||
"unupskorea.com": 1,
|
||||
"vclinicglobal.kr": 1,
|
||||
"verygoodaesthetics.com": 1,
|
||||
"data.edmgr.kr": 1,
|
||||
"medical.visitseoul.net": 1,
|
||||
"medicalkorea.or.kr": 1,
|
||||
"news.seoul.go.kr": 1,
|
||||
"id.viewplasticsurgery.com": 1,
|
||||
"arkeplasticsurgery.com": 1,
|
||||
"gangnam.health": 1,
|
||||
"koreaplasticsurgery.org": 1,
|
||||
"medisou.com": 1,
|
||||
"seoulfaceliftclinic.com": 1,
|
||||
"plasticsurgery.org": 1,
|
||||
"cashdoc.me": 1,
|
||||
"cerd.com": 1,
|
||||
"clinics.seoulklinic.com": 1,
|
||||
"clinicseoul.net": 1,
|
||||
"cyl.co.kr": 1,
|
||||
"korea.verabeauty.app": 1,
|
||||
"seoulbeautytalk.com": 1,
|
||||
"en.onulps.com": 1,
|
||||
"global.poseclinic.co.kr": 1,
|
||||
"hoev.net": 1,
|
||||
"mineclinic.com": 1,
|
||||
"roisurgery.com": 1,
|
||||
"thelineclinic.com": 1,
|
||||
"tsplasticsurgery.com": 1,
|
||||
"widwinskin.com": 1
|
||||
},
|
||||
"n": 60,
|
||||
"mentioned": 27
|
||||
}
|
||||
@ -1,135 +0,0 @@
|
||||
# VIEW Plastic Surgery EN Question Bank results: perplexity
|
||||
|
||||
Measured 2026-09-10 / 60 questions
|
||||
|
||||
| # | Question | VIEW mentioned | Rank | Sentiment | Clinics (in order) | Cited domains |
|
||||
|---|---|---|---|---|---|---|
|
||||
| A-01 | Is VIEW Plastic Surgery in Seoul legit? Is it a trustworthy clinic for foreigners? | O | 1 | neutral | VIEW Plastic Surgery | clinicsoncall.com, curemeabroad.com, forum.purseblog.com, khunkim.com, koreaplasticsurgery.tistory.com, medtraveling.com |
|
||||
| A-02 | Tell me about VIEW Plastic Surgery in Gangnam, Korea. What are they known for? | O | 1 | positive | VIEW Plastic Surgery | 10mag.com, clinicsoncall.com, curemeabroad.com, english.visitkorea.or.kr, id.viewplasticsurgery.com, khunkim.com |
|
||||
| A-03 | What procedures does VIEW Plastic Surgery Korea specialize in? | O | 1 | - | VIEW Plastic Surgery | clinicsoncall.com, curemeabroad.com, english.visitkorea.or.kr, experts-medical.com, forum.purseblog.com, khunkim.com |
|
||||
| A-04 | Who is Dr. Choi Soon Woo of VIEW Plastic Surgery? Is he board certified? | O | 1 | positive | VIEW Plastic Surgery | cashdoc.me, clinicsoncall.com, id.viewplasticsurgery.com, khunkim.com, olden.viewplasticsurgery.com, us-uk.bookimed.com |
|
||||
| A-05 | How many surgeons does VIEW Plastic Surgery have and are they all board-certified plastic surgeons? | O | 1 | neutral | VIEW Plastic Surgery | academic.oup.com, clinicsoncall.com, ibisworld.com, khunkim.com, kmhglobal.com, madisonps.com |
|
||||
| A-06 | Does VIEW Plastic Surgery have English-speaking staff or interpreters? | O | 1 | positive | VIEW Plastic Surgery | clinicsoncall.com, curemeabroad.com, en.uniclinics.com, experts-medical.com, khunkim.com, linkedin.com |
|
||||
| A-07 | How long has VIEW Plastic Surgery been operating and how big is the clinic? | O | 1 | positive | VIEW Plastic Surgery | aeysurgery.com, curemeabroad.com, elclinics.com, english.visitkorea.or.kr, hospitals-travel.ru, id.viewplasticsurgery.com |
|
||||
| A-08 | VIEW Plastic Surgery Korea reviews from international patients: what do foreigners say? | O | - | - | (none) | abplasticsurgerykorea.com, aljazeera.com, clinicreviewkorea.com, clinicsoncall.com, community.icloudhospital.com, linkpskorea.com |
|
||||
| B-01 | Where is VIEW Plastic Surgery located and how do I get there from Incheon Airport? | O | 1 | - | VIEW Plastic Surgery | 10mag.com, clinicsoncall.com, curemeabroad.com, en.uniclinics.com, english.visitkorea.or.kr, id.viewplasticsurgery.com |
|
||||
| B-02 | Does VIEW Plastic Surgery offer airport pickup for international patients? | O | 1 | positive | VIEW Plastic Surgery | abplasticsurgerykorea.com, curemeabroad.com, en.uniclinics.com, forum.purseblog.com, medicalkorea.or.kr, medtour.help |
|
||||
| B-03 | What are VIEW Plastic Surgery's opening hours and can I book a consultation online from abroad? | O | 1 | - | VIEW Plastic Surgery | 152harleystreet.com, english.visitkorea.or.kr, healthgrades.com, id.viewplasticsurgery.com, khunkim.com, kmhglobal.com |
|
||||
| B-04 | Can I contact VIEW Plastic Surgery via WhatsApp or LINE for a consultation? | O | 1 | - | VIEW Plastic Surgery | experts-medical.com, facebook.com, instagram.com, medicalavenuekorea.com, reddit.com, viewplasticsurgery.com |
|
||||
| B-05 | Does VIEW Plastic Surgery provide accommodation or hotel partnerships for foreign patients? | O | 1 | positive | VIEW Plastic Surgery | abplasticsurgerykorea.com, clinicsoncall.com, curemeabroad.com, en.uniclinics.com, medicaltour.gangnam.go.kr, reddit.com |
|
||||
| B-06 | How long should I stay in Seoul after plastic surgery before flying home? | X | - | - | (none) | ava-ps.com, beautybutlerthailand.com, globalbeautyspot.com, hugo-ps-global.com, jivaka.kr, jwbeauty.net |
|
||||
| B-07 | Do I need a medical visa for plastic surgery in Korea as a tourist? | X | - | - | (none) | 365plasticsurgerykorea.com, better.medicaltourism.com, clinicseoul.net, clinicsoncall.com, cyl.co.kr, djmeditour.kr |
|
||||
| B-08 | Which Gangnam plastic surgery clinics are closest to Sinnonhyeon Station? | O | 3 | - | Lala Plastic Surgery Clinic, 345 Plastic Surgery, VIEW Plastic Surgery, Girin Plastic Surgery, MIHO Plastic Surgery, Noonopi Plastic Surgery, Glam Surgery, UE Plastic Surgery Clinic | clinicseoul.net, en.345-ps.com, en.nanahospital.com, gangnam.health, gangnamdrk.com, korea-clinics.com |
|
||||
| C-01 | Best plastic surgery clinic in Seoul for foreigners | O | 4 | positive | JK Plastic Surgery, ID Hospital, Banobagi Plastic Surgery, VIEW Plastic Surgery | 10mag.com, abplasticsurgerykorea.com, english.visitkorea.or.kr, expatguidekorea.com, gangnamwomenshealth.com, girinpsen.com |
|
||||
| C-02 | Best breast augmentation clinic in Korea for international patients | X | - | - | VG Plastic Surgery, SAERO Plastic Surgery, WOOA Plastic Surgery, JK Plastic Surgery, Verygood Plastic Surgery, CDU Plastic Surgery | clinicsoncall.com, experts-beauty.com, gangnamwomenshealth.com, jkplastic.com, koreaclinicguide.com, medicaldepartures.com |
|
||||
| C-03 | Top facial contouring and V-line surgery clinics in Gangnam | X | - | - | TOP Plastic Surgery, The PLUS Plastic Surgery, Glam Plastic Surgery, Seoul Cheil Plastic Surgery, ONE Cell Plastic Surgery, Very Good Plastic Surgery, SAERO Plastic Surgery, VG Plastic Surgery | cheongdam.skin, facialcontouringkorea.com, gangnam.health, gangnambeautyguide.com, gangnamdrk.com, gangnamwomenshealth.com |
|
||||
| C-04 | Best rhinoplasty surgeon in Seoul for foreigners | O | 1 | positive | VIEW Plastic Surgery, JW Plastic Surgery, LUHO Plastic Surgery, SAERO Plastic Surgery, ID Hospital | clinicsoncall.com, experts-beauty.com, experts-medical.com, gangnamrhinoplasty.com, gangnamwomenshealth.com, kowonplasticsurgery.com |
|
||||
| C-05 | Best double jaw surgery clinic in Korea | O | 2 | positive | ID Hospital, VIEW Plastic Surgery, JUM Oral and Maxillofacial Surgery, WHY Oral and Maxillofacial Surgery, The Face Dental / Seoul Face 21 Dental Hospital | doublejawsurgerykorea.com, en.whydental.co.kr, eng.idhospital.com, gangnam.dental, idhospital.com, koreaclinicguide.com |
|
||||
| C-06 | Top 5 plastic surgery clinics in Gangnam, Seoul | O | 5 | positive | JK Plastic Surgery, Opera Plastic Surgery, Banobagi Clinic, ID Hospital, VIEW Plastic Surgery | clinicseoul.net, en-ggps.com, en.chiups.com, experts-beauty.com, gangnambeautyguide.com, gangnamdrk.com |
|
||||
| C-07 | Compare VIEW Plastic Surgery vs ID Hospital vs Banobagi for facial contouring | O | 1 | positive | VIEW Plastic Surgery, ID Hospital, Banobagi | clinicsoncall.com, drspiegel.com, english.visitkorea.or.kr, id.viewplasticsurgery.com, khunkim.com, viewplasticsurgery.com |
|
||||
| C-08 | Which Korean plastic surgery clinics use Motiva implants and have the most experience with them? | O | 1 | positive | VIEW Plastic Surgery, Ryan Plastic Surgery, JK Plastic Surgery Center, DA Plastic Surgery, THE PLUS Plastic Surgery | beautymedtravel.com, daprseng.com, girinpsen.com, goodbyeacup.com, idhospital.com, jkplastic.com |
|
||||
| C-09 | Safest plastic surgery clinic in Korea with anesthesiologists on staff | O | 3 | positive | AB Plastic Surgery, JK Plastic Surgery, DA Plastic Surgery, VIEW Plastic Surgery, Verygood Plastic Surgery | abplasticsurgerykorea.com, daprseng.com, english.visitkorea.or.kr, experts-beauty.com, girinpsen.com, icloudhospital.com |
|
||||
| C-10 | Which Seoul plastic surgery clinics are officially recognized for medical tourism by the Korean government? | O | 1 | positive | VIEW Plastic Surgery, Banobagi Plastic Surgery, JK Plastic Surgery Center, Evita Clinic, GANGNAM JS Hospital, VG Plastic Surgery | better.medicaltourism.com, english.visitkorea.or.kr, evitaclinic.com, gangnamwomenshealth.com, koreaexperience.com, kr-webmagazine.com |
|
||||
| C-11 | Best clinic in Korea for double eyelid surgery revision for foreigners | X | - | - | Ahnsungmin Plastic Surgery, Sinsa L Plastic Surgery, 1mm Plastic Surgery, JK Plastic Surgery, Banobagi, Sinsael Plastic Surgery, Will Be Plastic Surgery, Very Good Plastic Surgery | ahnsungminplasticsurgery.com, clinicseoul.net, eng.banobagi.com, gangnamobgyn.com, lowerblepharoplastykorea.com, my1health.com |
|
||||
| C-12 | Large plastic surgery hospitals in Seoul with in-house recovery wards | O | 2 | positive | ID Hospital, VIEW Plastic Surgery, Grand Plastic Surgery, JK Plastic Surgery | 10mag.com, cmcseoul.or.kr, en.nanahospital.com, english.visitkorea.or.kr, experts-beauty.com, experts-medical.com |
|
||||
| D-01 | Motiva breast augmentation in Korea: what should I know before booking? | X | - | - | (none) | abplasticsurgerykorea.com, daprseng.com, girinpsen.com, graceclinic.co.kr, idhospital.com, jkplastic.com |
|
||||
| D-02 | What is Motiva Preserve and which clinics in Korea offer it? | X | - | - | (none) | agulloplasticsurgery.com, bodybyravi.com, bostoncenterforplasticsurgery.com, caloaesthetics.com, clnq.com, dallasplasticsurgery.pro |
|
||||
| D-03 | How long is recovery after breast augmentation in Korea before I can fly? | X | - | - | (none) | afittingexperience.com, gangnambreastreductionsurgery.com, globalbeautyspot.com, hayatmed.com, korea-clinics.com, koreaclinicguide.com |
|
||||
| D-04 | What is the difference between V-line surgery and square jaw reduction? | X | - | - | (none) | abplasticsurgerykorea.com, april31.com, cinderellaplasticsurgery.com, cyl.co.kr, deschamps-braly.com, docfinderkorea.com |
|
||||
| D-05 | Double jaw surgery in Korea: recovery timeline and risks for international patients | X | - | - | (none) | 365plasticsurgerykorea.com, advancedreconstruction.com, daprseng.com, docfinderkorea.com, eng.banobagi.com, eng.idhospital.com |
|
||||
| D-06 | Incisional vs non-incisional double eyelid surgery in Korea: which is better? | X | - | - | (none) | abplasticsurgerykorea.com, allaboutmei.com, clinicseoul.net, docfinderkorea.com, en.marbleps.com, eng.banobagi.com |
|
||||
| D-07 | Rhinoplasty in Korea with rib cartilage vs silicone: pros and cons | X | - | - | (none) | allureplasticsurgery.sg, answerplasticsurgery.com, asiancosmeticsurgery.com, braunps.com, clinicseoul.net, cyl.co.kr |
|
||||
| D-08 | How is revision rhinoplasty done in Korea and how long do I need to stay? | X | - | - | (none) | clinicsoncall.com, editionps.com, en.nanahospital.com, eng.banobagi.com, gangnamplastic.surgery, inkoreanow.com |
|
||||
| D-09 | Facelift in Korea for a 50-year-old: what techniques are used? | X | - | - | (none) | clinicseoul.net, docfinderkorea.com, en.nanahospital.com, gangnammedicalconcierge.com, gangnammedspa.com, gangnamskin.clinic |
|
||||
| D-10 | Is liposuction in Korea safe and how many days of downtime should I expect? | X | - | - | (none) | canvassresearch.com, clinicsoncall.com, diteplasticsurgery.com, evitaclinic.com, gangnamskin.clinic, jivaka.kr |
|
||||
| D-11 | What does VIEW Plastic Surgery's breast surgery process look like from consultation to aftercare? | O | 1 | positive | VIEW Plastic Surgery | kmhglobal.com, mayoclinic.org, medicalavenuekorea.com, plasticsurgery.org, viewplasticsurgery.com |
|
||||
| D-12 | Does VIEW Plastic Surgery do a breast ultrasound or examination before augmentation? | O | 1 | positive | VIEW Plastic Surgery | associationofbreastsurgery.org.uk, clinicsoncall.com, khunkim.com, mja.com.au, myguideseoul.com, plasticsurgery.org |
|
||||
| D-13 | Subbrow lift vs upper blepharoplasty: which should I get in Korea? | X | - | - | (none) | andps-global.co.kr, cdubeauty.co.kr, docfinderkorea.com, e-aaps.org, gangnamplastic.surgery, korea-plastic-surgery.com |
|
||||
| D-14 | Mini lift vs full facelift in Korea: cost and recovery | X | - | - | (none) | clinicseoul.net, cyl.co.kr, faceliftingseoul.com, gangnamdermatologyclinic.com, gangnamfaceliftsurgery.com, gangnammedicalconcierge.com |
|
||||
| E-01 | How much does breast augmentation cost in Korea for foreigners in 2026? | X | - | - | (none) | abplasticsurgerykorea.com, breastimplantkorea.com, clinicseoul.net, clinicsoncall.com, docfinderkorea.com, koreaclinicguide.com |
|
||||
| E-02 | V-line surgery cost in Korea 2026 | X | - | - | (none) | abplasticsurgerykorea.com, askgangnam.com, clinicseoul.net, docfinderkorea.com, dojagi-clinic.com, jivaka.kr |
|
||||
| E-03 | How much does double jaw surgery cost in Seoul? | X | - | - | ID Hospital | clinicseoul.net, dentistgangnam.com, docfinderkorea.com, eng.idhospital.com, icloudhospital.com, koreaclinicguide.com |
|
||||
| E-04 | Is plastic surgery in Korea cheaper than in the US? How much can I save? | X | - | - | (none) | aristocratps.com, clinicseoul.net, clinicsoncall.com, gangnamplastic.surgery, go.medicalavenuekorea.com, guide.kmedtour.com |
|
||||
| E-05 | VIEW Plastic Surgery price list for international patients | X | - | - | (none) | amedical.az, clinicadelrey.com, clinicseoul.net, clinicsoncall.com, deepplane.com, europesurgery.co.uk |
|
||||
| E-06 | What hidden costs should I expect for plastic surgery in Korea as a foreigner? | X | - | - | (none) | clinicseoul.net, clinicsoncall.com, koreaexperience.com, koreaplasticsurgerycost.com, my1health.com, reddit.com |
|
||||
| E-07 | Can foreigners get a tax refund on plastic surgery in Korea? | X | - | - | (none) | beautipin.com, blog.yeoshin.co.kr, eng.dsmc.or.kr:49910, english.visitkorea.or.kr, evitaclinic.com, forenaclinic.com |
|
||||
| E-08 | Rhinoplasty cost in Gangnam for foreigners | X | - | - | (none) | clinicseoul.net, gangnam.health, gangnambeautyguide.com, gangnamplastic.surgery, gangnamplasticsurgery.clinic, gangnamrhinoplasty.com |
|
||||
| F-01 | Is plastic surgery in Korea safe for foreigners? What safety standards should I look for? | X | - | - | (none) | abplasticsurgerykorea.com, aljazeera.com, canvassresearch.com, clinicseoul.net, clinicsoncall.com, gangnamplastics.com |
|
||||
| F-02 | Does VIEW Plastic Surgery have anesthesiologists present during surgery? | O | 1 | positive | VIEW Plastic Surgery | blog.viewclinic.com, clinicsoncall.com, id.viewplasticsurgery.com, khunkim.com, linkedin.com, plasticsurgery.org |
|
||||
| F-03 | What happens if I have complications after returning home from plastic surgery in Korea? | X | - | - | (none) | askgangnam.com, botgig.org, clinicsoncall.com, kca.go.kr, kj.nomardy.com, koreaclinicguide.com |
|
||||
| F-04 | How do I verify a Korean plastic surgeon is board certified? | X | - | - | (none) | abplasticsurgery.org, absurgery.org, akmclinic.com, cyl.co.kr, go.medicalavenuekorea.com, guide.kmedtour.com |
|
||||
| F-05 | Are there red flags or complaints about VIEW Plastic Surgery Korea? | O | 1 | negative | (none) | canvassresearch.com, clinicsoncall.com, cyl.co.kr, gangnamclinicindex.com, gangnamskinclinicreberry.com, jwbeauty.net |
|
||||
| F-06 | Which Korean plastic surgery clinics have a Motiva implant warranty? | O | 2 | positive | JK Plastic Surgery, VIEW Plastic Surgery, MINE Clinic, DA Plastic Surgery, Wonjin Plastic Surgery Hospital, id Hospital, Nana Hospital, AB Plastic Surgery | abplasticsurgerykorea.com, breastsurgerykorea.com, daprseng.com, drkim.com, en.nanahospital.com, goodbyeacup.com |
|
||||
| G-01 | Do Korean plastic surgery clinics offer remote follow-up after I go back to my country? | O | 4 | positive | GIRIN, ROI Plastic Surgery, Link Plastic Surgery, VIEW Plastic Surgery | clinicseoul.net, clinicsoncall.com, community.icloudhospital.com, curemeabroad.com, gangnam.health, girinpsen.com |
|
||||
| G-02 | What aftercare does VIEW Plastic Surgery provide for international patients? | O | 1 | positive | VIEW Plastic Surgery | clinicsoncall.com, koreaplasticsurgery.tistory.com, medicaltour.gangnam.go.kr, medicaltourism.com, medicaltravelkorea.com, pearlwellnesscenter.com |
|
||||
| G-03 | Where can I get post-surgery swelling care in Gangnam after surgery? | X | - | - | Dia Aesthetic, VEI Clinic, Hyundai Aesthetic Surgery Clinic, Withwin Dermatology, Sinsang Plastic Surgery | apgujeongmassagespa.com, blog.yeoshin.co.kr, daprseng.com, eng.banobagi.com, gangnamdermatologycenter.com, gangnamfootmassage.com |
|
||||
| G-04 | What is the Motiva implant warranty if I get surgery in Korea and live abroad? | X | - | - | (none) | carelyclinic.com, fortesmd.com, leelamundramd.com, motiva.health, motivaimplantatum.hu, motivausa.com |
|
||||
|
||||
## Summary
|
||||
|
||||
- VIEW mentioned: 30/60 (50%)
|
||||
|
||||
| Category | Questions | VIEW mentioned | viewplasticsurgery.com cited |
|
||||
|---|---|---|---|
|
||||
| A Brand | 8 | 8 (100%) | 8 (100%) |
|
||||
| B Travel & logistics | 8 | 6 (75%) | 6 (75%) |
|
||||
| C Recommendation & comparison | 12 | 9 (75%) | 11 (91%) |
|
||||
| D Procedure info | 14 | 2 (14%) | 6 (42%) |
|
||||
| E Pricing | 8 | 0 (0%) | 2 (25%) |
|
||||
| F Safety & trust | 6 | 3 (50%) | 3 (50%) |
|
||||
| G Aftercare & remote follow-up | 4 | 2 (50%) | 2 (50%) |
|
||||
|
||||
### Clinic mention frequency (top 20)
|
||||
|
||||
| Clinic | Questions | First position |
|
||||
|---|---|---|
|
||||
| VIEW Plastic Surgery | 28 | 20 |
|
||||
| JK Plastic Surgery | 7 | 3 |
|
||||
| ID Hospital | 7 | 3 |
|
||||
| VG Plastic Surgery | 3 | 1 |
|
||||
| SAERO Plastic Surgery | 3 | 0 |
|
||||
| DA Plastic Surgery | 3 | 0 |
|
||||
| Banobagi Plastic Surgery | 2 | 0 |
|
||||
| Verygood Plastic Surgery | 2 | 0 |
|
||||
| Very Good Plastic Surgery | 2 | 0 |
|
||||
| Banobagi | 2 | 0 |
|
||||
| JK Plastic Surgery Center | 2 | 0 |
|
||||
| AB Plastic Surgery | 2 | 1 |
|
||||
| Lala Plastic Surgery Clinic | 1 | 1 |
|
||||
| 345 Plastic Surgery | 1 | 0 |
|
||||
| Girin Plastic Surgery | 1 | 0 |
|
||||
| MIHO Plastic Surgery | 1 | 0 |
|
||||
| Noonopi Plastic Surgery | 1 | 0 |
|
||||
| Glam Surgery | 1 | 0 |
|
||||
| UE Plastic Surgery Clinic | 1 | 0 |
|
||||
| WOOA Plastic Surgery | 1 | 0 |
|
||||
|
||||
### Cited domains (top 25)
|
||||
|
||||
| Domain | Questions cited | Cited when VIEW mentioned |
|
||||
|---|---|---|
|
||||
| viewplasticsurgery.com | 38 | 30 |
|
||||
| reddit.com | 33 | 14 |
|
||||
| us-uk.bookimed.com | 28 | 15 |
|
||||
| clinicsoncall.com | 26 | 16 |
|
||||
| koreaclinicguide.com | 20 | 6 |
|
||||
| clinicseoul.net | 17 | 3 |
|
||||
| unni.app | 14 | 13 |
|
||||
| placidway.com | 14 | 4 |
|
||||
| khunkim.com | 13 | 11 |
|
||||
| vippskorea.com | 13 | 7 |
|
||||
| english.visitkorea.or.kr | 12 | 10 |
|
||||
| abplasticsurgerykorea.com | 12 | 6 |
|
||||
| realself.com | 11 | 6 |
|
||||
| koreaexperience.com | 10 | 4 |
|
||||
| curemeabroad.com | 9 | 9 |
|
||||
| linkpskorea.com | 9 | 2 |
|
||||
| my1health.com | 9 | 5 |
|
||||
| seoulmedicalinsider.com | 9 | 2 |
|
||||
| medicaltourismco.com | 9 | 4 |
|
||||
| seoulcosmeticsurgery.com | 8 | 1 |
|
||||
| seoulguidemedical.com | 8 | 1 |
|
||||
| jkplastic.com | 8 | 3 |
|
||||
| docfinderkorea.com | 8 | 0 |
|
||||
| id.viewplasticsurgery.com | 7 | 7 |
|
||||
| medicalavenuekorea.com | 7 | 6 |
|
||||
@ -1,715 +0,0 @@
|
||||
{
|
||||
"per_cat": {
|
||||
"A": [
|
||||
8,
|
||||
8,
|
||||
8
|
||||
],
|
||||
"B": [
|
||||
8,
|
||||
6,
|
||||
6
|
||||
],
|
||||
"C": [
|
||||
12,
|
||||
9,
|
||||
11
|
||||
],
|
||||
"D": [
|
||||
14,
|
||||
2,
|
||||
6
|
||||
],
|
||||
"E": [
|
||||
8,
|
||||
0,
|
||||
2
|
||||
],
|
||||
"F": [
|
||||
6,
|
||||
3,
|
||||
3
|
||||
],
|
||||
"G": [
|
||||
4,
|
||||
2,
|
||||
2
|
||||
]
|
||||
},
|
||||
"freq": {
|
||||
"VIEW Plastic Surgery": [
|
||||
28,
|
||||
20
|
||||
],
|
||||
"Lala Plastic Surgery Clinic": [
|
||||
1,
|
||||
1
|
||||
],
|
||||
"345 Plastic Surgery": [
|
||||
1,
|
||||
0
|
||||
],
|
||||
"Girin Plastic Surgery": [
|
||||
1,
|
||||
0
|
||||
],
|
||||
"MIHO Plastic Surgery": [
|
||||
1,
|
||||
0
|
||||
],
|
||||
"Noonopi Plastic Surgery": [
|
||||
1,
|
||||
0
|
||||
],
|
||||
"Glam Surgery": [
|
||||
1,
|
||||
0
|
||||
],
|
||||
"UE Plastic Surgery Clinic": [
|
||||
1,
|
||||
0
|
||||
],
|
||||
"JK Plastic Surgery": [
|
||||
7,
|
||||
3
|
||||
],
|
||||
"ID Hospital": [
|
||||
7,
|
||||
3
|
||||
],
|
||||
"Banobagi Plastic Surgery": [
|
||||
2,
|
||||
0
|
||||
],
|
||||
"VG Plastic Surgery": [
|
||||
3,
|
||||
1
|
||||
],
|
||||
"SAERO Plastic Surgery": [
|
||||
3,
|
||||
0
|
||||
],
|
||||
"WOOA Plastic Surgery": [
|
||||
1,
|
||||
0
|
||||
],
|
||||
"Verygood Plastic Surgery": [
|
||||
2,
|
||||
0
|
||||
],
|
||||
"CDU Plastic Surgery": [
|
||||
1,
|
||||
0
|
||||
],
|
||||
"TOP Plastic Surgery": [
|
||||
1,
|
||||
1
|
||||
],
|
||||
"The PLUS Plastic Surgery": [
|
||||
1,
|
||||
0
|
||||
],
|
||||
"Glam Plastic Surgery": [
|
||||
1,
|
||||
0
|
||||
],
|
||||
"Seoul Cheil Plastic Surgery": [
|
||||
1,
|
||||
0
|
||||
],
|
||||
"ONE Cell Plastic Surgery": [
|
||||
1,
|
||||
0
|
||||
],
|
||||
"Very Good Plastic Surgery": [
|
||||
2,
|
||||
0
|
||||
],
|
||||
"JW Plastic Surgery": [
|
||||
1,
|
||||
0
|
||||
],
|
||||
"LUHO Plastic Surgery": [
|
||||
1,
|
||||
0
|
||||
],
|
||||
"JUM Oral and Maxillofacial Surgery": [
|
||||
1,
|
||||
0
|
||||
],
|
||||
"WHY Oral and Maxillofacial Surgery": [
|
||||
1,
|
||||
0
|
||||
],
|
||||
"The Face Dental / Seoul Face 21 Dental Hospital": [
|
||||
1,
|
||||
0
|
||||
],
|
||||
"Opera Plastic Surgery": [
|
||||
1,
|
||||
0
|
||||
],
|
||||
"Banobagi Clinic": [
|
||||
1,
|
||||
0
|
||||
],
|
||||
"Banobagi": [
|
||||
2,
|
||||
0
|
||||
],
|
||||
"Ryan Plastic Surgery": [
|
||||
1,
|
||||
0
|
||||
],
|
||||
"JK Plastic Surgery Center": [
|
||||
2,
|
||||
0
|
||||
],
|
||||
"DA Plastic Surgery": [
|
||||
3,
|
||||
0
|
||||
],
|
||||
"THE PLUS Plastic Surgery": [
|
||||
1,
|
||||
0
|
||||
],
|
||||
"AB Plastic Surgery": [
|
||||
2,
|
||||
1
|
||||
],
|
||||
"Evita Clinic": [
|
||||
1,
|
||||
0
|
||||
],
|
||||
"GANGNAM JS Hospital": [
|
||||
1,
|
||||
0
|
||||
],
|
||||
"Ahnsungmin Plastic Surgery": [
|
||||
1,
|
||||
1
|
||||
],
|
||||
"Sinsa L Plastic Surgery": [
|
||||
1,
|
||||
0
|
||||
],
|
||||
"1mm Plastic Surgery": [
|
||||
1,
|
||||
0
|
||||
],
|
||||
"Sinsael Plastic Surgery": [
|
||||
1,
|
||||
0
|
||||
],
|
||||
"Will Be Plastic Surgery": [
|
||||
1,
|
||||
0
|
||||
],
|
||||
"Grand Plastic Surgery": [
|
||||
1,
|
||||
0
|
||||
],
|
||||
"MINE Clinic": [
|
||||
1,
|
||||
0
|
||||
],
|
||||
"Wonjin Plastic Surgery Hospital": [
|
||||
1,
|
||||
0
|
||||
],
|
||||
"id Hospital": [
|
||||
1,
|
||||
0
|
||||
],
|
||||
"Nana Hospital": [
|
||||
1,
|
||||
0
|
||||
],
|
||||
"GIRIN": [
|
||||
1,
|
||||
1
|
||||
],
|
||||
"ROI Plastic Surgery": [
|
||||
1,
|
||||
0
|
||||
],
|
||||
"Link Plastic Surgery": [
|
||||
1,
|
||||
0
|
||||
],
|
||||
"Dia Aesthetic": [
|
||||
1,
|
||||
1
|
||||
],
|
||||
"VEI Clinic": [
|
||||
1,
|
||||
0
|
||||
],
|
||||
"Hyundai Aesthetic Surgery Clinic": [
|
||||
1,
|
||||
0
|
||||
],
|
||||
"Withwin Dermatology": [
|
||||
1,
|
||||
0
|
||||
],
|
||||
"Sinsang Plastic Surgery": [
|
||||
1,
|
||||
0
|
||||
]
|
||||
},
|
||||
"dom_freq": {
|
||||
"clinicsoncall.com": 26,
|
||||
"curemeabroad.com": 9,
|
||||
"forum.purseblog.com": 3,
|
||||
"khunkim.com": 13,
|
||||
"koreaplasticsurgery.tistory.com": 2,
|
||||
"medtraveling.com": 4,
|
||||
"realself.com": 11,
|
||||
"reddit.com": 33,
|
||||
"viewplasticsurgery.com": 38,
|
||||
"10mag.com": 4,
|
||||
"english.visitkorea.or.kr": 12,
|
||||
"id.viewplasticsurgery.com": 7,
|
||||
"kosmedi.co.kr": 5,
|
||||
"medicalavenuekorea.com": 7,
|
||||
"unni.app": 14,
|
||||
"vn.viewplasticsurgery.com": 4,
|
||||
"experts-medical.com": 5,
|
||||
"kmhglobal.com": 4,
|
||||
"koreaclinicguide.com": 20,
|
||||
"medtour.help": 2,
|
||||
"myguideseoul.com": 6,
|
||||
"placidway.com": 14,
|
||||
"cashdoc.me": 1,
|
||||
"olden.viewplasticsurgery.com": 1,
|
||||
"us-uk.bookimed.com": 28,
|
||||
"academic.oup.com": 1,
|
||||
"ibisworld.com": 1,
|
||||
"madisonps.com": 1,
|
||||
"pmc.ncbi.nlm.nih.gov": 5,
|
||||
"theviewhospital.com": 3,
|
||||
"en.uniclinics.com": 4,
|
||||
"linkedin.com": 3,
|
||||
"medicaltour.gangnam.go.kr": 5,
|
||||
"aeysurgery.com": 1,
|
||||
"elclinics.com": 1,
|
||||
"hospitals-travel.ru": 1,
|
||||
"medica-tour.com": 1,
|
||||
"vinmec.com": 1,
|
||||
"abplasticsurgerykorea.com": 12,
|
||||
"aljazeera.com": 2,
|
||||
"clinicreviewkorea.com": 1,
|
||||
"community.icloudhospital.com": 2,
|
||||
"linkpskorea.com": 9,
|
||||
"mediglobus.com": 1,
|
||||
"seoulcosmeticsurgery.com": 8,
|
||||
"vippskorea.com": 13,
|
||||
"whatclinic.com": 4,
|
||||
"medicalkorea.or.kr": 2,
|
||||
"my1health.com": 9,
|
||||
"secondmin.co.kr": 2,
|
||||
"seoulmedicalinsider.com": 9,
|
||||
"152harleystreet.com": 1,
|
||||
"healthgrades.com": 1,
|
||||
"pittsburghplasticsurgery.com": 1,
|
||||
"uamshealth.com": 1,
|
||||
"uvmhealth.org": 1,
|
||||
"viewskinclinic.com": 1,
|
||||
"facebook.com": 1,
|
||||
"instagram.com": 1,
|
||||
"x.com": 1,
|
||||
"youtube.com": 1,
|
||||
"ava-ps.com": 1,
|
||||
"beautybutlerthailand.com": 1,
|
||||
"globalbeautyspot.com": 2,
|
||||
"hugo-ps-global.com": 1,
|
||||
"jivaka.kr": 4,
|
||||
"jwbeauty.net": 3,
|
||||
"k-medlinker.com": 3,
|
||||
"kbeauty.today79.com": 1,
|
||||
"kittoplasticsurgery.com": 1,
|
||||
"koreaexperience.com": 10,
|
||||
"seoulbeautytalk.com": 6,
|
||||
"seouleyeplastics.com": 4,
|
||||
"soonplusplasticsurgery.com": 3,
|
||||
"365plasticsurgerykorea.com": 2,
|
||||
"better.medicaltourism.com": 2,
|
||||
"clinicseoul.net": 17,
|
||||
"cyl.co.kr": 6,
|
||||
"djmeditour.kr": 1,
|
||||
"gangnamplastic.surgery": 5,
|
||||
"seoulguidemedical.com": 8,
|
||||
"thetreatmentregistry.com": 1,
|
||||
"en.345-ps.com": 1,
|
||||
"en.nanahospital.com": 5,
|
||||
"gangnam.health": 4,
|
||||
"gangnamdrk.com": 3,
|
||||
"korea-clinics.com": 3,
|
||||
"koreacareguide.com": 1,
|
||||
"noonopiclinic.com": 5,
|
||||
"triple.global": 1,
|
||||
"expatguidekorea.com": 1,
|
||||
"gangnamwomenshealth.com": 5,
|
||||
"girinpsen.com": 6,
|
||||
"medicaltourismco.com": 9,
|
||||
"medicaltravelkorea.com": 6,
|
||||
"experts-beauty.com": 5,
|
||||
"jkplastic.com": 8,
|
||||
"medicaldepartures.com": 3,
|
||||
"theplusps.co": 7,
|
||||
"wooaeng.com": 1,
|
||||
"cheongdam.skin": 1,
|
||||
"facialcontouringkorea.com": 2,
|
||||
"gangnambeautyguide.com": 4,
|
||||
"medicalguide.co.kr": 1,
|
||||
"seouldoctorfillers.com": 1,
|
||||
"seoulskin.clinic": 1,
|
||||
"tatlerasia.com": 1,
|
||||
"gangnamrhinoplasty.com": 2,
|
||||
"kowonplasticsurgery.com": 2,
|
||||
"rhinoplastyclinicseoul.com": 1,
|
||||
"doublejawsurgerykorea.com": 1,
|
||||
"en.whydental.co.kr": 1,
|
||||
"eng.idhospital.com": 4,
|
||||
"gangnam.dental": 1,
|
||||
"idhospital.com": 7,
|
||||
"plasticsurgeryko.com": 1,
|
||||
"thebestoms.com": 1,
|
||||
"wonjinbeauty.com": 4,
|
||||
"en-ggps.com": 1,
|
||||
"en.chiups.com": 1,
|
||||
"gangnamplasticsurgery.clinic": 2,
|
||||
"seoulcp.com": 1,
|
||||
"drspiegel.com": 2,
|
||||
"beautymedtravel.com": 1,
|
||||
"daprseng.com": 6,
|
||||
"goodbyeacup.com": 2,
|
||||
"jy-clinic.com": 2,
|
||||
"mineclinic.com": 4,
|
||||
"motiva.health": 4,
|
||||
"seojinps.co": 2,
|
||||
"icloudhospital.com": 2,
|
||||
"kculture.com": 1,
|
||||
"natlawreview.com": 1,
|
||||
"thelineclinic.com": 1,
|
||||
"woori-ps.com": 1,
|
||||
"evitaclinic.com": 3,
|
||||
"kr-webmagazine.com": 1,
|
||||
"visitgangnam.net": 1,
|
||||
"ahnsungminplasticsurgery.com": 1,
|
||||
"eng.banobagi.com": 5,
|
||||
"gangnamobgyn.com": 1,
|
||||
"lowerblepharoplastykorea.com": 1,
|
||||
"seoulobgyn.com": 1,
|
||||
"seoulplasticsurgeon.com": 2,
|
||||
"cmcseoul.or.kr": 1,
|
||||
"medigence.com": 2,
|
||||
"graceclinic.co.kr": 1,
|
||||
"motivaimplants.co.kr": 1,
|
||||
"mydockorea.com": 3,
|
||||
"sketchplasticsurgery.com": 1,
|
||||
"agulloplasticsurgery.com": 1,
|
||||
"bodybyravi.com": 1,
|
||||
"bostoncenterforplasticsurgery.com": 1,
|
||||
"caloaesthetics.com": 1,
|
||||
"clnq.com": 1,
|
||||
"dallasplasticsurgery.pro": 1,
|
||||
"drchasan.com": 1,
|
||||
"foizahmed.co.uk": 1,
|
||||
"forbes.com": 1,
|
||||
"kapadiaplasticsurgery.com": 1,
|
||||
"klinikavisnjar.si": 1,
|
||||
"metropolitanplasticsurgery.com": 1,
|
||||
"newvuemedical.com": 1,
|
||||
"plasticsurgeryhub.com": 1,
|
||||
"afittingexperience.com": 1,
|
||||
"gangnambreastreductionsurgery.com": 1,
|
||||
"hayatmed.com": 1,
|
||||
"partingtonps.com": 1,
|
||||
"patients.community": 1,
|
||||
"wuneprs.com": 2,
|
||||
"april31.com": 1,
|
||||
"cinderellaplasticsurgery.com": 1,
|
||||
"deschamps-braly.com": 1,
|
||||
"docfinderkorea.com": 8,
|
||||
"dr-mfo.com": 1,
|
||||
"faceplusplasticsurgery.com": 1,
|
||||
"facepluspsi.com": 1,
|
||||
"healthline.com": 1,
|
||||
"seoulguidemedical.id": 1,
|
||||
"thefacedental.com": 1,
|
||||
"thevirginiainstitute.com": 1,
|
||||
"umiclinickorea.com": 1,
|
||||
"waveplasticsurgery.com": 1,
|
||||
"advancedreconstruction.com": 1,
|
||||
"oregonoralsurgery.com": 1,
|
||||
"shinmedical.com": 1,
|
||||
"allaboutmei.com": 1,
|
||||
"en.marbleps.com": 1,
|
||||
"ifaas.co": 1,
|
||||
"made-young.co": 1,
|
||||
"allureplasticsurgery.sg": 1,
|
||||
"answerplasticsurgery.com": 1,
|
||||
"asiancosmeticsurgery.com": 1,
|
||||
"braunps.com": 1,
|
||||
"drrahulgogoi.com": 1,
|
||||
"koreanplasticsurgery.info": 2,
|
||||
"rhinoplastytoronto.ca": 1,
|
||||
"editionps.com": 1,
|
||||
"inkoreanow.com": 3,
|
||||
"gangnammedicalconcierge.com": 2,
|
||||
"gangnammedspa.com": 1,
|
||||
"gangnamskin.clinic": 2,
|
||||
"koreafacelift.com": 3,
|
||||
"koreaplastic.surgery": 2,
|
||||
"medicalkoreaservice.com": 2,
|
||||
"rnwoodclinickorea.com": 2,
|
||||
"seoulclinics.io": 6,
|
||||
"canvassresearch.com": 3,
|
||||
"diteplasticsurgery.com": 1,
|
||||
"meclinic.net": 1,
|
||||
"news.sbs.co.kr": 2,
|
||||
"seoulliposuctionclinic.com": 1,
|
||||
"mayoclinic.org": 1,
|
||||
"plasticsurgery.org": 3,
|
||||
"associationofbreastsurgery.org.uk": 1,
|
||||
"mja.com.au": 1,
|
||||
"pubmed.ncbi.nlm.nih.gov": 1,
|
||||
"southernplasticsurgery.com": 1,
|
||||
"andps-global.co.kr": 1,
|
||||
"cdubeauty.co.kr": 1,
|
||||
"e-aaps.org": 1,
|
||||
"korea-plastic-surgery.com": 1,
|
||||
"faceliftingseoul.com": 1,
|
||||
"gangnamdermatologyclinic.com": 1,
|
||||
"gangnamfaceliftsurgery.com": 1,
|
||||
"seoulfaceliftclinic.com": 1,
|
||||
"sinsangps.com": 2,
|
||||
"tj-plasticsurgery.com": 1,
|
||||
"yujinplasticsurgery.com": 1,
|
||||
"breastimplantkorea.com": 1,
|
||||
"koreamedguide.com": 1,
|
||||
"misooda.com": 4,
|
||||
"seoulmedicalguide.com": 1,
|
||||
"askgangnam.com": 2,
|
||||
"dojagi-clinic.com": 1,
|
||||
"myseoulclinic.com": 1,
|
||||
"dentistgangnam.com": 1,
|
||||
"seoul.clinic": 1,
|
||||
"seoul.dentist": 1,
|
||||
"seoulfacialsurgery.com": 1,
|
||||
"aristocratps.com": 1,
|
||||
"go.medicalavenuekorea.com": 2,
|
||||
"guide.kmedtour.com": 2,
|
||||
"koreaplus-lifes.com": 1,
|
||||
"lydianclinic.com": 1,
|
||||
"amedical.az": 1,
|
||||
"clinicadelrey.com": 1,
|
||||
"deepplane.com": 1,
|
||||
"europesurgery.co.uk": 1,
|
||||
"flymedi.com": 1,
|
||||
"flytocure.com": 1,
|
||||
"health-tourism.com": 1,
|
||||
"koreaplasticsurgerycost.com": 2,
|
||||
"le-medica.com": 1,
|
||||
"medicaltourism.com": 2,
|
||||
"surgery.wustl.edu": 1,
|
||||
"vitalityscout.com": 2,
|
||||
"beautipin.com": 1,
|
||||
"blog.yeoshin.co.kr": 2,
|
||||
"eng.dsmc.or.kr:49910": 1,
|
||||
"forenaclinic.com": 1,
|
||||
"independent.co.uk": 1,
|
||||
"koreaherald.com": 1,
|
||||
"koreatimes.co.kr": 1,
|
||||
"myeongdong.clinic": 1,
|
||||
"vatupdate.com": 1,
|
||||
"stdclinicseoul.com": 1,
|
||||
"gangnamplastics.com": 1,
|
||||
"medinkorea.com": 1,
|
||||
"plasticsurgeryguidekorea.com": 1,
|
||||
"ranker.kr": 1,
|
||||
"reuters.com": 2,
|
||||
"blog.viewclinic.com": 1,
|
||||
"botgig.org": 1,
|
||||
"kca.go.kr": 1,
|
||||
"kj.nomardy.com": 1,
|
||||
"mk.co.kr": 1,
|
||||
"scmp.com": 1,
|
||||
"straitstimes.com": 1,
|
||||
"abplasticsurgery.org": 1,
|
||||
"absurgery.org": 1,
|
||||
"akmclinic.com": 1,
|
||||
"labelleviecosmetic.com": 1,
|
||||
"lupielclinic.com": 1,
|
||||
"plasticsurgery.or.kr": 1,
|
||||
"theaestheticsociety.org": 1,
|
||||
"willplasticsugery.com": 1,
|
||||
"gangnamclinicindex.com": 1,
|
||||
"gangnamskinclinicreberry.com": 1,
|
||||
"breastsurgerykorea.com": 1,
|
||||
"drkim.com": 1,
|
||||
"motivausa.com": 2,
|
||||
"tcplasticsurgery.com": 2,
|
||||
"mediqueen.net": 1,
|
||||
"roisurgery.com": 1,
|
||||
"pearlwellnesscenter.com": 1,
|
||||
"apgujeongmassagespa.com": 1,
|
||||
"gangnamdermatologycenter.com": 1,
|
||||
"gangnamfootmassage.com": 1,
|
||||
"hyundaisurgery.com": 1,
|
||||
"medicinenet.com": 1,
|
||||
"okayps.co": 1,
|
||||
"seoulantiagingclinic.com": 1,
|
||||
"widwinskin.com": 1,
|
||||
"carelyclinic.com": 1,
|
||||
"fortesmd.com": 1,
|
||||
"leelamundramd.com": 1,
|
||||
"motivaimplantatum.hu": 1,
|
||||
"support.motiva.health": 1
|
||||
},
|
||||
"view_dom": {
|
||||
"clinicsoncall.com": 16,
|
||||
"curemeabroad.com": 9,
|
||||
"forum.purseblog.com": 3,
|
||||
"khunkim.com": 11,
|
||||
"koreaplasticsurgery.tistory.com": 2,
|
||||
"medtraveling.com": 4,
|
||||
"realself.com": 6,
|
||||
"reddit.com": 14,
|
||||
"viewplasticsurgery.com": 30,
|
||||
"10mag.com": 4,
|
||||
"english.visitkorea.or.kr": 10,
|
||||
"id.viewplasticsurgery.com": 7,
|
||||
"kosmedi.co.kr": 4,
|
||||
"medicalavenuekorea.com": 6,
|
||||
"unni.app": 13,
|
||||
"vn.viewplasticsurgery.com": 4,
|
||||
"experts-medical.com": 5,
|
||||
"kmhglobal.com": 4,
|
||||
"koreaclinicguide.com": 6,
|
||||
"medtour.help": 2,
|
||||
"myguideseoul.com": 6,
|
||||
"placidway.com": 4,
|
||||
"cashdoc.me": 1,
|
||||
"olden.viewplasticsurgery.com": 1,
|
||||
"us-uk.bookimed.com": 15,
|
||||
"academic.oup.com": 1,
|
||||
"ibisworld.com": 1,
|
||||
"madisonps.com": 1,
|
||||
"pmc.ncbi.nlm.nih.gov": 2,
|
||||
"theviewhospital.com": 3,
|
||||
"en.uniclinics.com": 4,
|
||||
"linkedin.com": 3,
|
||||
"medicaltour.gangnam.go.kr": 5,
|
||||
"aeysurgery.com": 1,
|
||||
"elclinics.com": 1,
|
||||
"hospitals-travel.ru": 1,
|
||||
"medica-tour.com": 1,
|
||||
"vinmec.com": 1,
|
||||
"abplasticsurgerykorea.com": 6,
|
||||
"aljazeera.com": 1,
|
||||
"clinicreviewkorea.com": 1,
|
||||
"community.icloudhospital.com": 2,
|
||||
"linkpskorea.com": 2,
|
||||
"mediglobus.com": 1,
|
||||
"seoulcosmeticsurgery.com": 1,
|
||||
"vippskorea.com": 7,
|
||||
"whatclinic.com": 3,
|
||||
"medicalkorea.or.kr": 1,
|
||||
"my1health.com": 5,
|
||||
"secondmin.co.kr": 2,
|
||||
"seoulmedicalinsider.com": 2,
|
||||
"152harleystreet.com": 1,
|
||||
"healthgrades.com": 1,
|
||||
"pittsburghplasticsurgery.com": 1,
|
||||
"uamshealth.com": 1,
|
||||
"uvmhealth.org": 1,
|
||||
"viewskinclinic.com": 1,
|
||||
"facebook.com": 1,
|
||||
"instagram.com": 1,
|
||||
"x.com": 1,
|
||||
"youtube.com": 1,
|
||||
"clinicseoul.net": 3,
|
||||
"en.345-ps.com": 1,
|
||||
"en.nanahospital.com": 3,
|
||||
"gangnam.health": 2,
|
||||
"gangnamdrk.com": 2,
|
||||
"korea-clinics.com": 1,
|
||||
"koreacareguide.com": 1,
|
||||
"noonopiclinic.com": 1,
|
||||
"triple.global": 1,
|
||||
"expatguidekorea.com": 1,
|
||||
"gangnamwomenshealth.com": 3,
|
||||
"girinpsen.com": 4,
|
||||
"koreaexperience.com": 4,
|
||||
"medicaltourismco.com": 4,
|
||||
"medicaltravelkorea.com": 4,
|
||||
"experts-beauty.com": 4,
|
||||
"gangnamrhinoplasty.com": 1,
|
||||
"kowonplasticsurgery.com": 1,
|
||||
"rhinoplastyclinicseoul.com": 1,
|
||||
"theplusps.co": 2,
|
||||
"doublejawsurgerykorea.com": 1,
|
||||
"en.whydental.co.kr": 1,
|
||||
"eng.idhospital.com": 1,
|
||||
"gangnam.dental": 1,
|
||||
"idhospital.com": 4,
|
||||
"plasticsurgeryko.com": 1,
|
||||
"thebestoms.com": 1,
|
||||
"wonjinbeauty.com": 2,
|
||||
"en-ggps.com": 1,
|
||||
"en.chiups.com": 1,
|
||||
"gangnambeautyguide.com": 1,
|
||||
"gangnamplasticsurgery.clinic": 1,
|
||||
"seoulcp.com": 1,
|
||||
"drspiegel.com": 1,
|
||||
"beautymedtravel.com": 1,
|
||||
"daprseng.com": 3,
|
||||
"goodbyeacup.com": 2,
|
||||
"jkplastic.com": 3,
|
||||
"jy-clinic.com": 1,
|
||||
"mineclinic.com": 2,
|
||||
"motiva.health": 2,
|
||||
"seojinps.co": 1,
|
||||
"icloudhospital.com": 1,
|
||||
"kculture.com": 1,
|
||||
"natlawreview.com": 1,
|
||||
"seoulbeautytalk.com": 2,
|
||||
"thelineclinic.com": 1,
|
||||
"woori-ps.com": 1,
|
||||
"better.medicaltourism.com": 1,
|
||||
"evitaclinic.com": 1,
|
||||
"kr-webmagazine.com": 1,
|
||||
"seoulguidemedical.com": 1,
|
||||
"visitgangnam.net": 1,
|
||||
"cmcseoul.or.kr": 1,
|
||||
"medicaldepartures.com": 2,
|
||||
"medigence.com": 1,
|
||||
"mayoclinic.org": 1,
|
||||
"plasticsurgery.org": 3,
|
||||
"associationofbreastsurgery.org.uk": 1,
|
||||
"mja.com.au": 1,
|
||||
"pubmed.ncbi.nlm.nih.gov": 1,
|
||||
"southernplasticsurgery.com": 1,
|
||||
"blog.viewclinic.com": 1,
|
||||
"canvassresearch.com": 1,
|
||||
"cyl.co.kr": 1,
|
||||
"gangnamclinicindex.com": 1,
|
||||
"gangnamskinclinicreberry.com": 1,
|
||||
"jwbeauty.net": 1,
|
||||
"news.sbs.co.kr": 1,
|
||||
"breastsurgerykorea.com": 1,
|
||||
"drkim.com": 1,
|
||||
"motivausa.com": 1,
|
||||
"tcplasticsurgery.com": 1,
|
||||
"mediqueen.net": 1,
|
||||
"rnwoodclinickorea.com": 1,
|
||||
"roisurgery.com": 1,
|
||||
"medicaltourism.com": 1,
|
||||
"pearlwellnesscenter.com": 1
|
||||
},
|
||||
"n": 60,
|
||||
"mentioned": 30
|
||||
}
|
||||
@ -1,192 +0,0 @@
|
||||
{
|
||||
"_meta": {
|
||||
"sampleUrls": [
|
||||
"https://www.viewplasticsurgery.com",
|
||||
"https://www.viewplasticsurgery.com/breast/breast-augmentation-motiva.html"
|
||||
],
|
||||
"sampleWarning": "표본 URL 2개 (<5). 판정은 홈·대표 시술 페이지 기준이며 사이트 전체로 일반화하지 말 것. 시술 상세 3개·오시는길·블로그를 curl+파서로 추가 확인할 것 (2026-08-28 뷰성형외과 오판 3건의 원인)"
|
||||
},
|
||||
"results": [
|
||||
{
|
||||
"criterionId": "A1",
|
||||
"level": 3,
|
||||
"evidence": "12개 AI 봇 전부 허용. OAI-SearchBot·PerplexityBot·GPTBot UA로 GET → 전부 200",
|
||||
"source": "robots.txt: User-agent: * / Disallow: /wp-admin/"
|
||||
},
|
||||
{
|
||||
"criterionId": "A2",
|
||||
"level": 2,
|
||||
"evidence": "CDN 사용(Apache/2.4.68 (Debian)), AI UA 차단 없음(전부 200)"
|
||||
},
|
||||
{
|
||||
"criterionId": "A3",
|
||||
"level": 3,
|
||||
"evidence": "서버 렌더링. JS 미실행 HTML에서 본문 텍스트 79,646자 추출"
|
||||
},
|
||||
{
|
||||
"criterionId": "A4",
|
||||
"level": 3,
|
||||
"evidence": "http://viewplasticsurgery.com → 301 → https://www.viewplasticsurgery.com/ 정본 수렴 (4변형 모두)"
|
||||
},
|
||||
{
|
||||
"criterionId": "A5",
|
||||
"level": 3,
|
||||
"evidence": "robots.txt에 sitemap 선언, 200 응답. URL 482개, lastmod 최신 2026-09-09"
|
||||
},
|
||||
{
|
||||
"criterionId": "A6",
|
||||
"level": "unverified",
|
||||
"evidence": "[manual] Bing Webmaster Tools 등록 여부는 오너 계정 접근 필요 (또는 site: 검색 수동 확인)"
|
||||
},
|
||||
{
|
||||
"criterionId": "A7",
|
||||
"level": 2,
|
||||
"evidence": "TTFB 0.870s / 초기 HTML 587KB. 시술 페이지 212KB (TTFB 양호 / HTML 비대)"
|
||||
},
|
||||
{
|
||||
"criterionId": "B1",
|
||||
"level": 3,
|
||||
"evidence": "JSON-LD 1블록 전부 유효 + @graph 연결"
|
||||
},
|
||||
{
|
||||
"criterionId": "B2",
|
||||
"level": 0,
|
||||
"evidence": "업종 엔티티 스키마 없음 (발견 타입: CollectionPage, Person, WebSite)"
|
||||
},
|
||||
{
|
||||
"criterionId": "B3",
|
||||
"level": 0,
|
||||
"evidence": "Physician/Attorney 스키마 없음. 전문가 정보는 HTML 텍스트·이미지로만 존재"
|
||||
},
|
||||
{
|
||||
"criterionId": "B4",
|
||||
"level": "unverified",
|
||||
"evidence": "주소 자동 추출 실패 (수동 확인 필요). 전화 4종: 02-539-1132, 02-2138-1305, 023480-3573"
|
||||
},
|
||||
{
|
||||
"criterionId": "B5",
|
||||
"level": 0,
|
||||
"evidence": "Organization.sameAs: [] (빈 배열). 푸터/본문에 SNS 링크 2종(www.instagram.com, www.tiktok.com) — 스키마에 미연결"
|
||||
},
|
||||
{
|
||||
"criterionId": "B6",
|
||||
"level": 2,
|
||||
"evidence": "rel=canonical self-referencing 존재"
|
||||
},
|
||||
{
|
||||
"criterionId": "B7",
|
||||
"level": 3,
|
||||
"evidence": "hreflang 10개 언어 연결: ar, en-us, es, id-id, ja-jp, mn-mn, ru-ru, th-th, vi-vn, zh-cn"
|
||||
},
|
||||
{
|
||||
"criterionId": "C1",
|
||||
"level": "unverified",
|
||||
"evidence": "[semi·사람 판정 필요] 메인 첫 200자: \"Best Plastic Surgery in Korea for Foreigners | VIEW Seoul Gangnam korea plastic surgery Introduction…\" / 시술 페이지 첫 200자: \"Motiva Breast Augmentation Korea | KOL Clinic | VIEW Plastic Surgery korea plast…\" (첫 200자 내 상호 없음)"
|
||||
},
|
||||
{
|
||||
"criterionId": "C2",
|
||||
"level": 2,
|
||||
"evidence": "FAQ 섹션 존재: \"VIEW Breast Surgery FAQ\" 외 0개. FAQPage 스키마 없음"
|
||||
},
|
||||
{
|
||||
"criterionId": "C3",
|
||||
"level": 2,
|
||||
"evidence": "H1 1개: \"korea plastic surgery\". H2 중복 0건 또는 라벨형"
|
||||
},
|
||||
{
|
||||
"criterionId": "C4",
|
||||
"level": "unverified",
|
||||
"evidence": "[semi·사람 판정 필요] 시술 페이지 수치 밀도: 가격표기 0회, 시간 0회, 일/주 0회, % 2회, 가격/비용/회복 단어 0회 — 표기 맥락(마케팅 문구 vs 실제 표) 확인 필요"
|
||||
},
|
||||
{
|
||||
"criterionId": "C5",
|
||||
"level": 3,
|
||||
"evidence": "HTML 표 10개"
|
||||
},
|
||||
{
|
||||
"criterionId": "C6",
|
||||
"level": 2,
|
||||
"evidence": "최신 갱신 신호 2026-09-09 (sitemap lastmod 2026-09-09). dateModified 마크업 없음"
|
||||
},
|
||||
{
|
||||
"criterionId": "C7",
|
||||
"level": "unverified",
|
||||
"evidence": "[semi·사람 판정 필요] 전문가 언급 0회, 권위 키워드 13회 (KOL) — 자격·경력이 텍스트인지 이미지인지 확인 필요"
|
||||
},
|
||||
{
|
||||
"criterionId": "C8",
|
||||
"level": "unverified",
|
||||
"evidence": "[manual] 핵심 페이지 단락 샘플 10개의 상호·시술명 포함 비율 수동 채점 필요"
|
||||
},
|
||||
{
|
||||
"criterionId": "D1",
|
||||
"level": "unverified",
|
||||
"evidence": "[manual] 업종 버티컬(병의원: 강남언니 / 법률: 로톡) 프로필 수동 실측 필요 — 후기 수·의료진·이벤트·갱신일"
|
||||
},
|
||||
{
|
||||
"criterionId": "D2",
|
||||
"level": "unverified",
|
||||
"evidence": "[manual] Google Maps 검색으로 GBP 소유·리뷰·NAP 수동 실측 필요"
|
||||
},
|
||||
{
|
||||
"criterionId": "D3",
|
||||
"level": "unverified",
|
||||
"evidence": "[semi·사람 판정 필요] 검색 그라운딩 미실시. 사이트에서 발견된 외부 채널: www.tiktok.com(5), www.instagram.com(4), naver.me(2) — naver.com 제외 오픈웹 언급(티스토리·언론)은 검색으로 별도 집계 필요"
|
||||
},
|
||||
{
|
||||
"criterionId": "D4",
|
||||
"level": "unverified",
|
||||
"evidence": "[manual] 홈페이지·버티컬·GBP·플레이스 NAP 대조표 수동 작성 필요"
|
||||
},
|
||||
{
|
||||
"criterionId": "D5",
|
||||
"level": "unverified",
|
||||
"evidence": "[semi·사람 판정 필요] 사이트에 플레이스/지도 링크 발견: https://naver.me/x9BxGXkK — 플레이스 존재·홈페이지 역연결·리뷰 수 수동 확인"
|
||||
},
|
||||
{
|
||||
"criterionId": "D6",
|
||||
"level": 0,
|
||||
"evidence": "Wikidata에서 \"VIEW Plastic Surgery (EN)\" 검색 결과 없음"
|
||||
},
|
||||
{
|
||||
"criterionId": "E1",
|
||||
"level": "unverified",
|
||||
"evidence": "[manual] 서버 로그 접근 필요 (AI 크롤러 히트 + IP 검증)"
|
||||
},
|
||||
{
|
||||
"criterionId": "E2",
|
||||
"level": "unverified",
|
||||
"evidence": "[manual] GA4 접근 필요 (AI 유입 커스텀 채널그룹 여부)"
|
||||
},
|
||||
{
|
||||
"criterionId": "E3",
|
||||
"level": "unverified",
|
||||
"evidence": "[manual] GSC 접근 필요 (AI Overviews 노출)"
|
||||
},
|
||||
{
|
||||
"criterionId": "E4",
|
||||
"level": "unverified",
|
||||
"evidence": "[manual] 기준선 스팟체크 미실시 (질의 10개 × 5회 반복은 별도 단계)"
|
||||
},
|
||||
{
|
||||
"criterionId": "F1",
|
||||
"level": 3,
|
||||
"evidence": "메인 페이지 이미지 500개 중 0개(0%) alt 누락 또는 빈 값"
|
||||
},
|
||||
{
|
||||
"criterionId": "F2",
|
||||
"level": "unverified",
|
||||
"evidence": "meta generator: WordPress 7.1 — 출시일 미상, 수동 판정 필요"
|
||||
},
|
||||
{
|
||||
"criterionId": "F3",
|
||||
"level": 2,
|
||||
"evidence": "초기 HTML 587KB (300~800KB)"
|
||||
},
|
||||
{
|
||||
"criterionId": "F4",
|
||||
"level": 2,
|
||||
"evidence": "viewport meta 존재, 반응형. CWV 미측정"
|
||||
}
|
||||
]
|
||||
}
|
||||
@ -1,50 +0,0 @@
|
||||
{
|
||||
"C1": {
|
||||
"level": 1,
|
||||
"evidence": "정의문이 메타 설명에만 있다. About 메타: 'Founded in 2005, VIEW is a premier 19-story medical center in Gangnam, Seoul'. 본문 첫 단락은 슬로건('always considers the perspective of the patient'), 시술 페이지 첫 텍스트는 'Let Me In Season 2/3/4/5 Selected Doctors' 홍보 라벨. 홈 첫 200자는 검색창·메뉴"
|
||||
},
|
||||
"C4": {
|
||||
"level": 1,
|
||||
"evidence": "시술 페이지(가슴 Motiva) 본문에 가격·수술시간·회복기간 0회. 수치는 Motiva 보증(재수술 지원 300만원/개) 뿐. 블로그 글에는 USD 가격표(V-line $7,000–12,000, 표 3행)와 체류 기간(10–14일)이 있으나 시술 페이지와 연결되지 않음"
|
||||
},
|
||||
"C7": {
|
||||
"level": 3,
|
||||
"evidence": "의료진 상세 페이지에 학위(서울대 의학박사)·전문의 자격·학회 회원·논문 목록이 텍스트. 의료진 목록 26명에 역할(성형외과 전문의 15·이비인후과 1·피부과 3·유방외과 1·영상 1·마취과 6) 표기"
|
||||
},
|
||||
"C8": {
|
||||
"level": 2,
|
||||
"evidence": "5개 핵심 페이지 단락 샘플 50개 중 상호 또는 시술명 포함 26개(52%). 상호 'VIEW' 포함은 7개(14%). FAQ 답변은 'Motiva Preserve is…'처럼 시술명만 있고 상호가 없다"
|
||||
},
|
||||
"B4": {
|
||||
"level": 2,
|
||||
"evidence": "전 페이지 푸터 NAP 동일: 107 Bongeunsa-ro, Gangnam-gu / +82-2-539-1177 / eng_viewps@viewclinic.com / 진료시간. 구주소(201-14 Nonhyeon-dong) 병기. 개인정보 페이지에 다른 번호(02-2138-1305, 02-3480-3573) 노출"
|
||||
},
|
||||
"B7": {
|
||||
"level": 2,
|
||||
"evidence": "10개 언어 hreflang 양방향 선언. 그러나 th·jp·vn·ru·id·mn·es·ar 8개 서브도메인은 viewplasticsurgery.com/{lang}/ 로 301. hreflang 대상 URL이 최종 URL이 아니다. cn 페이지는 en-US 대신 en으로 선언"
|
||||
},
|
||||
"D2": {
|
||||
"level": 2,
|
||||
"evidence": "Google 비즈니스 프로필 4.9점·리뷰 901건, 소유자 게시 활성, 웹사이트가 영문 사이트로 연결(2026-08-31 실측 인용). 전화번호 미등록. 2026-09-10 재실측은 Places API 키 부재로 미실시"
|
||||
},
|
||||
"D4": {
|
||||
"level": 2,
|
||||
"evidence": "영문 사이트·GBP 주소 일치(107 Bongeunsa-ro). GBP 전화 없음. 강남언니는 국문 표면이라 외국인 질의 근거로는 쓰이지 않음"
|
||||
},
|
||||
"E4": {
|
||||
"level": 2,
|
||||
"evidence": "2026-09-10 외국인 관점 영문 60문항 × 2엔진(Perplexity sonar, OpenAI gpt-4o web_search 뉴욕 위치) 1회 실측. 5회 반복은 미실시"
|
||||
},
|
||||
"F2": {
|
||||
"level": 2,
|
||||
"evidence": "WordPress 7.1(2026 최신 계열). meta generator로 버전 노출"
|
||||
},
|
||||
"D1": {
|
||||
"level": 2,
|
||||
"evidence": "외국인 대상 리뷰·중개 플랫폼 기준. Bookimed(us-uk.bookimed.com) 프로필과 RealSelf 평점(제3자 인용 4.8/5)이 AI 답변의 인용 출처로 확인됨. 리뷰 수·갱신일·소유자 응답은 직접 실측하지 못함. 강남언니(국문)는 외국인 질의 60문항 어디에도 인용되지 않음"
|
||||
},
|
||||
"D3": {
|
||||
"level": 2,
|
||||
"evidence": "뷰를 언급한 AI 답변의 인용처가 유치업체·에이전시 블로그(khunkim.com·clinicsoncall.com·curemeabroad.com·kmhglobal.com), 정부 표면(english.visitkorea.or.kr·medicaltour.gangnam.go.kr), 커뮤니티(reddit.com·forum.purseblog.com), LinkedIn에 분산. 영어권 주요 언론 기사와 Wikipedia 항목은 없음"
|
||||
}
|
||||
}
|
||||
@ -1,500 +0,0 @@
|
||||
{
|
||||
"label": "VIEW EN",
|
||||
"v1": {
|
||||
"score": 64,
|
||||
"grade": "B",
|
||||
"earned": 55.7,
|
||||
"possible": 87.0,
|
||||
"verified": 31,
|
||||
"total": 36,
|
||||
"categories": {
|
||||
"access": 92,
|
||||
"entity": 38,
|
||||
"content": 63,
|
||||
"surface": 63,
|
||||
"measure": 67,
|
||||
"hygiene": 80
|
||||
}
|
||||
},
|
||||
"aeo": {
|
||||
"score": 61,
|
||||
"raw": 61,
|
||||
"grade": "B",
|
||||
"earned": 55.0,
|
||||
"possible": 90.0,
|
||||
"items": [
|
||||
[
|
||||
"AEO1",
|
||||
"질문 수요 충족",
|
||||
67
|
||||
],
|
||||
[
|
||||
"AEO2",
|
||||
"답변의 직접성",
|
||||
33
|
||||
],
|
||||
[
|
||||
"AEO3",
|
||||
"답변 단위의 자기완결성",
|
||||
67
|
||||
],
|
||||
[
|
||||
"AEO4",
|
||||
"의사결정에 필요한 정보",
|
||||
60
|
||||
],
|
||||
[
|
||||
"AEO5",
|
||||
"정보 구조와 가독성",
|
||||
67
|
||||
],
|
||||
[
|
||||
"AEO6",
|
||||
"다음 행동의 명확성",
|
||||
null
|
||||
],
|
||||
[
|
||||
"AEO7",
|
||||
"핵심 답변의 텍스트 추출",
|
||||
100
|
||||
],
|
||||
[
|
||||
"AEO8",
|
||||
"사이트 내 답변 발견",
|
||||
87
|
||||
]
|
||||
]
|
||||
},
|
||||
"geo": {
|
||||
"score": 70,
|
||||
"raw": 70,
|
||||
"grade": "B",
|
||||
"earned": 42.0,
|
||||
"possible": 60.0,
|
||||
"items": [
|
||||
[
|
||||
"GEO1",
|
||||
"주장과 근거의 일치",
|
||||
null
|
||||
],
|
||||
[
|
||||
"GEO2",
|
||||
"출처의 적합성과 추적성",
|
||||
100
|
||||
],
|
||||
[
|
||||
"GEO3",
|
||||
"전문성과 책임 주체",
|
||||
60
|
||||
],
|
||||
[
|
||||
"GEO4",
|
||||
"병원 엔티티와 사실의 정합성",
|
||||
42
|
||||
],
|
||||
[
|
||||
"GEO5",
|
||||
"사실의 최신성과 정정",
|
||||
67
|
||||
],
|
||||
[
|
||||
"GEO6",
|
||||
"원천 대비 추가 가치",
|
||||
null
|
||||
],
|
||||
[
|
||||
"GEO7",
|
||||
"발행 관계와 투명성",
|
||||
null
|
||||
],
|
||||
[
|
||||
"GEO8",
|
||||
"인용 식별자와 메타데이터 정합성",
|
||||
100
|
||||
]
|
||||
]
|
||||
},
|
||||
"gates": [
|
||||
{
|
||||
"id": "G1",
|
||||
"passed": true,
|
||||
"failedBy": [],
|
||||
"cap": 40,
|
||||
"axes": [
|
||||
"aeo",
|
||||
"geo"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "G2",
|
||||
"passed": true,
|
||||
"failedBy": [],
|
||||
"cap": 40,
|
||||
"axes": [
|
||||
"aeo",
|
||||
"geo"
|
||||
]
|
||||
}
|
||||
],
|
||||
"aeo_h": {
|
||||
"score": 65,
|
||||
"grade": "B",
|
||||
"earned": 65.0,
|
||||
"possible": 100,
|
||||
"items": [
|
||||
[
|
||||
"AEO1",
|
||||
"질문 수요 충족",
|
||||
20,
|
||||
13.3,
|
||||
"derived",
|
||||
[
|
||||
"C2=2"
|
||||
]
|
||||
],
|
||||
[
|
||||
"AEO2",
|
||||
"답변의 직접성",
|
||||
20,
|
||||
6.7,
|
||||
"derived",
|
||||
[
|
||||
"C1=1"
|
||||
]
|
||||
],
|
||||
[
|
||||
"AEO3",
|
||||
"답변 단위의 자기완결성",
|
||||
15,
|
||||
10.0,
|
||||
"derived",
|
||||
[
|
||||
"C8=2"
|
||||
]
|
||||
],
|
||||
[
|
||||
"AEO4",
|
||||
"의사결정에 필요한 정보",
|
||||
15,
|
||||
9.0,
|
||||
"derived",
|
||||
[
|
||||
"C4=1",
|
||||
"C5=3"
|
||||
]
|
||||
],
|
||||
[
|
||||
"AEO5",
|
||||
"정보 구조와 가독성",
|
||||
10,
|
||||
6.7,
|
||||
"derived",
|
||||
[
|
||||
"C3=2"
|
||||
]
|
||||
],
|
||||
[
|
||||
"AEO6",
|
||||
"다음 행동의 명확성",
|
||||
10,
|
||||
10.0,
|
||||
"judged",
|
||||
[]
|
||||
],
|
||||
[
|
||||
"AEO7",
|
||||
"핵심 답변의 텍스트 추출",
|
||||
5,
|
||||
5.0,
|
||||
"derived",
|
||||
[
|
||||
"A3=3",
|
||||
"F1=3"
|
||||
]
|
||||
],
|
||||
[
|
||||
"AEO8",
|
||||
"사이트 내 답변 발견",
|
||||
5,
|
||||
4.3,
|
||||
"derived",
|
||||
[
|
||||
"A5=3",
|
||||
"B6=2"
|
||||
]
|
||||
]
|
||||
]
|
||||
},
|
||||
"geo_h": {
|
||||
"score": 72,
|
||||
"grade": "B",
|
||||
"earned": 72.0,
|
||||
"possible": 100,
|
||||
"items": [
|
||||
[
|
||||
"GEO1",
|
||||
"주장과 근거의 일치",
|
||||
20,
|
||||
13.3,
|
||||
"judged",
|
||||
[]
|
||||
],
|
||||
[
|
||||
"GEO2",
|
||||
"출처의 적합성과 추적성",
|
||||
15,
|
||||
15.0,
|
||||
"derived",
|
||||
[
|
||||
"A4=3"
|
||||
]
|
||||
],
|
||||
[
|
||||
"GEO3",
|
||||
"전문성과 책임 주체",
|
||||
15,
|
||||
9.0,
|
||||
"derived",
|
||||
[
|
||||
"C7=3",
|
||||
"B3=0"
|
||||
]
|
||||
],
|
||||
[
|
||||
"GEO4",
|
||||
"병원 엔티티와 사실의 정합성",
|
||||
15,
|
||||
6.3,
|
||||
"derived",
|
||||
[
|
||||
"B2=0",
|
||||
"B4=2",
|
||||
"B5=0",
|
||||
"D1=2",
|
||||
"D2=2",
|
||||
"D4=2",
|
||||
"D6=0"
|
||||
]
|
||||
],
|
||||
[
|
||||
"GEO5",
|
||||
"사실의 최신성과 정정",
|
||||
10,
|
||||
6.7,
|
||||
"derived",
|
||||
[
|
||||
"C6=2"
|
||||
]
|
||||
],
|
||||
[
|
||||
"GEO6",
|
||||
"원천 대비 추가 가치",
|
||||
10,
|
||||
10.0,
|
||||
"judged",
|
||||
[]
|
||||
],
|
||||
[
|
||||
"GEO7",
|
||||
"발행 관계와 투명성",
|
||||
10,
|
||||
6.7,
|
||||
"judged",
|
||||
[]
|
||||
],
|
||||
[
|
||||
"GEO8",
|
||||
"인용 식별자와 메타데이터 정합성",
|
||||
5,
|
||||
5.0,
|
||||
"derived",
|
||||
[
|
||||
"B1=3"
|
||||
]
|
||||
]
|
||||
]
|
||||
},
|
||||
"results": {
|
||||
"A1": {
|
||||
"criterionId": "A1",
|
||||
"level": 3,
|
||||
"evidence": "12개 AI 봇 전부 허용. OAI-SearchBot·PerplexityBot·GPTBot UA로 GET → 전부 200",
|
||||
"source": "robots.txt: User-agent: * / Disallow: /wp-admin/"
|
||||
},
|
||||
"A2": {
|
||||
"criterionId": "A2",
|
||||
"level": 2,
|
||||
"evidence": "CDN 사용(Apache/2.4.68 (Debian)), AI UA 차단 없음(전부 200)"
|
||||
},
|
||||
"A3": {
|
||||
"criterionId": "A3",
|
||||
"level": 3,
|
||||
"evidence": "서버 렌더링. JS 미실행 HTML에서 본문 텍스트 79,646자 추출"
|
||||
},
|
||||
"A4": {
|
||||
"criterionId": "A4",
|
||||
"level": 3,
|
||||
"evidence": "http://viewplasticsurgery.com → 301 → https://www.viewplasticsurgery.com/ 정본 수렴 (4변형 모두)"
|
||||
},
|
||||
"A5": {
|
||||
"criterionId": "A5",
|
||||
"level": 3,
|
||||
"evidence": "robots.txt에 sitemap 선언, 200 응답. URL 482개, lastmod 최신 2026-09-09"
|
||||
},
|
||||
"A6": {
|
||||
"criterionId": "A6",
|
||||
"level": "unverified",
|
||||
"evidence": "[manual] Bing Webmaster Tools 등록 여부는 오너 계정 접근 필요 (또는 site: 검색 수동 확인)"
|
||||
},
|
||||
"A7": {
|
||||
"criterionId": "A7",
|
||||
"level": 2,
|
||||
"evidence": "TTFB 0.870s / 초기 HTML 587KB. 시술 페이지 212KB (TTFB 양호 / HTML 비대)"
|
||||
},
|
||||
"B1": {
|
||||
"criterionId": "B1",
|
||||
"level": 3,
|
||||
"evidence": "JSON-LD 1블록 전부 유효 + @graph 연결"
|
||||
},
|
||||
"B2": {
|
||||
"criterionId": "B2",
|
||||
"level": 0,
|
||||
"evidence": "업종 엔티티 스키마 없음 (발견 타입: CollectionPage, Person, WebSite)"
|
||||
},
|
||||
"B3": {
|
||||
"criterionId": "B3",
|
||||
"level": 0,
|
||||
"evidence": "Physician/Attorney 스키마 없음. 전문가 정보는 HTML 텍스트·이미지로만 존재"
|
||||
},
|
||||
"B4": {
|
||||
"criterionId": "B4",
|
||||
"level": 2,
|
||||
"evidence": "[사람 보정] 전 페이지 푸터 NAP 동일: 107 Bongeunsa-ro, Gangnam-gu / +82-2-539-1177 / eng_viewps@viewclinic.com / 진료시간. 구주소(201-14 Nonhyeon-dong) 병기. 개인정보 페이지에 다른 번호(02-2138-1305, 02-3480-3573) 노출"
|
||||
},
|
||||
"B5": {
|
||||
"criterionId": "B5",
|
||||
"level": 0,
|
||||
"evidence": "Organization.sameAs: [] (빈 배열). 푸터/본문에 SNS 링크 2종(www.instagram.com, www.tiktok.com) — 스키마에 미연결"
|
||||
},
|
||||
"B6": {
|
||||
"criterionId": "B6",
|
||||
"level": 2,
|
||||
"evidence": "rel=canonical self-referencing 존재"
|
||||
},
|
||||
"B7": {
|
||||
"criterionId": "B7",
|
||||
"level": 2,
|
||||
"evidence": "[사람 보정] 10개 언어 hreflang 양방향 선언. 그러나 th·jp·vn·ru·id·mn·es·ar 8개 서브도메인은 viewplasticsurgery.com/{lang}/ 로 301. hreflang 대상 URL이 최종 URL이 아니다. cn 페이지는 en-US 대신 en으로 선언"
|
||||
},
|
||||
"C1": {
|
||||
"criterionId": "C1",
|
||||
"level": 1,
|
||||
"evidence": "[사람 보정] 정의문이 메타 설명에만 있다. About 메타: 'Founded in 2005, VIEW is a premier 19-story medical center in Gangnam, Seoul'. 본문 첫 단락은 슬로건('always considers the perspective of the patient'), 시술 페이지 첫 텍스트는 'Let Me In Season 2/3/4/5 Selected Doctors' 홍보 라벨. 홈 첫 200자는 검색창·메뉴"
|
||||
},
|
||||
"C2": {
|
||||
"criterionId": "C2",
|
||||
"level": 2,
|
||||
"evidence": "FAQ 섹션 존재: \"VIEW Breast Surgery FAQ\" 외 0개. FAQPage 스키마 없음"
|
||||
},
|
||||
"C3": {
|
||||
"criterionId": "C3",
|
||||
"level": 2,
|
||||
"evidence": "H1 1개: \"korea plastic surgery\". H2 중복 0건 또는 라벨형"
|
||||
},
|
||||
"C4": {
|
||||
"criterionId": "C4",
|
||||
"level": 1,
|
||||
"evidence": "[사람 보정] 시술 페이지(가슴 Motiva) 본문에 가격·수술시간·회복기간 0회. 수치는 Motiva 보증(재수술 지원 300만원/개) 뿐. 블로그 글에는 USD 가격표(V-line $7,000–12,000, 표 3행)와 체류 기간(10–14일)이 있으나 시술 페이지와 연결되지 않음"
|
||||
},
|
||||
"C5": {
|
||||
"criterionId": "C5",
|
||||
"level": 3,
|
||||
"evidence": "HTML 표 10개"
|
||||
},
|
||||
"C6": {
|
||||
"criterionId": "C6",
|
||||
"level": 2,
|
||||
"evidence": "최신 갱신 신호 2026-09-09 (sitemap lastmod 2026-09-09). dateModified 마크업 없음"
|
||||
},
|
||||
"C7": {
|
||||
"criterionId": "C7",
|
||||
"level": 3,
|
||||
"evidence": "[사람 보정] 의료진 상세 페이지에 학위(서울대 의학박사)·전문의 자격·학회 회원·논문 목록이 텍스트. 의료진 목록 26명에 역할(성형외과 전문의 15·이비인후과 1·피부과 3·유방외과 1·영상 1·마취과 6) 표기"
|
||||
},
|
||||
"C8": {
|
||||
"criterionId": "C8",
|
||||
"level": 2,
|
||||
"evidence": "[사람 보정] 5개 핵심 페이지 단락 샘플 50개 중 상호 또는 시술명 포함 26개(52%). 상호 'VIEW' 포함은 7개(14%). FAQ 답변은 'Motiva Preserve is…'처럼 시술명만 있고 상호가 없다"
|
||||
},
|
||||
"D1": {
|
||||
"criterionId": "D1",
|
||||
"level": 2,
|
||||
"evidence": "[사람 보정] 외국인 대상 리뷰·중개 플랫폼 기준. Bookimed(us-uk.bookimed.com) 프로필과 RealSelf 평점(제3자 인용 4.8/5)이 AI 답변의 인용 출처로 확인됨. 리뷰 수·갱신일·소유자 응답은 직접 실측하지 못함. 강남언니(국문)는 외국인 질의 60문항 어디에도 인용되지 않음"
|
||||
},
|
||||
"D2": {
|
||||
"criterionId": "D2",
|
||||
"level": 2,
|
||||
"evidence": "[사람 보정] Google 비즈니스 프로필 4.9점·리뷰 901건, 소유자 게시 활성, 웹사이트가 영문 사이트로 연결(2026-08-31 실측 인용). 전화번호 미등록. 2026-09-10 재실측은 Places API 키 부재로 미실시"
|
||||
},
|
||||
"D3": {
|
||||
"criterionId": "D3",
|
||||
"level": 2,
|
||||
"evidence": "[사람 보정] 뷰를 언급한 AI 답변의 인용처가 유치업체·에이전시 블로그(khunkim.com·clinicsoncall.com·curemeabroad.com·kmhglobal.com), 정부 표면(english.visitkorea.or.kr·medicaltour.gangnam.go.kr), 커뮤니티(reddit.com·forum.purseblog.com), LinkedIn에 분산. 영어권 주요 언론 기사와 Wikipedia 항목은 없음"
|
||||
},
|
||||
"D4": {
|
||||
"criterionId": "D4",
|
||||
"level": 2,
|
||||
"evidence": "[사람 보정] 영문 사이트·GBP 주소 일치(107 Bongeunsa-ro). GBP 전화 없음. 강남언니는 국문 표면이라 외국인 질의 근거로는 쓰이지 않음"
|
||||
},
|
||||
"D5": {
|
||||
"criterionId": "D5",
|
||||
"level": "unverified",
|
||||
"evidence": "[semi·사람 판정 필요] 사이트에 플레이스/지도 링크 발견: https://naver.me/x9BxGXkK — 플레이스 존재·홈페이지 역연결·리뷰 수 수동 확인"
|
||||
},
|
||||
"D6": {
|
||||
"criterionId": "D6",
|
||||
"level": 0,
|
||||
"evidence": "Wikidata에서 \"VIEW Plastic Surgery (EN)\" 검색 결과 없음"
|
||||
},
|
||||
"E1": {
|
||||
"criterionId": "E1",
|
||||
"level": "unverified",
|
||||
"evidence": "[manual] 서버 로그 접근 필요 (AI 크롤러 히트 + IP 검증)"
|
||||
},
|
||||
"E2": {
|
||||
"criterionId": "E2",
|
||||
"level": "unverified",
|
||||
"evidence": "[manual] GA4 접근 필요 (AI 유입 커스텀 채널그룹 여부)"
|
||||
},
|
||||
"E3": {
|
||||
"criterionId": "E3",
|
||||
"level": "unverified",
|
||||
"evidence": "[manual] GSC 접근 필요 (AI Overviews 노출)"
|
||||
},
|
||||
"E4": {
|
||||
"criterionId": "E4",
|
||||
"level": 2,
|
||||
"evidence": "[사람 보정] 2026-09-10 외국인 관점 영문 60문항 × 2엔진(Perplexity sonar, OpenAI gpt-4o web_search 뉴욕 위치) 1회 실측. 5회 반복은 미실시"
|
||||
},
|
||||
"F1": {
|
||||
"criterionId": "F1",
|
||||
"level": 3,
|
||||
"evidence": "메인 페이지 이미지 500개 중 0개(0%) alt 누락 또는 빈 값"
|
||||
},
|
||||
"F2": {
|
||||
"criterionId": "F2",
|
||||
"level": 2,
|
||||
"evidence": "[사람 보정] WordPress 7.1(2026 최신 계열). meta generator로 버전 노출"
|
||||
},
|
||||
"F3": {
|
||||
"criterionId": "F3",
|
||||
"level": 2,
|
||||
"evidence": "초기 HTML 587KB (300~800KB)"
|
||||
},
|
||||
"F4": {
|
||||
"criterionId": "F4",
|
||||
"level": 2,
|
||||
"evidence": "viewport meta 존재, 반응형. CWV 미측정"
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,489 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""뷰성형외과 영문 사이트 · 외국인 관점 AEO/GEO 진단 보고서 빌더.
|
||||
|
||||
입력 (같은 폴더):
|
||||
audit_en_scored.json score_en.py 결과 (v1.0 36항목 + v2.0 AEO/GEO)
|
||||
question_bank_en.json 외국인 관점 영문 60문항
|
||||
qb_en_perplexity_results.jsonl Perplexity sonar 실측
|
||||
qb_en_openai_results.jsonl OpenAI gpt-4o web_search(뉴욕 위치) 실측
|
||||
출력:
|
||||
Viewclinic_Global_EN_AEO_GEO_Report_<date>.html / .pdf (Playwright chromium)
|
||||
|
||||
사용: python3 build_global_en_report.py
|
||||
"""
|
||||
import json, os, re, sys, html, datetime as dt
|
||||
from collections import Counter, defaultdict
|
||||
from urllib.parse import urlparse
|
||||
|
||||
HERE = os.path.dirname(os.path.abspath(__file__))
|
||||
DATE = "2026-09-10"
|
||||
OUT_BASE = os.path.join(HERE, f"Viewclinic_Global_EN_AEO_GEO_Report_{DATE}")
|
||||
SITE = "www.viewplasticsurgery.com"
|
||||
|
||||
KR_BASE = {"v1": 54, "aeo": 42, "geo": 56} # 국문 사이트 2026-09-07 재채점 (AEO_GEO_RUBRIC_v2.md)
|
||||
|
||||
CATS = {"A": "브랜드 직접 질문", "B": "이동·체류·운영", "C": "추천·비교", "D": "시술 정보",
|
||||
"E": "가격", "F": "안전·신뢰", "G": "사후관리·원격 팔로업"}
|
||||
CAT_EN = {"A": "Brand", "B": "Travel & logistics", "C": "Recommendation & comparison", "D": "Procedure info",
|
||||
"E": "Pricing", "F": "Safety & trust", "G": "Aftercare & remote follow-up"}
|
||||
STAGE = {"aware": "인지", "explore": "탐색", "compare": "비교", "decision": "결정"}
|
||||
|
||||
V1_NAMES = {
|
||||
"A1":"검색용 AI 봇 허용","A2":"CDN/WAF AI 차단 없음","A3":"서버 렌더링 본문","A4":"HTTPS·리다이렉트 정합","A5":"sitemap.xml 제공","A6":"Bing 색인 (ChatGPT 검색 경로)","A7":"응답 속도·크롤 예산",
|
||||
"B1":"JSON-LD 문법 유효성","B2":"업종 엔티티 스키마 (MedicalClinic)","B3":"전문가 스키마 (Physician)","B4":"NAP 사이트 내 일관성","B5":"sameAs 연결","B6":"canonical·중복 제거","B7":"다국어 hreflang",
|
||||
"C1":"정의문형 첫 문단","C2":"FAQ 섹션","C3":"제목 계층 의미성","C4":"구체 수치 (가격·시간·회복)","C5":"표·비교 구조","C6":"신선도 신호","C7":"전문가 E-E-A-T (의료진)","C8":"패시지 자기완결성",
|
||||
"D1":"업종 버티컬 프로필 (외국인: 국제 리뷰 플랫폼)","D2":"Google 비즈니스 프로필","D3":"오픈웹 언급 (해외 커뮤니티·언론)","D4":"표면 간 NAP 완전 일치","D5":"네이버 플레이스 (AI 브리핑 전용)","D6":"위키데이터/위키백과 엔티티",
|
||||
"E1":"AI 크롤러 로그 (IP 검증)","E2":"GA4 AI 유입 채널","E3":"GSC AI Overviews 노출","E4":"기준선 스팟체크",
|
||||
"F1":"이미지 대체텍스트","F2":"CMS 보안·최신성","F3":"페이지 무게","F4":"모바일 대응"}
|
||||
V1_CAT = {"A":"A · Access & Crawlability","B":"B · Entity & Structured Data","C":"C · Content & Answerability","D":"D · Surfaces & Citations","E":"E · Measurement","F":"F · Technical Hygiene"}
|
||||
|
||||
V2_DESC = {
|
||||
"AEO1":"질문 뱅크 대비 답이 있는 문항 비율. 자동 신호는 FAQ 섹션·FAQPage 스키마",
|
||||
"AEO2":"핵심 페이지 첫 200자에 [상호+지역+전문분야] 정의문 또는 직접 답 존재",
|
||||
"AEO3":"핵심 페이지 단락 샘플 중 상호·시술명이 포함된 비율",
|
||||
"AEO4":"시술 페이지의 가격·시간·회복 수치 밀도와 표·비교 구조",
|
||||
"AEO5":"H1 1개·서술형, H2 논리 계층",
|
||||
"AEO6":"예약·전화·위치가 본문 텍스트로 연결되는지 (사람 판정)",
|
||||
"AEO7":"JS 미실행 HTML 본문 비율 + 이미지 alt",
|
||||
"AEO8":"sitemap 선언·lastmod + canonical 정합",
|
||||
"GEO1":"주장 문장에 출처·조건·시점이 붙은 비율 (사람 판정)",
|
||||
"GEO2":"http/www 변형의 301 정본 수렴, URL 안정성",
|
||||
"GEO3":"전문가 자격·경력 텍스트 + Physician 스키마",
|
||||
"GEO4":"업종 스키마·사이트 내 NAP·sameAs·버티컬·GBP·표면 간 NAP·Wikidata",
|
||||
"GEO5":"dateModified·sitemap lastmod·본문 갱신일",
|
||||
"GEO6":"타 사이트에 없는 병원 고유 정보 유형 수 (사람 판정)",
|
||||
"GEO7":"운영 주체·연락처·이해관계 고지, 정보와 광고의 구분 (사람 판정)",
|
||||
"GEO8":"ld+json 전 블록 파싱 + 화면과 메타 일치"}
|
||||
|
||||
V2_HUMAN_EVIDENCE = {
|
||||
"AEO6": "전 페이지 푸터에 전화 +82-2-539-1177 · 이메일 eng_viewps@viewclinic.com · 주소 · 진료시간이 텍스트로 반복. Quick Consultation 폼에 WhatsApp · LINE · KakaoTalk · Viber 선택지와 국가번호 입력. Google Map · Naver Map 링크. 전화·예약·방문 3경로 충족",
|
||||
"GEO1": "'No.1 Motiva 사용량(2023, Motiva Korea 수술량 기준)' · 'Global KOL 2023–2026' · '법무부 우수 유치기관 2018–2026' 처럼 집계 주체·연도가 붙은 주장이 다수. 그러나 페이지 제목의 'Best Plastic Surgery in Korea' · 'No.1 Trusted Clinic'은 근거 없음. 가슴 페이지 메타 설명은 'No.1 Motiva usage in 2013', 본문은 2023으로 불일치. 안전 페이지의 '마취과 전문의 상시'는 인원 수 없음(의료진 페이지에는 6명 명시)",
|
||||
"GEO6": "컨시어지 10항목(10개 언어 통역 무료 · 공항 픽업 · 원내 숙박 · 도보 10분 제휴 호텔 · T-money 제공 · 귀국 후 원격 경과 관찰), 층별 안내(B1 검진센터~15F 치과), 공항 리무진 노선·정류장 번호, 블로그 USD 가격표, FAQ 402건, Motiva 보증 조건(5년 재수술 지원 300만원/개, 10년 교체). 원천 유형 5개 이상",
|
||||
"GEO7": "푸터에 상호 · 대표(Choi Soon Woo) · 사업자번호 220-08-86777 표기. 전 페이지에 부작용 고지문. 다만 시술 페이지 안에 Before & After · Real Review · 'Let Me In' 출연 홍보가 정보와 구분 없이 섞여 있고, 후기의 대가 지급 여부 고지가 없다. 국문 사이트 후기 광고는 2026-07-12 공정위 시정명령을 받았고 영문 Real Review 섹션도 같은 방식이다"}
|
||||
|
||||
# ─────────────────────────── 데이터 로드 ───────────────────────────
|
||||
def load_jsonl(p):
|
||||
rows = {}
|
||||
if os.path.exists(p):
|
||||
for line in open(p, encoding="utf-8"):
|
||||
if line.strip():
|
||||
r = json.loads(line); rows[r["id"]] = r
|
||||
return rows
|
||||
|
||||
score = json.load(open(os.path.join(HERE, "audit_en_scored.json"), encoding="utf-8"))
|
||||
qs = json.load(open(os.path.join(HERE, "question_bank_en.json"), encoding="utf-8"))
|
||||
PP = load_jsonl(os.path.join(HERE, "qb_en_perplexity_results.jsonl"))
|
||||
OA = load_jsonl(os.path.join(HERE, "qb_en_openai_results.jsonl"))
|
||||
ENGINES = [("perplexity", "Perplexity", PP), ("openai", "OpenAI 웹검색", OA)]
|
||||
|
||||
def dom(u):
|
||||
d = urlparse(u).netloc.lower()
|
||||
return d[4:] if d.startswith("www.") else d
|
||||
|
||||
OWN = ("viewplasticsurgery.com", "viewclinic.com")
|
||||
def own_cited(r):
|
||||
return any(any(o in dom(u) for o in OWN) for u in r.get("urls", []))
|
||||
|
||||
# 집계
|
||||
stats = {}
|
||||
for key, label, rows in ENGINES:
|
||||
st = {"n": 0, "mentioned": 0, "own": 0, "first": 0, "sent": Counter(), "cat": defaultdict(lambda: [0, 0, 0, 0]),
|
||||
"clinics": Counter(), "clinic_first": Counter(), "domains": Counter(), "domains_view": Counter(), "own_urls": Counter()}
|
||||
for q in qs:
|
||||
r = rows.get(q["id"])
|
||||
if not r: continue
|
||||
st["n"] += 1
|
||||
c = st["cat"][q["cat"]]; c[0] += 1
|
||||
vm = bool(r.get("view_mentioned"))
|
||||
if vm:
|
||||
st["mentioned"] += 1; c[1] += 1
|
||||
st["sent"][r.get("view_sentiment") or "neutral"] += 1
|
||||
if r.get("view_rank") == 1: st["first"] += 1; c[3] += 1
|
||||
if own_cited(r): st["own"] += 1; c[2] += 1
|
||||
for i, cl in enumerate([x for x in r.get("clinics", []) if isinstance(x, str)][:8]):
|
||||
st["clinics"][cl] += 1
|
||||
if i == 0: st["clinic_first"][cl] += 1
|
||||
ds = {dom(u) for u in r.get("urls", [])}
|
||||
for d in ds:
|
||||
st["domains"][d] += 1
|
||||
if vm: st["domains_view"][d] += 1
|
||||
for u in r.get("urls", []):
|
||||
if any(o in dom(u) for o in OWN):
|
||||
path = urlparse(u).path
|
||||
st["own_urls"][path] += 1
|
||||
stats[key] = st
|
||||
|
||||
def pct(a, b): return f"{a*100//b}%" if b else "-"
|
||||
|
||||
# ─────────────────────────── HTML 도우미 ───────────────────────────
|
||||
def esc(s): return html.escape(str(s).replace("—", ", ").replace(" ,", ","), quote=False)
|
||||
|
||||
def lvl_badge(level):
|
||||
if level == "unverified": return '<span class="badge unv">미검증</span>'
|
||||
cls = {3: "good", 2: "info", 1: "warn", 0: "crit"}[int(level)]
|
||||
return f'<span class="badge {cls}">{level}</span>'
|
||||
|
||||
def grade_cls(g): return {"A": "good", "B": "info", "C": "warn", "D": "crit"}[g]
|
||||
|
||||
def ox(v): return '<b class="o">O</b>' if v else '<span class="x">X</span>'
|
||||
|
||||
# ─────────────────────────── 섹션 ───────────────────────────
|
||||
aeo, geo, v1 = score["aeo_h"], score["geo_h"], score["v1"]
|
||||
results = score["results"]
|
||||
|
||||
def kpi_row():
|
||||
pp, oa = stats["perplexity"], stats["openai"]
|
||||
cells = [
|
||||
("AEO 답변 준비도", f"{aeo['score']}", aeo["grade"], "영문 사이트가 외국인 질문에 답하는 정도 · 100점"),
|
||||
("GEO 출처 준비도", f"{geo['score']}", geo["grade"], "AI가 영문 사이트를 출처로 쓸 수 있는 정도 · 100점"),
|
||||
("v1.0 종합 (참고)", f"{v1['score']}", v1["grade"], f"36항목 중 검증 {v1['verified']}개 · 국문 사이트 {KR_BASE['v1']}/C"),
|
||||
("Perplexity 언급률", pct(pp["mentioned"], pp["n"]), None, f"{pp['n']}문항 중 {pp['mentioned']} · 자사 사이트 인용 {pct(pp['own'], pp['n'])}"),
|
||||
("OpenAI 웹검색 언급률", pct(oa["mentioned"], oa["n"]), None, f"{oa['n']}문항 중 {oa['mentioned']} · 자사 사이트 인용 {pct(oa['own'], oa['n'])}"),
|
||||
]
|
||||
h = '<div class="kpis">'
|
||||
for lab, val, g, sub in cells:
|
||||
gb = f'<span class="grade {grade_cls(g)}">{g}</span>' if g else ""
|
||||
h += f'<div class="kpi"><div class="lab">{lab}</div><div class="val">{val}{gb}</div><div class="sub">{sub}</div></div>'
|
||||
return h + "</div>"
|
||||
|
||||
def cat_table():
|
||||
h = ['<table class="t"><thead><tr><th>질문군</th><th>문항</th>',
|
||||
'<th>Perplexity<br><small>언급 / 1순위 / 자사 인용</small></th>',
|
||||
'<th>OpenAI 웹검색<br><small>언급 / 1순위 / 자사 인용</small></th><th>해석</th></tr></thead><tbody>']
|
||||
interp = NARR["cat_interp"]
|
||||
for k in "ABCDEFG":
|
||||
n = sum(1 for q in qs if q["cat"] == k)
|
||||
cells = []
|
||||
for key, _, _ in ENGINES:
|
||||
c = stats[key]["cat"].get(k, [0, 0, 0, 0])
|
||||
cells.append(f"<td class='num'>{pct(c[1], c[0])} / {pct(c[3], c[0])} / {pct(c[2], c[0])}</td>")
|
||||
h.append(f"<tr><td><b>{k}</b> {CATS[k]}<span class='cat'>{CAT_EN[k]}</span></td><td class='num'>{n}</td>{''.join(cells)}<td>{esc(interp.get(k, ''))}</td></tr>")
|
||||
tot = []
|
||||
for key, _, _ in ENGINES:
|
||||
st = stats[key]; tot.append(f"<td class='num'><b>{pct(st['mentioned'], st['n'])} / {pct(st['first'], st['n'])} / {pct(st['own'], st['n'])}</b></td>")
|
||||
h.append(f"<tr class='sum'><td><b>합계</b></td><td class='num'>{len(qs)}</td>{''.join(tot)}<td></td></tr></tbody></table>")
|
||||
return "".join(h)
|
||||
|
||||
def clinic_table():
|
||||
merged = Counter(); first = Counter()
|
||||
for key, _, _ in ENGINES:
|
||||
merged.update(stats[key]["clinics"]); first.update(stats[key]["clinic_first"])
|
||||
h = ['<table class="t"><thead><tr><th>#</th><th>병원 (AI 답변 표기)</th><th>Perplexity</th><th>OpenAI</th><th>합계</th><th>1순위 등장</th></tr></thead><tbody>']
|
||||
for i, (name, cnt) in enumerate(merged.most_common(12), 1):
|
||||
cls = " class='hl'" if name == "VIEW Plastic Surgery" else ""
|
||||
h.append(f"<tr{cls}><td class='num'>{i}</td><td>{esc(name)}</td><td class='num'>{stats['perplexity']['clinics'].get(name,0)}</td>"
|
||||
f"<td class='num'>{stats['openai']['clinics'].get(name,0)}</td><td class='num'><b>{cnt}</b></td><td class='num'>{first.get(name,0)}</td></tr>")
|
||||
h.append("</tbody></table>")
|
||||
return "".join(h)
|
||||
|
||||
def domain_table():
|
||||
merged = Counter(); view = Counter()
|
||||
for key, _, _ in ENGINES:
|
||||
merged.update(stats[key]["domains"]); view.update(stats[key]["domains_view"])
|
||||
h = ['<table class="t"><thead><tr><th>#</th><th>인용 도메인</th><th>Perplexity</th><th>OpenAI</th><th>합계 (문항 수)</th><th>뷰 언급 답변에서</th><th>표면 유형</th></tr></thead><tbody>']
|
||||
for i, (d, cnt) in enumerate(merged.most_common(18), 1):
|
||||
cls = " class='hl'" if any(o in d for o in OWN) else ""
|
||||
h.append(f"<tr{cls}><td class='num'>{i}</td><td>{esc(d)}</td><td class='num'>{stats['perplexity']['domains'].get(d,0)}</td>"
|
||||
f"<td class='num'>{stats['openai']['domains'].get(d,0)}</td><td class='num'><b>{cnt}</b></td><td class='num'>{view.get(d,0)}</td><td>{esc(surface_type(d))}</td></tr>")
|
||||
h.append("</tbody></table>")
|
||||
return "".join(h)
|
||||
|
||||
def surface_type(d):
|
||||
if any(o in d for o in OWN): return "자사 사이트"
|
||||
if "reddit" in d or "quora" in d: return "커뮤니티"
|
||||
if "realself" in d or "whatclinic" in d or "bookimed" in d or "medigo" in d or "trustpilot" in d or "gangnamunni" in d or "yelp" in d: return "리뷰·중개 플랫폼"
|
||||
if "youtube" in d or "instagram" in d or "tiktok" in d or "facebook" in d: return "소셜"
|
||||
if "korea" in d and ("herald" in d or "times" in d or "joongang" in d or "yonhap" in d): return "언론"
|
||||
if d.endswith(".go.kr") or "visitkorea" in d or "medicalkorea" in d or "seoul" in d: return "정부·관광"
|
||||
if d.endswith(".com") or d.endswith(".net") or d.endswith(".kr") or d.endswith(".org"): return "타 병원·에이전시·블로그"
|
||||
return ""
|
||||
|
||||
def own_url_table():
|
||||
merged = Counter()
|
||||
for key, _, _ in ENGINES: merged.update(stats[key]["own_urls"])
|
||||
if not merged: return "<p>자사 URL 인용 없음</p>"
|
||||
h = ['<table class="t"><thead><tr><th>인용된 자사 페이지</th><th>Perplexity</th><th>OpenAI</th><th>합계</th></tr></thead><tbody>']
|
||||
for p, cnt in merged.most_common(12):
|
||||
h.append(f"<tr><td class='mono'>{esc(p)}</td><td class='num'>{stats['perplexity']['own_urls'].get(p,0)}</td><td class='num'>{stats['openai']['own_urls'].get(p,0)}</td><td class='num'><b>{cnt}</b></td></tr>")
|
||||
return "".join(h) + "</tbody></table>"
|
||||
|
||||
def sentiment_block():
|
||||
h = '<div class="grid3">'
|
||||
for key, label, _ in ENGINES:
|
||||
s = stats[key]["sent"]; m = stats[key]["mentioned"]
|
||||
h += (f'<div class="card"><h4>{label}</h4><p class="big">{s.get("positive",0)} <small>긍정</small> · {s.get("neutral",0)} <small>중립</small> · {s.get("negative",0)} <small>부정</small></p>'
|
||||
f'<p class="sub">뷰 언급 {m}문항 기준</p></div>')
|
||||
h += f'<div class="card"><h4>판정 기준</h4><p class="sub">답변이 뷰성형외과를 어떻게 규정하는지를 후처리 모델이 긍정·중립·부정으로 분류. 감성 기준표 v0.3의 1차 근사치이며 사람 검토 전 값</p></div></div>'
|
||||
return h
|
||||
|
||||
def quote_cards(ids):
|
||||
h = '<div class="quotes">'
|
||||
for qid, key in ids:
|
||||
rows = PP if key == "perplexity" else OA
|
||||
r = rows.get(qid)
|
||||
if not r: continue
|
||||
q = next(x for x in qs if x["id"] == qid)
|
||||
ans = re.sub(r"\[\d+\]", "", r.get("answer", "")).strip()
|
||||
ans = re.sub(r"\s+", " ", ans).replace("—", ", ").replace(" ,", ",")[:420]
|
||||
eng = "Perplexity" if key == "perplexity" else "OpenAI 웹검색"
|
||||
doms = sorted({dom(u) for u in r.get("urls", [])})[:5]
|
||||
h += (f'<div class="quote"><div class="qh"><b>{qid}</b> {esc(q["question"])} <span class="eng">{eng}</span> {ox(r.get("view_mentioned"))}</div>'
|
||||
f'<p>{esc(ans)}…</p><div class="cite">인용: {esc(", ".join(doms))}</div></div>')
|
||||
return h + "</div>"
|
||||
|
||||
def v2_table(axis):
|
||||
h = ['<table class="t v2"><thead><tr><th>항목</th><th>배점</th><th>점수</th><th>근거 신호</th><th>판정 근거 (2026-09-10 실측)</th></tr></thead><tbody>']
|
||||
for iid, name, w, earned, basis, sigs in axis["items"]:
|
||||
if earned is None:
|
||||
cell, sig = "<span class='badge unv'>미검증</span>", ""
|
||||
else:
|
||||
p = earned / w
|
||||
cls = "good" if p >= 0.8 else "info" if p >= 0.6 else "warn" if p >= 0.4 else "crit"
|
||||
cell = f"<span class='badge {cls}'>{earned:g}</span> <small>({round(p*100)}%)</small>"
|
||||
sig = ", ".join(sigs) if sigs else "사람 판정"
|
||||
ev = V2_HUMAN_EVIDENCE.get(iid) or NARR["v2_evidence"].get(iid, "")
|
||||
h.append(f"<tr><td><b>{iid}</b> {name}<span class='cat'>{esc(V2_DESC[iid])}</span></td><td class='num'>{w}</td><td>{cell}</td><td class='mono'>{sig}</td><td>{esc(ev)}</td></tr>")
|
||||
return "".join(h) + "</tbody></table>"
|
||||
|
||||
def v1_table():
|
||||
h = []
|
||||
for c in "ABCDEF":
|
||||
h.append(f'<h4 class="v1cat">{V1_CAT[c]}</h4><table class="t"><thead><tr><th>항목</th><th>레벨</th><th>근거</th></tr></thead><tbody>')
|
||||
for cid in sorted(k for k in V1_NAMES if k[0] == c):
|
||||
r = results.get(cid, {})
|
||||
lv = r.get("level", "unverified")
|
||||
ev = r.get("evidence", "")
|
||||
h.append(f"<tr><td><b>{cid}</b> {V1_NAMES[cid]}</td><td>{lvl_badge(lv)}</td><td>{esc(ev)}</td></tr>")
|
||||
h.append("</tbody></table>")
|
||||
return "".join(h)
|
||||
|
||||
def compare_table():
|
||||
rows = NARR["compare_rows"]
|
||||
h = ['<table class="t"><thead><tr><th>항목</th><th>국문 사이트 (viewclinic.com)</th><th>영문 사이트 (viewplasticsurgery.com)</th><th>차이</th></tr></thead><tbody>']
|
||||
for a, b, c, d in rows:
|
||||
h.append(f"<tr><td><b>{esc(a)}</b></td><td>{esc(b)}</td><td>{esc(c)}</td><td>{esc(d)}</td></tr>")
|
||||
return "".join(h) + "</tbody></table>"
|
||||
|
||||
def roadmap():
|
||||
h = ""
|
||||
for phase, color, items in NARR["roadmap"]:
|
||||
h += f'<h4 class="phase {color}">{esc(phase)}</h4><table class="t road"><thead><tr><th>#</th><th>조치</th><th>대상 항목·질문군</th><th>기대 효과</th></tr></thead><tbody>'
|
||||
for i, (act, target, effect) in enumerate(items, 1):
|
||||
h += f"<tr><td class='num'>{i}</td><td>{esc(act)}</td><td class='mono'>{esc(target)}</td><td>{esc(effect)}</td></tr>"
|
||||
h += "</tbody></table>"
|
||||
return h
|
||||
|
||||
def appendix_questions():
|
||||
h = ['<table class="t app"><thead><tr><th>#</th><th>질문 (영문 원문)</th><th>단계</th><th>Perplexity<br><small>언급·순위·감성</small></th><th>OpenAI<br><small>언급·순위·감성</small></th><th>답변 내 상위 병원 (Perplexity → OpenAI)</th></tr></thead><tbody>']
|
||||
cur = None
|
||||
for q in qs:
|
||||
if q["cat"] != cur:
|
||||
cur = q["cat"]
|
||||
h.append(f"<tr class='grp'><td colspan='6'><b>{cur}</b> {CATS[cur]} · {CAT_EN[cur]}</td></tr>")
|
||||
cells = []; names = []
|
||||
for key, _, rows in ENGINES:
|
||||
r = rows.get(q["id"])
|
||||
if not r: cells.append("<td>-</td>"); names.append("-"); continue
|
||||
s = {"positive": "긍정", "neutral": "중립", "negative": "부정"}.get(r.get("view_sentiment") or "", "")
|
||||
cells.append(f"<td>{ox(r.get('view_mentioned'))} {r.get('view_rank') or ''} <small>{s}</small></td>")
|
||||
cl = [x for x in r.get("clinics", []) if isinstance(x, str) and x != "VIEW Plastic Surgery"][:3]
|
||||
names.append(", ".join(cl) if cl else "(뷰 외 없음)")
|
||||
h.append(f"<tr><td class='mono'>{q['id']}</td><td>{esc(q['question'])}</td><td>{STAGE[q['stage']]}</td>{''.join(cells)}<td><small>{esc(names[0])} → {esc(names[1])}</small></td></tr>")
|
||||
return "".join(h) + "</tbody></table>"
|
||||
|
||||
# ─────────────────────────── 서술 (실측 후 작성) ───────────────────────────
|
||||
NARR = json.load(open(os.path.join(HERE, "narrative_ko.json"), encoding="utf-8"))
|
||||
|
||||
# ─────────────────────────── 렌더 ───────────────────────────
|
||||
CSS = """
|
||||
:root{--ink:#0A1128;--navy:#021341;--violet:#4F1DA1;--accent:#6C5CE7;--indigo:#1D0024;--light:#F7F8FC;--line:#E4E6F0;--slate6:#475569;--slate5:#64748B;
|
||||
--good-bg:#ECFDF5;--good-tx:#047857;--good-bd:#A7F3D0;--info-bg:#EEF2FF;--info-tx:#3730A3;--info-bd:#C7D2FE;--warn-bg:#FFFBEB;--warn-tx:#B45309;--warn-bd:#FDE68A;--crit-bg:#FEF2F2;--crit-tx:#B91C1C;--crit-bd:#FECACA}
|
||||
*{box-sizing:border-box}html{-webkit-print-color-adjust:exact;print-color-adjust:exact}
|
||||
body{margin:0;font-family:"Pretendard Variable",Pretendard,"Apple SD Gothic Neo","Noto Sans KR",sans-serif;color:var(--indigo);background:#fff;font-size:15px;line-height:1.65;word-break:keep-all}
|
||||
.page{max-width:1080px;margin:0 auto;padding:0 40px 60px}
|
||||
.serif{font-family:"Playfair Display",Georgia,serif}
|
||||
.cover{background:linear-gradient(135deg,var(--ink) 0%,var(--navy) 55%,#2A1466 100%);color:#fff;padding:64px 56px 52px;margin:0 -40px 36px;position:relative;overflow:hidden}
|
||||
.cover:after{content:"";position:absolute;right:-120px;top:-120px;width:420px;height:420px;border-radius:50%;background:radial-gradient(circle,rgba(108,92,231,.45),transparent 70%)}
|
||||
.eyebrow{font-family:Inter,Pretendard,sans-serif;font-size:12px;letter-spacing:.2em;text-transform:uppercase;color:#C4B5FD;font-weight:600}
|
||||
.cover h1{font-size:38px;line-height:1.25;margin:14px 0 8px;font-weight:800}
|
||||
.cover .sub{font-size:19px;color:#DDD6FE;margin:0 0 22px}
|
||||
.cover .meta{font-size:14px;color:#C4B5FD;display:flex;gap:22px;flex-wrap:wrap}
|
||||
.cover .meta b{color:#fff;font-weight:600}
|
||||
h2{font-family:"Playfair Display",Georgia,serif;font-size:28px;color:var(--indigo);margin:44px 0 6px;font-weight:700;display:flex;align-items:center;gap:12px}
|
||||
h2 .n{font-size:14px;font-family:Inter,Pretendard,sans-serif;letter-spacing:.15em;color:var(--accent);font-weight:700}
|
||||
h2 + .lead{font-size:17px;color:var(--violet);margin:0 0 18px;font-weight:500}
|
||||
h3{font-size:20px;margin:28px 0 10px;color:var(--indigo)}
|
||||
h4{font-size:16px;margin:18px 0 8px;color:var(--indigo)}
|
||||
p{margin:0 0 12px}
|
||||
.kpis{display:grid;grid-template-columns:repeat(5,1fr);gap:12px;margin:8px 0 22px}
|
||||
.kpi{background:var(--light);border:1px solid var(--line);border-radius:12px;padding:16px 16px 14px}
|
||||
.kpi .lab{font-size:13px;font-weight:600;color:var(--slate6)}
|
||||
.kpi .val{font-family:"Playfair Display",Georgia,serif;font-size:40px;line-height:1.1;margin:6px 0 4px;color:var(--indigo);display:flex;align-items:baseline;gap:8px}
|
||||
.kpi .sub{font-size:12.5px;color:var(--slate6);line-height:1.45}
|
||||
.grade{font-family:Inter,Pretendard,sans-serif;font-size:15px;font-weight:800;border-radius:999px;padding:2px 10px}
|
||||
.good{background:var(--good-bg);color:var(--good-tx);border:1px solid var(--good-bd)}.info{background:var(--info-bg);color:var(--info-tx);border:1px solid var(--info-bd)}
|
||||
.warn{background:var(--warn-bg);color:var(--warn-tx);border:1px solid var(--warn-bd)}.crit{background:var(--crit-bg);color:var(--crit-tx);border:1px solid var(--crit-bd)}
|
||||
.unv{background:#F1F5F9;color:var(--slate5);border:1px solid var(--line)}
|
||||
.badge{display:inline-block;font-family:Inter,Pretendard,sans-serif;font-size:15px;font-weight:800;border-radius:8px;padding:1px 10px;min-width:34px;text-align:center}
|
||||
.badge.unv{font-size:12px;font-weight:600}
|
||||
table.t{width:100%;border-collapse:collapse;font-size:14px;margin:8px 0 18px;page-break-inside:auto}
|
||||
table.t th{background:var(--light);text-align:left;font-weight:700;padding:9px 10px;border-bottom:2px solid var(--line);font-size:13.5px;color:var(--indigo);vertical-align:bottom}
|
||||
table.t th small{font-weight:500;color:var(--slate6)}
|
||||
table.t td{padding:9px 10px;border-bottom:1px solid var(--line);vertical-align:top}
|
||||
table.t tr{page-break-inside:avoid}
|
||||
table.t td.num{text-align:center;white-space:nowrap}
|
||||
table.t .mono{font-family:Inter,Menlo,monospace;font-size:12.5px;color:var(--slate6)}
|
||||
table.t tr.hl td{background:#F3F0FF;font-weight:600}
|
||||
table.t tr.sum td{background:var(--light)}
|
||||
table.t tr.grp td{background:#EEF2FF;color:var(--violet);font-size:14px}
|
||||
td .cat{display:block;font-size:12.5px;color:var(--slate5);font-weight:400;line-height:1.4;margin-top:2px}
|
||||
.o{color:var(--good-tx);font-weight:800}.x{color:var(--slate5)}
|
||||
.findings{display:grid;grid-template-columns:1fr 1fr;gap:14px;margin:6px 0 10px}
|
||||
.card{background:#fff;border:1px solid var(--line);border-radius:12px;padding:16px 18px;page-break-inside:avoid}
|
||||
.card h4{margin:0 0 6px;font-size:16px}
|
||||
.card p{font-size:14.5px;margin:0}
|
||||
.card .big{font-size:26px;font-family:"Playfair Display",Georgia,serif;margin:2px 0}
|
||||
.card .big small{font-size:13px;font-family:Pretendard,sans-serif;color:var(--slate6)}
|
||||
.card .sub{font-size:13px;color:var(--slate6);margin-top:4px}
|
||||
.card.dark{background:var(--navy);color:#fff;border:0}.card.dark h4{color:#C4B5FD}.card.dark p{color:#fff}
|
||||
.num-badge{display:inline-block;width:26px;height:26px;border-radius:50%;background:var(--accent);color:#fff;font-weight:800;text-align:center;line-height:26px;font-size:13px;margin-right:8px;font-family:Inter,Pretendard,sans-serif}
|
||||
.grid3{display:grid;grid-template-columns:repeat(3,1fr);gap:12px;margin:6px 0 14px}
|
||||
.prio{font-size:19px;font-weight:700;color:var(--indigo);margin:14px 0 8px}
|
||||
ol.actions{padding-left:0;list-style:none;margin:0}
|
||||
ol.actions li{padding:10px 0;border-bottom:1px solid var(--line);font-size:15px;display:flex;gap:10px;align-items:flex-start}
|
||||
.quotes{display:grid;grid-template-columns:1fr 1fr;gap:12px;margin:6px 0 16px}
|
||||
.quote{background:var(--light);border:1px solid var(--line);border-radius:12px;padding:14px 16px;page-break-inside:avoid}
|
||||
.quote .qh{font-size:14px;margin-bottom:6px;color:var(--indigo)}
|
||||
.quote .eng{display:inline-block;font-size:11.5px;background:#fff;border:1px solid var(--line);border-radius:999px;padding:0 8px;margin-left:6px;color:var(--slate6);font-family:Inter,Pretendard,sans-serif}
|
||||
.quote p{font-size:13.5px;color:var(--slate6);margin:0 0 6px;line-height:1.55}
|
||||
.quote .cite{font-size:12px;color:var(--slate5);font-family:Inter,Pretendard,sans-serif}
|
||||
h4.v1cat{font-family:"Playfair Display",Georgia,serif;font-size:20px;margin:22px 0 6px;color:var(--indigo)}
|
||||
h4.phase{font-size:17px;padding:8px 14px;border-radius:8px;margin:22px 0 6px}
|
||||
h4.phase.crit{background:var(--crit-bg);color:var(--crit-tx)}h4.phase.warn{background:var(--warn-bg);color:var(--warn-tx)}h4.phase.info{background:var(--info-bg);color:var(--info-tx)}
|
||||
.note{background:var(--light);border-left:4px solid var(--accent);padding:12px 16px;border-radius:0 10px 10px 0;font-size:14px;margin:12px 0 18px}
|
||||
.note b{color:var(--violet)}
|
||||
.footer{margin-top:40px;padding-top:16px;border-top:1px solid var(--line);font-size:12.5px;color:var(--slate5)}
|
||||
.pb{page-break-before:always}
|
||||
@page{size:A4;margin:12mm 11mm 14mm}
|
||||
@media print{body{font-size:13.5px}.page{max-width:none;padding:0}.cover{margin:0 0 24px;padding:44px 40px 36px}.cover h1{font-size:32px}h2{font-size:24px;margin-top:26px}
|
||||
.kpis{grid-template-columns:repeat(5,1fr);gap:8px}.kpi .val{font-size:30px}table.t{font-size:12px}table.t th{font-size:12px}
|
||||
.findings,.quotes{grid-template-columns:1fr 1fr}.grid3{grid-template-columns:repeat(3,1fr)}}
|
||||
"""
|
||||
|
||||
def render():
|
||||
pp, oa = stats["perplexity"], stats["openai"]
|
||||
findings = "".join(f'<div class="card"><h4><span class="num-badge">{i}</span>{esc(t)}</h4><p>{esc(b)}</p></div>' for i, (t, b) in enumerate(NARR["findings"], 1))
|
||||
actions = "".join(f'<li><span class="num-badge">{i}</span><span>{esc(a)}</span></li>' for i, a in enumerate(NARR["top_actions"], 1))
|
||||
method_rows = "".join(f"<tr><td><b>{esc(a)}</b></td><td>{esc(b)}</td></tr>" for a, b in NARR["method"])
|
||||
limits = "".join(f"<li>{esc(x)}</li>" for x in NARR["limits"])
|
||||
aux_rows = "".join(f"<tr><td><b>{esc(a)}</b></td><td>{lvl_badge(l)}</td><td>{esc(b)}</td></tr>" for a, l, b in NARR["aux"])
|
||||
kpi_rows = "".join(f"<tr><td><b>{esc(a)}</b></td><td>{esc(b)}</td><td>{esc(c)}</td><td>{esc(d)}</td></tr>" for a, b, c, d in NARR["kpi_plan"])
|
||||
|
||||
body = f"""
|
||||
<div class="page">
|
||||
<div class="cover">
|
||||
<div class="eyebrow">INFINITH AI Discovery · Global Patient Edition</div>
|
||||
<h1>뷰성형외과 영문 사이트<br>AEO · GEO 진단 보고서</h1>
|
||||
<p class="sub">외국인 환자 관점 질문 60문항 실측과 답변·출처 준비도 채점</p>
|
||||
<div class="meta"><span>진단일 <b>{DATE}</b></span><span>대상 <b>{SITE}</b> (en-US · x-default)</span><span>기준표 <b>AEO/GEO v2.0</b> (v1.0 36항목 병행)</span><span>답변엔진 <b>Perplexity sonar · OpenAI gpt-4o web_search (뉴욕)</b></span><span>문의 <b>o2oteam@o2o.kr</b></span></div>
|
||||
</div>
|
||||
|
||||
<h2><span class="n">01</span>요약 판정</h2>
|
||||
<p class="lead">{esc(NARR["lead"])}</p>
|
||||
{kpi_row()}
|
||||
<div class="findings">{findings}</div>
|
||||
<div class="prio">먼저 할 일 5가지</div>
|
||||
<ol class="actions">{actions}</ol>
|
||||
|
||||
<h2 class="pb"><span class="n">02</span>진단 범위와 방법</h2>
|
||||
<p class="lead">국문 사이트 진단(2026-08-31 · 09-07)과 같은 기준표를 영문 사이트와 외국인 질문에 적용했습니다.</p>
|
||||
<table class="t"><tbody>{method_rows}</tbody></table>
|
||||
<h4>이번 측정의 한계</h4><ul>{limits}</ul>
|
||||
|
||||
<h2 class="pb"><span class="n">03</span>외국인 질의 실측 결과</h2>
|
||||
<p class="lead">{esc(NARR["qb_lead"])}</p>
|
||||
<h3>3.1 질문군별 언급률</h3>
|
||||
<p>셀 값은 순서대로 뷰성형외과 언급 비율, 답변에서 첫 번째로 등장한 비율, 답변이 viewplasticsurgery.com 페이지를 인용한 비율입니다.</p>
|
||||
{cat_table()}
|
||||
<h3>3.2 답변에 등장한 병원</h3>
|
||||
<p>{esc(NARR["clinic_text"])}</p>
|
||||
{clinic_table()}
|
||||
<h3>3.3 AI가 근거로 쓴 표면</h3>
|
||||
<p>{esc(NARR["domain_text"])}</p>
|
||||
{domain_table()}
|
||||
<h4>인용된 자사 페이지</h4>
|
||||
{own_url_table()}
|
||||
<h3>3.4 답변 감성</h3>
|
||||
{sentiment_block()}
|
||||
<h3>3.5 답변 사례</h3>
|
||||
{quote_cards(NARR["quote_ids"])}
|
||||
|
||||
<h2 class="pb"><span class="n">04</span>AEO 답변 준비도 <span class="grade {grade_cls(aeo['grade'])}" style="font-size:18px">{aeo['score']} · {aeo['grade']}</span></h2>
|
||||
<p class="lead">고객의 질문에 얼마나 명확하고 충분하게 답하는가. 8항목 100점.</p>
|
||||
{v2_table(aeo)}
|
||||
<div class="note">{esc(NARR["aeo_note"])}</div>
|
||||
|
||||
<h2 class="pb"><span class="n">05</span>GEO 출처 준비도 <span class="grade {grade_cls(geo['grade'])}" style="font-size:18px">{geo['score']} · {geo['grade']}</span></h2>
|
||||
<p class="lead">그 답을 얼마나 검증하고 신뢰할 수 있는 출처로 사용할 수 있는가. 8항목 100점.</p>
|
||||
{v2_table(geo)}
|
||||
<div class="note">{esc(NARR["geo_note"])}</div>
|
||||
|
||||
<h2 class="pb"><span class="n">06</span>게이트와 부가 지표</h2>
|
||||
<p class="lead">게이트 G1(AI 크롤러 접근) · G2(본문 렌더링) 모두 통과. 상한 적용 없음.</p>
|
||||
<table class="t"><thead><tr><th>지표</th><th>레벨</th><th>실측</th></tr></thead><tbody>{aux_rows}</tbody></table>
|
||||
|
||||
<h2 class="pb"><span class="n">07</span>국문 사이트와의 비교</h2>
|
||||
<p class="lead">{esc(NARR["compare_lead"])}</p>
|
||||
<div class="kpis" style="grid-template-columns:repeat(3,1fr)">
|
||||
<div class="kpi"><div class="lab">v1.0 종합</div><div class="val">{KR_BASE['v1']} <small style="font-size:18px;color:#64748B">→</small> {v1['score']}</div><div class="sub">국문 C → 영문 {v1['grade']}</div></div>
|
||||
<div class="kpi"><div class="lab">AEO 답변 준비도</div><div class="val">{KR_BASE['aeo']} <small style="font-size:18px;color:#64748B">→</small> {aeo['score']}</div><div class="sub">국문 C → 영문 {aeo['grade']}</div></div>
|
||||
<div class="kpi"><div class="lab">GEO 출처 준비도</div><div class="val">{KR_BASE['geo']} <small style="font-size:18px;color:#64748B">→</small> {geo['score']}</div><div class="sub">국문 C → 영문 {geo['grade']}</div></div>
|
||||
</div>
|
||||
{compare_table()}
|
||||
|
||||
<h2 class="pb"><span class="n">08</span>개선 로드맵</h2>
|
||||
<p class="lead">{esc(NARR["roadmap_lead"])}</p>
|
||||
{roadmap()}
|
||||
|
||||
<h2><span class="n">09</span>측정 계획과 KPI</h2>
|
||||
<p class="lead">개선 전후를 같은 60문항 · 같은 두 엔진으로 월 1회 재실측합니다.</p>
|
||||
<table class="t"><thead><tr><th>지표</th><th>현재 (2026-09-10)</th><th>90일 목표</th><th>측정 방법</th></tr></thead><tbody>{kpi_rows}</tbody></table>
|
||||
|
||||
<h2 class="pb"><span class="n">A</span>부록 A · 60문항 실측표</h2>
|
||||
<p class="lead">O = 뷰성형외과 언급, 숫자 = 답변 내 등장 순위. 감성은 후처리 모델 1차 분류.</p>
|
||||
{appendix_questions()}
|
||||
|
||||
<h2 class="pb"><span class="n">B</span>부록 B · v1.0 36항목 판정</h2>
|
||||
<p class="lead">자동 채점 20항목 + 사람 보정 10항목 + 미검증 6항목. 미검증은 분모에서 제외합니다.</p>
|
||||
{v1_table()}
|
||||
|
||||
<div class="footer">INFINITH AI Discovery · 뷰성형외과 영문 사이트 진단 · {DATE} · 이 점수는 INFINITH 제품용 진단 기준이며 검색엔진의 공식 점수나 인용 확률이 아닙니다 · 문의 o2oteam@o2o.kr</div>
|
||||
</div>"""
|
||||
head = f"""<!DOCTYPE html><html lang="ko"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>뷰성형외과 영문 사이트 AEO · GEO 진단 보고서 {DATE}</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@400;600;700;800&family=Playfair+Display:wght@400;700;800&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></head><body>"""
|
||||
return head + body + "</body></html>"
|
||||
|
||||
|
||||
def to_pdf(html_path, pdf_path):
|
||||
from playwright.sync_api import sync_playwright
|
||||
with sync_playwright() as p:
|
||||
b = p.chromium.launch()
|
||||
pg = b.new_page()
|
||||
pg.goto("file://" + html_path, wait_until="networkidle")
|
||||
pg.wait_for_timeout(1200)
|
||||
pg.emulate_media(media="print")
|
||||
pg.pdf(path=pdf_path, format="A4", print_background=True, prefer_css_page_size=True)
|
||||
b.close()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
out_html = OUT_BASE + ".html"
|
||||
open(out_html, "w", encoding="utf-8").write(render())
|
||||
print("HTML:", out_html)
|
||||
if "--no-pdf" not in sys.argv:
|
||||
to_pdf(out_html, OUT_BASE + ".pdf")
|
||||
print("PDF:", OUT_BASE + ".pdf")
|
||||
@ -1,421 +0,0 @@
|
||||
{
|
||||
"lead": "영문 사이트는 국문 사이트보다 답변·출처 준비도가 모두 높습니다(AEO 65 · GEO 72, 둘 다 B). 외국인이 병원 이름을 알고 묻는 질문에는 두 엔진 모두 100% 답하지만, 병원을 모르는 상태의 시술·가격 질문에서는 언급률이 0~21%에 그칩니다. 원인은 콘텐츠 부족이 아니라 인용된 문단에 상호가 없어서입니다.",
|
||||
"findings": [
|
||||
[
|
||||
"이름을 알면 100%, 모르면 0~21%",
|
||||
"브랜드(A)·이동·체류(B) 질문군은 두 엔진 모두 뷰성형외과를 1순위로 답하고 viewplasticsurgery.com을 직접 인용합니다. 반면 시술 정보(D)는 Perplexity 14% · OpenAI 21%, 가격(E)은 두 엔진 모두 0%입니다. 외국인 환자 여정에서 가장 긴 구간이 비어 있습니다."
|
||||
],
|
||||
[
|
||||
"인용은 되는데 이름이 안 나오는 문항 8개",
|
||||
"Perplexity는 가격 질문(E-01 가슴성형 비용, E-02 V-line 비용)과 시술 질문(D-01·D-04·D-05·D-10)에서 뷰 블로그·시술 페이지를 출처로 썼지만 답변에 'VIEW'가 한 번도 등장하지 않았습니다. 인용된 문단이 'The cost of V-line surgery in Seoul ranges…'처럼 상호 없이 쓰여 있어 AI가 수치만 가져가고 병원 이름은 남기지 않습니다."
|
||||
],
|
||||
[
|
||||
"추천 질문의 경쟁 구도: JK · ID · Banobagi",
|
||||
"'Best plastic surgery clinic in Seoul for foreigners' 류 추천 질문(C)에서 뷰는 Perplexity 9/12, OpenAI 6/12에 등장했지만 1순위는 드뭅니다. JK Plastic Surgery(KAHF 인증 4회 연속), ID Hospital, Banobagi가 상위를 차지하고, 그 근거는 유치 에이전시·중개 플랫폼의 영문 가이드 글입니다."
|
||||
],
|
||||
[
|
||||
"AI가 근거로 쓰는 표면은 Reddit · Bookimed · 에이전시 가이드",
|
||||
"두 엔진 합산 인용 도메인 1위는 viewplasticsurgery.com(54문항), 2위는 reddit.com(49문항)입니다. 그다음이 us-uk.bookimed.com · clinicsoncall.com · koreaclinicguide.com · clinicseoul.net · unni.app 같은 중개·가이드 표면입니다. 강남언니 국문 페이지는 60문항 어디에도 인용되지 않았습니다."
|
||||
],
|
||||
[
|
||||
"엔티티 스키마 0, hreflang은 리다이렉트 URL",
|
||||
"홈 JSON-LD가 병원을 Person 타입으로 선언하고 MedicalClinic·Physician 스키마와 sameAs가 없습니다(GEO4 6.3/15). 10개 언어 hreflang은 갖췄지만 8개 언어 서브도메인이 경로 URL로 301 하고, origin.·olden. 서브도메인이 전체 사이트를 색인 가능 상태로 서빙해 AI 인용 URL이 분산됩니다."
|
||||
],
|
||||
[
|
||||
"부정 답변 1건: 공정위 후기 광고 시정명령",
|
||||
"'Are there red flags about VIEW Plastic Surgery'(F-05) 질문에 Perplexity는 국문 사이트 후기 광고에 대한 2026-07 공정위 시정명령을 첫 문장으로 답했습니다. 영문 Real Review 섹션도 같은 구조(대가 고지 없음)라 GEO7 발행 투명성 항목의 감점 요인입니다."
|
||||
]
|
||||
],
|
||||
"top_actions": [
|
||||
"시술 페이지·블로그 가격 가이드의 첫 문단과 표 캡션에 'At VIEW Plastic Surgery in Gangnam, Seoul' 형태로 상호·지역을 넣습니다. 인용은 되는데 이름이 안 나오는 8문항이 바로 대상입니다.",
|
||||
"MedicalClinic + Physician JSON-LD를 추가하고 Person 타입을 Organization/MedicalClinic으로 바꾸며 sameAs에 Instagram·TikTok·YouTube·Google 프로필·Bookimed·RealSelf·unni.app을 연결합니다.",
|
||||
"시술 페이지 상단에 결정 정보 블록(USD 가격대 · 수술 시간 · 입원 · 권장 체류일 · 귀국 가능일)을 표로 두고 블로그 가격 가이드와 상호 링크합니다. 가격(E)·시술(D) 질문군 0~21%의 직접 대응입니다.",
|
||||
"FAQ 402문항에 FAQPage 스키마를 붙이고 카테고리별 URL로 나눕니다. 전 페이지 동일 H1('korea plastic surgery')을 페이지별 서술형 H1으로 교체합니다.",
|
||||
"origin.·olden. 서브도메인을 www로 301 하고 언어 서브도메인 hreflang을 최종 URL로 통일합니다. 후기·전후 섹션에 대가 제공 여부와 개인차 고지를 넣어 정보 페이지와 분리합니다."
|
||||
],
|
||||
"method": [
|
||||
[
|
||||
"진단 대상",
|
||||
"https://www.viewplasticsurgery.com (en-US, x-default). 홈 · About · 의료진 · 컨시어지 · 오시는길 · 안전 · 수상 · FAQ · 가슴(Motiva) · 코 · 윤곽 · 블로그 목록 · 블로그 글 1건 등 13개 페이지를 JS 미실행 HTML로 수집"
|
||||
],
|
||||
[
|
||||
"자동 채점",
|
||||
"scripts/audit_aeo_geo.py (기준표 v1.0 36항목 중 자동 20항목). AI 봇 12종 UA 접근, sitemap, JSON-LD, hreflang, H1/H2, FAQ, 표, alt, TTFB 실측"
|
||||
],
|
||||
[
|
||||
"사람 판정",
|
||||
"C1 정의문 · C4 수치 · C7 전문성 · C8 자기완결성 · B4/B7 · D1~D4 · E4 · F2 (10항목) + v2.0 사람 판정 4항목(AEO6 · GEO1 · GEO6 · GEO7). 근거는 각 표에 기재"
|
||||
],
|
||||
[
|
||||
"질문 뱅크",
|
||||
"외국인 환자 관점 영문 60문항 · 7군 (브랜드 8 · 이동·체류 8 · 추천·비교 12 · 시술 14 · 가격 8 · 안전·신뢰 6 · 사후관리 4). 국문 120문항 뱅크와 같은 퍼널 구조"
|
||||
],
|
||||
[
|
||||
"답변엔진",
|
||||
"Perplexity API sonar (자체 웹검색) · OpenAI Responses API gpt-4o + web_search (사용자 위치 미국 뉴욕). 소비자 ChatGPT UI 자동 조회는 약관 위반이라 쓰지 않음. 각 문항 1회 질의, 2026-09-10"
|
||||
],
|
||||
[
|
||||
"후처리",
|
||||
"답변에서 병원명 최대 8개를 등장 순서로 추출(gpt-4o-mini). VIEW Plastic Surgery · View Clinic · VIEW Seoul 등 표기 변형을 같은 병원으로 정규화. 감성은 긍정·중립·부정 1차 분류"
|
||||
],
|
||||
[
|
||||
"점수 산식",
|
||||
"v2.0: 항목 점수 = 파생 신호(v1.0 레벨 0~3) 가중 평균 × 배점, 사람 판정 우선, 미검증은 분모 제외. 게이트 실패 시 상한 40. 등급 A 81+ · B 61–80 · C 41–60 · D 40 이하"
|
||||
]
|
||||
],
|
||||
"limits": [
|
||||
"질의는 문항당 1회입니다. 답변엔진은 같은 질문에도 매번 다른 병원을 고를 수 있어 5회 반복 평균이 정식 기준선입니다.",
|
||||
"OpenAI gpt-4o는 60문항 중 23문항에서 웹검색을 실행하지 않고 모델 기억으로 답했습니다(시술·가격 일반 질문 대부분). 이 문항의 미언급은 사이트 문제가 아니라 모델 기억에 뷰가 없다는 뜻입니다.",
|
||||
"Google 비즈니스 프로필은 2026-08-31 실측값(4.9점 · 리뷰 901건)을 인용했습니다. 이번에는 Places API 키가 없어 재실측하지 못했습니다.",
|
||||
"Bing 색인(A6), 서버 로그(E1), GA4(E2), GSC(E3)는 소유자 계정이 필요해 미검증입니다.",
|
||||
"감성 분류는 후처리 모델의 1차 판정이며 감성 기준표 v0.3의 사람 검토 전 값입니다."
|
||||
],
|
||||
"qb_lead": "Perplexity는 60문항 중 30문항(50%), OpenAI 웹검색은 27문항(45%)에서 뷰성형외과를 언급했습니다. 국문 120문항 실측(2026-09-01, Perplexity 32% · OpenAI 37%)보다 높지만, 상승분은 전부 병원 이름이 들어간 질문에서 나왔습니다.",
|
||||
"cat_interp": {
|
||||
"A": "두 엔진 모두 전 문항 1순위. 자사 사이트·에이전시 블로그·정부 의료관광 페이지가 근거",
|
||||
"B": "공항 픽업·숙박·WhatsApp 상담은 컨시어지 페이지가 직접 인용됨. 미언급 2문항은 비자·체류기간 일반 질문",
|
||||
"C": "Perplexity는 9/12 언급하나 1순위는 4건. OpenAI는 절반. JK·ID·Banobagi가 상위, 근거는 제3자 가이드 글",
|
||||
"D": "병원 이름이 들어간 D-11·D-12만 언급. 나머지 12문항은 뷰 페이지가 인용돼도 이름이 안 나옴",
|
||||
"E": "두 엔진 모두 0. Perplexity는 뷰 블로그 가격 가이드를 2문항에서 인용했으나 상호 없이 수치만 사용. OpenAI는 가격 질문 대부분을 검색 없이 답변",
|
||||
"F": "마취과 전문의(F-02)·보증(F-06)은 언급. 안전 일반론(F-01·F-03·F-04)은 미언급. F-05는 공정위 시정명령이 첫 문장",
|
||||
"G": "병원 이름이 있는 G-02·G-04만 언급. 원격 팔로업 일반 질문(G-01)에서 Perplexity 4순위"
|
||||
},
|
||||
"clinic_text": "두 엔진을 합쳐 뷰성형외과는 57문항에서 언급됐고 그중 40문항이 1순위입니다. 그러나 1순위는 대부분 브랜드·체류 질문입니다. 병원을 고르는 추천·비교 질문에서는 JK Plastic Surgery, ID Hospital, Banobagi, AB Plastic Surgery, SAERO가 반복 등장합니다.",
|
||||
"domain_text": "인용 도메인은 세 층으로 나뉩니다. 자사 사이트(viewplasticsurgery.com 54문항), 커뮤니티(reddit.com 49문항), 그리고 중개·가이드 표면(Bookimed · clinicsoncall · koreaclinicguide · clinicseoul.net · unni.app · khunkim.com)입니다. 강남언니 국문 페이지와 네이버는 한 번도 인용되지 않았습니다. 외국인 대상 GEO는 이 세 층에 원문을 두는 일입니다.",
|
||||
"quote_ids": [
|
||||
[
|
||||
"E-02",
|
||||
"perplexity"
|
||||
],
|
||||
[
|
||||
"C-01",
|
||||
"perplexity"
|
||||
],
|
||||
[
|
||||
"C-02",
|
||||
"openai"
|
||||
],
|
||||
[
|
||||
"F-05",
|
||||
"perplexity"
|
||||
],
|
||||
[
|
||||
"D-11",
|
||||
"openai"
|
||||
],
|
||||
[
|
||||
"E-05",
|
||||
"openai"
|
||||
]
|
||||
],
|
||||
"aeo_note": "AEO 65점의 감점은 AEO2 답변의 직접성(6.7/20)과 AEO4 의사결정 정보(9/15)에 집중됩니다. 정의문이 메타 설명에만 있고 본문은 슬로건으로 시작하며, 시술 페이지에 가격·시간·회복 수치가 없습니다. 이 두 항목이 그대로 시술(D)·가격(E) 질문군의 0~21%로 나타납니다.",
|
||||
"geo_note": "GEO 72점의 감점은 GEO4 엔티티 정합성(6.3/15)과 GEO3 전문성 스키마(9/15)입니다. 의료진 자격·논문은 텍스트로 충분하지만 Physician 스키마가 없고, 병원 자체가 Person 타입으로 선언돼 있습니다. GEO7 투명성(6.7/10)은 후기·전후 섹션의 대가 고지 부재가 원인이며 F-05 답변에서 실제 감점으로 확인됩니다.",
|
||||
"v2_evidence": {
|
||||
"AEO1": "FAQ 페이지에 402문항이 서버 렌더링(카테고리 11개). 시술 페이지 하단에도 FAQ 5문항. FAQPage 스키마는 블로그 글에만 있고 FAQ 페이지에는 없음. 질문 뱅크 대비 A·B군은 사이트가 직접 답하고, D·E군 답은 블로그에 있으나 상호가 없음",
|
||||
"AEO2": "정의문이 About 메타 설명에만 있음('Founded in 2005, VIEW is a premier 19-story medical center in Gangnam, Seoul'). 본문 첫 단락은 슬로건, 시술 페이지 첫 텍스트는 'Let Me In Season 2/3/4/5 Selected Doctors' 홍보 라벨, 홈 첫 200자는 검색창·메뉴",
|
||||
"AEO3": "5개 핵심 페이지 단락 샘플 50개 중 상호 또는 시술명 포함 26개(52%). 상호 'VIEW' 포함은 7개(14%). FAQ 답변과 블로그 가격 문단은 시술명만 있고 상호가 없어 인용돼도 병원 이름이 남지 않음(실측 8문항)",
|
||||
"AEO4": "시술 페이지 본문에 가격·수술시간·회복기간 0회. 수치는 Motiva 보증(재수술 지원 300만원/개)뿐. HTML 표는 10개 존재. 블로그에는 USD 가격표(V-line $7,000–12,000)와 체류 권장일(10–14일)이 있으나 시술 페이지와 연결되지 않음",
|
||||
"AEO5": "전 페이지 H1이 동일하게 'korea plastic surgery'(키워드 라벨). 페이지 고유 제목은 H2·H3. H2 'What would you like to know today?'(검색창)가 모든 페이지에 반복",
|
||||
"AEO7": "JS 미실행 HTML 본문 79,646자 서버 렌더링. 메인 이미지 500개 alt 누락 0%",
|
||||
"AEO8": "sitemap index 10개 · URL 482개 · lastmod 2026-09-09. canonical self 지정. 다만 origin.·olden. 서브도메인이 전체 사이트를 200·index로 서빙(canonical은 www 지정)해 AI 답변이 origin.·olden. URL을 인용함"
|
||||
},
|
||||
"aux": [
|
||||
[
|
||||
"A6 Bing 색인 (ChatGPT 검색 경로)",
|
||||
"unverified",
|
||||
"Bing 검색 결과 페이지가 스크립트 렌더링이라 curl로 확인 불가. Bing Webmaster Tools 등록 여부는 소유자 확인 필요"
|
||||
],
|
||||
[
|
||||
"A7 응답 속도·크롤 예산",
|
||||
2,
|
||||
"TTFB 0.87s · 초기 HTML 587KB · 시술 페이지 212KB. 속도 양호, HTML 비대"
|
||||
],
|
||||
[
|
||||
"B7 다국어 hreflang",
|
||||
2,
|
||||
"10개 언어(en-US · zh-CN · th · ja · vi · ru · id · mn · es · ar) 양방향 선언. th·jp·vn·ru·id·mn·es·ar 8개 서브도메인은 viewplasticsurgery.com/{lang}/ 로 301. cn은 en-US 대신 en 선언"
|
||||
],
|
||||
[
|
||||
"D3 오픈웹 언급 (해외)",
|
||||
2,
|
||||
"에이전시 블로그(khunkim · clinicsoncall · curemeabroad · kmhglobal), 정부 표면(visitkorea · 강남구 의료관광), Reddit · Purseblog 포럼, LinkedIn에 분산. 영어권 주요 언론 기사·Wikipedia 없음"
|
||||
],
|
||||
[
|
||||
"D5 네이버 플레이스",
|
||||
"unverified",
|
||||
"외국인 질의 60문항에서 네이버 표면 인용 0건. 외국인 관점에서는 채점 대상 아님"
|
||||
],
|
||||
[
|
||||
"E1 AI 크롤러 로그",
|
||||
"unverified",
|
||||
"서버 로그 접근 필요. Perplexity가 60문항 중 38문항에서 자사 URL을 인용했으므로 PerplexityBot 히트는 존재할 것으로 추정"
|
||||
],
|
||||
[
|
||||
"E2 GA4 AI 유입 채널",
|
||||
"unverified",
|
||||
"GA4 접근 필요. chatgpt.com · perplexity.ai · copilot 리퍼러 채널 그룹 설정 여부 미확인"
|
||||
],
|
||||
[
|
||||
"E3 GSC AI Overviews",
|
||||
"unverified",
|
||||
"GSC 접근 필요"
|
||||
],
|
||||
[
|
||||
"E4 기준선 스팟체크",
|
||||
2,
|
||||
"60문항 × 2엔진 × 1회 실측 완료(이 보고서). 5회 반복 미실시"
|
||||
],
|
||||
[
|
||||
"F2 CMS 보안·최신성",
|
||||
2,
|
||||
"WordPress 7.1(최신 계열). meta generator로 버전 노출"
|
||||
],
|
||||
[
|
||||
"F3 페이지 무게",
|
||||
2,
|
||||
"초기 HTML 587KB(300~800KB 구간)"
|
||||
],
|
||||
[
|
||||
"F4 모바일 대응",
|
||||
2,
|
||||
"viewport 존재 · 반응형. Core Web Vitals 미측정"
|
||||
]
|
||||
],
|
||||
"compare_lead": "같은 병원의 두 사이트를 같은 기준표로 채점했습니다. 영문 사이트가 세 점수 모두 한 등급 높고, 질문 뱅크 언급률도 13~18%p 높습니다. 차이는 콘텐츠 양(FAQ 402건 · 블로그 주 1회 이상 발행 · 의료진 논문 목록)에서 나왔고, 남은 감점 원인(정의문 · 수치 · 스키마 · 상호 없는 문단)은 두 사이트가 같습니다.",
|
||||
"compare_rows": [
|
||||
[
|
||||
"v1.0 종합",
|
||||
"54 / C (검증 32/36)",
|
||||
"64 / B (검증 31/36)",
|
||||
"+10"
|
||||
],
|
||||
[
|
||||
"AEO 답변 준비도",
|
||||
"42 / C",
|
||||
"65 / B",
|
||||
"+23"
|
||||
],
|
||||
[
|
||||
"GEO 출처 준비도",
|
||||
"56 / C",
|
||||
"72 / B",
|
||||
"+16"
|
||||
],
|
||||
[
|
||||
"질문 뱅크 언급률",
|
||||
"120문항 · Perplexity 32% · OpenAI 37% (09-01)",
|
||||
"60문항 · Perplexity 50% · OpenAI 45% (09-10)",
|
||||
"+18p / +8p"
|
||||
],
|
||||
[
|
||||
"C1 정의문",
|
||||
"없음 (0)",
|
||||
"메타 설명에만 (1)",
|
||||
"본문 정의문은 둘 다 없음"
|
||||
],
|
||||
[
|
||||
"C2 FAQ",
|
||||
"재검증 후 2",
|
||||
"FAQ 페이지 402문항 · 시술별 5문항 (2)",
|
||||
"영문이 양은 많으나 FAQPage 스키마는 블로그에만"
|
||||
],
|
||||
[
|
||||
"C3 H1",
|
||||
"'VIEW SELFIE' 등 영문 라벨 (0)",
|
||||
"전 페이지 동일 'korea plastic surgery' (2)",
|
||||
"둘 다 페이지별 서술형 H1 없음"
|
||||
],
|
||||
[
|
||||
"C4 · C5 수치와 표",
|
||||
"C4=2 · C5=0",
|
||||
"C4=1 · C5=3",
|
||||
"영문은 수치가 블로그에, 표는 시술 페이지에 따로 있음"
|
||||
],
|
||||
[
|
||||
"C6 신선도",
|
||||
"블로그 2024-05-31 이후 정지 (재검증 후 3)",
|
||||
"블로그 2026-09-09 발행 · sitemap lastmod 당일 (2)",
|
||||
"영문은 갱신 활발, dateModified 마크업은 둘 다 없음"
|
||||
],
|
||||
[
|
||||
"C7 전문성",
|
||||
"2",
|
||||
"학위 · 전문의 · 학회 · 논문 목록 텍스트 (3)",
|
||||
"영문 의료진 상세가 더 충실"
|
||||
],
|
||||
[
|
||||
"B2 · B3 스키마",
|
||||
"B2=1 · B3=0",
|
||||
"B2=0 · B3=0 (Person 타입 오선언)",
|
||||
"둘 다 MedicalClinic · Physician 없음"
|
||||
],
|
||||
[
|
||||
"B7 hreflang",
|
||||
"없음",
|
||||
"10개 언어 (2)",
|
||||
"영문만 다국어 연결, 대상 URL 정리 필요"
|
||||
],
|
||||
[
|
||||
"GEO7 투명성",
|
||||
"3.3 / 10 (공정위 시정명령)",
|
||||
"6.7 / 10",
|
||||
"영문 Real Review도 대가 고지 없음, F-05 답변에 노출"
|
||||
]
|
||||
],
|
||||
"roadmap_lead": "우선순위는 실측에서 확인된 감점 순서입니다. 즉시 조치는 개발 작업 없이 텍스트·스키마 수정으로 끝나고, 30일 조치는 시술 페이지 콘텐츠 블록, 90일 조치는 외부 표면과 측정 인프라입니다.",
|
||||
"roadmap": [
|
||||
[
|
||||
"즉시 (0–2주) · 텍스트와 스키마",
|
||||
"crit",
|
||||
[
|
||||
[
|
||||
"시술 페이지·블로그 가격 가이드의 첫 문단과 표 캡션에 'At VIEW Plastic Surgery in Gangnam, Seoul, …' 형태로 상호·지역 삽입. FAQ 답변 첫 문장에도 'At VIEW' 추가",
|
||||
"AEO3 · C8 / D·E군 8문항",
|
||||
"인용은 되는데 이름이 안 나오는 8문항이 언급으로 전환. 답변 단위 자기완결성 52% → 80%"
|
||||
],
|
||||
[
|
||||
"JSON-LD: Person → MedicalClinic(+Organization)으로 교체, name · address · telephone · openingHours · medicalSpecialty · availableLanguage(10개) 기재. 의료진 26명 Physician 스키마(자격 · 소속 · sameAs)",
|
||||
"GEO3 · GEO4 / B2 · B3 · B5",
|
||||
"엔티티 정합성 6.3 → 12/15. 의료진 질문(A-04 · A-05 · F-02) 근거가 스키마로 확정"
|
||||
],
|
||||
[
|
||||
"sameAs 채우기: Instagram · TikTok · YouTube · Facebook · Google 비즈니스 프로필 · Bookimed · RealSelf · unni.app · LinkedIn",
|
||||
"GEO4 / B5",
|
||||
"AI가 표면 간 동일 병원임을 확인. Reddit · Bookimed 인용이 뷰 엔티티로 연결"
|
||||
],
|
||||
[
|
||||
"FAQ 페이지 402문항에 FAQPage 스키마 부여, 카테고리별 URL(/faq/breast/ 등 11개)로 분할",
|
||||
"AEO1 / C2",
|
||||
"질문 수요 충족 13.3 → 20/20. 시술 질문(D) 인용 페이지 확보"
|
||||
],
|
||||
[
|
||||
"H1을 페이지별 서술형으로 교체('Motiva Breast Augmentation in Seoul at VIEW Plastic Surgery'). 검색창 H2 제거",
|
||||
"AEO5 / C3",
|
||||
"제목 계층 6.7 → 10/10"
|
||||
],
|
||||
[
|
||||
"origin.·olden. 서브도메인 → www 301. 언어 서브도메인 8개의 hreflang을 최종 URL(/th/ 등)로 통일, cn hreflang en-US로 정정",
|
||||
"AEO8 · B7",
|
||||
"인용 URL 분산 해소, 다국어 신호 정합"
|
||||
],
|
||||
[
|
||||
"메타 설명 오류 정정: 가슴 페이지 'No.1 Motiva usage in 2013' → 2023. 제목의 'Best' · 'No.1 Trusted' 표현은 근거 있는 표현으로 교체",
|
||||
"GEO1",
|
||||
"주장·근거 일치 13.3 → 16/20"
|
||||
]
|
||||
]
|
||||
],
|
||||
[
|
||||
"30일 · 시술 페이지 결정 정보",
|
||||
"warn",
|
||||
[
|
||||
[
|
||||
"시술 페이지 상단에 정의문 + 결정 정보 블록: USD 가격대 · 수술 시간 · 마취 · 입원 · 권장 체류일 · 귀국 가능일 · 실밥 제거일을 표로. 부위별 8개 시술군 우선",
|
||||
"AEO2 · AEO4 / C1 · C4 / D·E군",
|
||||
"시술(D) 14~21% · 가격(E) 0% 구간의 직접 대응. 답변 직접성 6.7 → 16/20"
|
||||
],
|
||||
[
|
||||
"블로그 가격 가이드(가슴 · V-line · 코 · 양악 · 눈)와 시술 페이지 상호 링크. 표는 시술 페이지에 요약 이식",
|
||||
"AEO4 / C4 · C5",
|
||||
"의사결정 정보 9 → 13/15"
|
||||
],
|
||||
[
|
||||
"'귀국 후 합병증 대응 · 원격 팔로업' 전용 페이지: 절차 · 연락 채널 · 응답 시간 · 현지 협력 · Motiva 보증 청구 방법",
|
||||
"F-03 · G-01 · G-04",
|
||||
"안전(F) · 사후관리(G) 질문군에서 인용 원문 확보"
|
||||
],
|
||||
[
|
||||
"수상·인증 페이지에 주최 · 연도 · 원문 링크 명시(법무부 우수 유치기관 2018–2026, Motiva KOL, 서울시 표창)",
|
||||
"GEO1 / C-10",
|
||||
"정부 인증 질문(C-10)에서 1순위 유지, 주장 근거 일치 향상"
|
||||
],
|
||||
[
|
||||
"후기 · 전후 · 'Let Me In' 홍보를 정보 페이지와 분리. 후기에 대가 제공 여부 · 개인차 고지 첫 부분에 표기",
|
||||
"GEO7",
|
||||
"F-05 부정 답변의 맥락 제공. 발행 투명성 6.7 → 10/10"
|
||||
],
|
||||
[
|
||||
"dateModified · author · reviewedBy를 시술 페이지와 블로그 글에 마크업. 의료진 검토 표기",
|
||||
"GEO5 · GEO8",
|
||||
"최신성 6.7 → 10/10"
|
||||
]
|
||||
]
|
||||
],
|
||||
[
|
||||
"90일 · 외부 표면과 측정",
|
||||
"info",
|
||||
[
|
||||
[
|
||||
"Google 비즈니스 프로필: 전화번호 등록, 영문 리뷰 응답, 컨시어지 서비스 속성 기재",
|
||||
"D2 · D4",
|
||||
"위치·연락처 질문(B) 오답 위험 제거"
|
||||
],
|
||||
[
|
||||
"Bookimed · RealSelf · unni.app 프로필 갱신(의료진 · 시술 · 가격대 · 언어). Reddit r/PlasticSurgery · r/korea 질문에 공식 계정으로 사실 답변",
|
||||
"D1 · D3",
|
||||
"reddit.com 49문항 · Bookimed 32문항 인용 표면에 뷰 원문 배치. 추천 질문(C) 1순위 비율 상승"
|
||||
],
|
||||
[
|
||||
"Bing Webmaster Tools 등록 + IndexNow. GSC · GA4 AI 유입 채널 그룹 설정, 서버 로그 AI 크롤러 IP 검증",
|
||||
"A6 · E1 · E2 · E3",
|
||||
"ChatGPT 검색 경로 확보, 소유 신호로 월간 측정"
|
||||
],
|
||||
[
|
||||
"60문항 × 2엔진 × 5회 반복 월간 재실측. 질문 뱅크에 일본어 · 중국어 · 태국어 각 60문항 추가(hreflang 언어 순)",
|
||||
"E4",
|
||||
"개선 전후 비교 기준선. 언어별 언급률 관리"
|
||||
]
|
||||
]
|
||||
]
|
||||
],
|
||||
"kpi_plan": [
|
||||
[
|
||||
"60문항 언급률",
|
||||
"Perplexity 50% · OpenAI 45%",
|
||||
"Perplexity 65% · OpenAI 60%",
|
||||
"월 1회 60문항 × 2엔진 × 5회 반복"
|
||||
],
|
||||
[
|
||||
"시술(D) · 가격(E) 언급률",
|
||||
"D 14~21% · E 0%",
|
||||
"D 40% · E 30%",
|
||||
"같은 실측의 질문군별 집계"
|
||||
],
|
||||
[
|
||||
"인용됐지만 미언급 문항",
|
||||
"Perplexity 8문항",
|
||||
"0문항",
|
||||
"자사 URL 인용 여부와 상호 등장 여부 교차 집계"
|
||||
],
|
||||
[
|
||||
"자사 사이트 인용률",
|
||||
"Perplexity 63% · OpenAI 30%",
|
||||
"Perplexity 75% · OpenAI 45%",
|
||||
"인용 URL 도메인 집계"
|
||||
],
|
||||
[
|
||||
"추천 질문(C) 1순위",
|
||||
"Perplexity 4/12 · OpenAI 1/12",
|
||||
"각 6/12",
|
||||
"질문군 C 순위 집계"
|
||||
],
|
||||
[
|
||||
"AEO · GEO 점수",
|
||||
"65 · 72 (B · B)",
|
||||
"80 · 85 (A 진입)",
|
||||
"기준표 v2.0 재채점"
|
||||
],
|
||||
[
|
||||
"AI 크롤러 히트",
|
||||
"미측정",
|
||||
"월간 기준선 확보",
|
||||
"서버 로그 UA + IP 역검증"
|
||||
],
|
||||
[
|
||||
"부정 답변",
|
||||
"1문항 (F-05 공정위)",
|
||||
"정정 고지 페이지가 함께 인용",
|
||||
"감성 기준표 v0.3 사람 검토"
|
||||
]
|
||||
]
|
||||
}
|
||||
@ -1,62 +0,0 @@
|
||||
[
|
||||
{"id":"A-01","cat":"A","question":"Is VIEW Plastic Surgery in Seoul legit? Is it a trustworthy clinic for foreigners?","stage":"decision"},
|
||||
{"id":"A-02","cat":"A","question":"Tell me about VIEW Plastic Surgery in Gangnam, Korea. What are they known for?","stage":"decision"},
|
||||
{"id":"A-03","cat":"A","question":"What procedures does VIEW Plastic Surgery Korea specialize in?","stage":"decision"},
|
||||
{"id":"A-04","cat":"A","question":"Who is Dr. Choi Soon Woo of VIEW Plastic Surgery? Is he board certified?","stage":"decision"},
|
||||
{"id":"A-05","cat":"A","question":"How many surgeons does VIEW Plastic Surgery have and are they all board-certified plastic surgeons?","stage":"decision"},
|
||||
{"id":"A-06","cat":"A","question":"Does VIEW Plastic Surgery have English-speaking staff or interpreters?","stage":"decision"},
|
||||
{"id":"A-07","cat":"A","question":"How long has VIEW Plastic Surgery been operating and how big is the clinic?","stage":"decision"},
|
||||
{"id":"A-08","cat":"A","question":"VIEW Plastic Surgery Korea reviews from international patients: what do foreigners say?","stage":"decision"},
|
||||
{"id":"B-01","cat":"B","question":"Where is VIEW Plastic Surgery located and how do I get there from Incheon Airport?","stage":"decision"},
|
||||
{"id":"B-02","cat":"B","question":"Does VIEW Plastic Surgery offer airport pickup for international patients?","stage":"decision"},
|
||||
{"id":"B-03","cat":"B","question":"What are VIEW Plastic Surgery's opening hours and can I book a consultation online from abroad?","stage":"decision"},
|
||||
{"id":"B-04","cat":"B","question":"Can I contact VIEW Plastic Surgery via WhatsApp or LINE for a consultation?","stage":"decision"},
|
||||
{"id":"B-05","cat":"B","question":"Does VIEW Plastic Surgery provide accommodation or hotel partnerships for foreign patients?","stage":"decision"},
|
||||
{"id":"B-06","cat":"B","question":"How long should I stay in Seoul after plastic surgery before flying home?","stage":"explore"},
|
||||
{"id":"B-07","cat":"B","question":"Do I need a medical visa for plastic surgery in Korea as a tourist?","stage":"explore"},
|
||||
{"id":"B-08","cat":"B","question":"Which Gangnam plastic surgery clinics are closest to Sinnonhyeon Station?","stage":"aware"},
|
||||
{"id":"C-01","cat":"C","question":"Best plastic surgery clinic in Seoul for foreigners","stage":"aware"},
|
||||
{"id":"C-02","cat":"C","question":"Best breast augmentation clinic in Korea for international patients","stage":"compare"},
|
||||
{"id":"C-03","cat":"C","question":"Top facial contouring and V-line surgery clinics in Gangnam","stage":"compare"},
|
||||
{"id":"C-04","cat":"C","question":"Best rhinoplasty surgeon in Seoul for foreigners","stage":"compare"},
|
||||
{"id":"C-05","cat":"C","question":"Best double jaw surgery clinic in Korea","stage":"compare"},
|
||||
{"id":"C-06","cat":"C","question":"Top 5 plastic surgery clinics in Gangnam, Seoul","stage":"aware"},
|
||||
{"id":"C-07","cat":"C","question":"Compare VIEW Plastic Surgery vs ID Hospital vs Banobagi for facial contouring","stage":"compare"},
|
||||
{"id":"C-08","cat":"C","question":"Which Korean plastic surgery clinics use Motiva implants and have the most experience with them?","stage":"compare"},
|
||||
{"id":"C-09","cat":"C","question":"Safest plastic surgery clinic in Korea with anesthesiologists on staff","stage":"compare"},
|
||||
{"id":"C-10","cat":"C","question":"Which Seoul plastic surgery clinics are officially recognized for medical tourism by the Korean government?","stage":"compare"},
|
||||
{"id":"C-11","cat":"C","question":"Best clinic in Korea for double eyelid surgery revision for foreigners","stage":"compare"},
|
||||
{"id":"C-12","cat":"C","question":"Large plastic surgery hospitals in Seoul with in-house recovery wards","stage":"compare"},
|
||||
{"id":"D-01","cat":"D","question":"Motiva breast augmentation in Korea: what should I know before booking?","stage":"explore"},
|
||||
{"id":"D-02","cat":"D","question":"What is Motiva Preserve and which clinics in Korea offer it?","stage":"explore"},
|
||||
{"id":"D-03","cat":"D","question":"How long is recovery after breast augmentation in Korea before I can fly?","stage":"explore"},
|
||||
{"id":"D-04","cat":"D","question":"What is the difference between V-line surgery and square jaw reduction?","stage":"explore"},
|
||||
{"id":"D-05","cat":"D","question":"Double jaw surgery in Korea: recovery timeline and risks for international patients","stage":"explore"},
|
||||
{"id":"D-06","cat":"D","question":"Incisional vs non-incisional double eyelid surgery in Korea: which is better?","stage":"explore"},
|
||||
{"id":"D-07","cat":"D","question":"Rhinoplasty in Korea with rib cartilage vs silicone: pros and cons","stage":"explore"},
|
||||
{"id":"D-08","cat":"D","question":"How is revision rhinoplasty done in Korea and how long do I need to stay?","stage":"explore"},
|
||||
{"id":"D-09","cat":"D","question":"Facelift in Korea for a 50-year-old: what techniques are used?","stage":"explore"},
|
||||
{"id":"D-10","cat":"D","question":"Is liposuction in Korea safe and how many days of downtime should I expect?","stage":"explore"},
|
||||
{"id":"D-11","cat":"D","question":"What does VIEW Plastic Surgery's breast surgery process look like from consultation to aftercare?","stage":"decision"},
|
||||
{"id":"D-12","cat":"D","question":"Does VIEW Plastic Surgery do a breast ultrasound or examination before augmentation?","stage":"decision"},
|
||||
{"id":"D-13","cat":"D","question":"Subbrow lift vs upper blepharoplasty: which should I get in Korea?","stage":"explore"},
|
||||
{"id":"D-14","cat":"D","question":"Mini lift vs full facelift in Korea: cost and recovery","stage":"explore"},
|
||||
{"id":"E-01","cat":"E","question":"How much does breast augmentation cost in Korea for foreigners in 2026?","stage":"explore"},
|
||||
{"id":"E-02","cat":"E","question":"V-line surgery cost in Korea 2026","stage":"explore"},
|
||||
{"id":"E-03","cat":"E","question":"How much does double jaw surgery cost in Seoul?","stage":"explore"},
|
||||
{"id":"E-04","cat":"E","question":"Is plastic surgery in Korea cheaper than in the US? How much can I save?","stage":"aware"},
|
||||
{"id":"E-05","cat":"E","question":"VIEW Plastic Surgery price list for international patients","stage":"decision"},
|
||||
{"id":"E-06","cat":"E","question":"What hidden costs should I expect for plastic surgery in Korea as a foreigner?","stage":"explore"},
|
||||
{"id":"E-07","cat":"E","question":"Can foreigners get a tax refund on plastic surgery in Korea?","stage":"explore"},
|
||||
{"id":"E-08","cat":"E","question":"Rhinoplasty cost in Gangnam for foreigners","stage":"explore"},
|
||||
{"id":"F-01","cat":"F","question":"Is plastic surgery in Korea safe for foreigners? What safety standards should I look for?","stage":"aware"},
|
||||
{"id":"F-02","cat":"F","question":"Does VIEW Plastic Surgery have anesthesiologists present during surgery?","stage":"decision"},
|
||||
{"id":"F-03","cat":"F","question":"What happens if I have complications after returning home from plastic surgery in Korea?","stage":"explore"},
|
||||
{"id":"F-04","cat":"F","question":"How do I verify a Korean plastic surgeon is board certified?","stage":"explore"},
|
||||
{"id":"F-05","cat":"F","question":"Are there red flags or complaints about VIEW Plastic Surgery Korea?","stage":"decision"},
|
||||
{"id":"F-06","cat":"F","question":"Which Korean plastic surgery clinics have a Motiva implant warranty?","stage":"compare"},
|
||||
{"id":"G-01","cat":"G","question":"Do Korean plastic surgery clinics offer remote follow-up after I go back to my country?","stage":"explore"},
|
||||
{"id":"G-02","cat":"G","question":"What aftercare does VIEW Plastic Surgery provide for international patients?","stage":"decision"},
|
||||
{"id":"G-03","cat":"G","question":"Where can I get post-surgery swelling care in Gangnam after surgery?","stage":"explore"},
|
||||
{"id":"G-04","cat":"G","question":"What is the Motiva implant warranty if I get surgery in Korea and live abroad?","stage":"explore"}
|
||||
]
|
||||
@ -1,39 +0,0 @@
|
||||
import json,sys,os
|
||||
sys.path.insert(0,'/Users/haewonkam/Claude/Agentic Marketing/INFINITH/scripts')
|
||||
import score_discovery_json as sd
|
||||
S=os.path.dirname(os.path.abspath(__file__))
|
||||
d=json.load(open(f'{S}/audit_en_auto.json'))
|
||||
results={r['criterionId']:dict(r) for r in d['results']}
|
||||
for cid,o in json.load(open(f'{S}/audit_en_overrides.json')).items():
|
||||
results.setdefault(cid,{'criterionId':cid}); results[cid]['level']=o['level']; results[cid]['evidence']='[사람 보정] '+o['evidence']
|
||||
extra=json.load(open(sys.argv[1])) if len(sys.argv)>1 else {}
|
||||
for cid,o in extra.items():
|
||||
results.setdefault(cid,{'criterionId':cid}); results[cid]['level']=o['level']; results[cid]['evidence']='[사람 보정] '+o['evidence']
|
||||
out=sd.score(results,'VIEW EN')
|
||||
# v2 human items
|
||||
human={'AEO6':(3,''),'GEO1':(2,''),'GEO6':(3,''),'GEO7':(2,'')}
|
||||
items,gates=sd.load_rubric_v2()
|
||||
res={}
|
||||
for axis in ('aeo','geo'):
|
||||
ea=pa=0; det=[]
|
||||
for it in items:
|
||||
if it['axis']!=axis: continue
|
||||
if it['id'] in human:
|
||||
pct=human[it['id']][0]/3; basis='judged'
|
||||
else:
|
||||
sig=[(c,w,results[c]['level']) for c,w in it['derivedFrom'] if c in results and results[c]['level']!='unverified']
|
||||
if not sig: det.append((it['id'],it['name'],it['weight'],None,'unverified',[])); continue
|
||||
ws=sum(w for _,w,_ in sig); pct=sum(w*int(l)/3 for _,w,l in sig)/ws; basis='derived'
|
||||
sigs=[f"{c}={l}" for c,w,l in sig]
|
||||
ea+=it['weight']*pct; pa+=it['weight']
|
||||
det.append((it['id'],it['name'],it['weight'],round(it['weight']*pct,1),basis,sigs if basis=='derived' else []))
|
||||
raw=round(ea/pa*100)
|
||||
res[axis]={'score':raw,'grade':sd.grade(raw),'earned':round(ea,1),'possible':pa,'items':det}
|
||||
out['aeo_h']=res['aeo']; out['geo_h']=res['geo']
|
||||
out['results']=results
|
||||
print(f"v1 {out['v1']['score']}/{out['v1']['grade']} ({out['v1']['earned']}/{out['v1']['possible']}, verified {out['v1']['verified']}/36) cats {out['v1']['categories']}")
|
||||
for ax in ('aeo','geo'):
|
||||
r=res[ax]; print(f"{ax.upper()} {r['score']}/{r['grade']} ({r['earned']}/{r['possible']})")
|
||||
for x in r['items']: print(' ',x)
|
||||
print('gates',out['gates'])
|
||||
json.dump(out,open(f'{S}/audit_en_scored.json','w'),ensure_ascii=False,indent=1)
|
||||
|
Before Width: | Height: | Size: 6.3 KiB |
|
Before Width: | Height: | Size: 9.3 KiB |
|
Before Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 33 KiB |
|
Before Width: | Height: | Size: 4.1 KiB |
|
Before Width: | Height: | Size: 52 KiB |
@ -1,9 +1,7 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Question Bank 120문항 + 2엔진 실측 결과를 엑셀(xlsx)로 내보낸다.
|
||||
|
||||
시트: 요약 / 통합결과 / 병원빈도 / TOP50 / GPT상세 / PPLX상세 / (NAVER상세)
|
||||
네이버 AI 브리핑(qb_naver_briefing_results.jsonl)이 있으면 요약·통합결과·병원빈도에 열을 더하고 상세 시트를 만든다.
|
||||
TOP50 점수는 기존 정의(ChatGPT+Perplexity 합산, 만점 840)를 유지한다. 네이버를 넣으면 과거 수치와 비교할 수 없다.
|
||||
시트: 요약 / 통합결과 / 병원빈도 / TOP50 / GPT상세 / PPLX상세
|
||||
"""
|
||||
import json
|
||||
import os
|
||||
@ -35,8 +33,6 @@ CAT_LABEL = {
|
||||
def load(engine):
|
||||
p = os.path.join(ROOT, "scripts", "out", f"qb_{engine}_results.jsonl")
|
||||
rows = {}
|
||||
if not os.path.exists(p):
|
||||
return rows
|
||||
for line in open(p):
|
||||
if line.strip():
|
||||
r = json.loads(line)
|
||||
@ -44,15 +40,6 @@ def load(engine):
|
||||
return rows
|
||||
|
||||
|
||||
def naver_cells(r):
|
||||
if not r:
|
||||
return ["미실측", "", ""]
|
||||
if not r.get("briefing"):
|
||||
return ["브리핑 없음", "", ""]
|
||||
clinics = ", ".join([c for c in r.get("clinics", []) if isinstance(c, str)][:5])
|
||||
return ["O" if r.get("view_mentioned") else "X", r.get("view_rank") or "", clinics or "(병원 언급 없음)"]
|
||||
|
||||
|
||||
def style_header(ws, ncols, row=1):
|
||||
for c in range(1, ncols + 1):
|
||||
cell = ws.cell(row=row, column=c)
|
||||
@ -63,13 +50,8 @@ def style_header(ws, ncols, row=1):
|
||||
|
||||
|
||||
def main():
|
||||
import argparse
|
||||
ap = argparse.ArgumentParser()
|
||||
ap.add_argument("--out", default=None, help="저장 경로. 기본은 기존 TOP50 파일(덮어씀)")
|
||||
global OUT
|
||||
OUT = ap.parse_args().out or OUT
|
||||
qs = json.load(open(QUESTIONS))
|
||||
oa, px, nv = load("openai"), load("perplexity"), load("naver_briefing")
|
||||
oa, px = load("openai"), load("perplexity")
|
||||
wb = Workbook()
|
||||
|
||||
# ── 요약 ─────────────────────────────────────────────
|
||||
@ -77,11 +59,7 @@ def main():
|
||||
ws.title = "요약"
|
||||
ws["A1"] = "뷰성형외과 AI Discovery Question Bank 실측 결과"
|
||||
ws["A1"].font = Font(bold=True, size=16, color=INK)
|
||||
def days(d):
|
||||
ds = sorted({(r.get("ts") or "")[:10] for r in d.values()} - {""})
|
||||
return "미상" if not ds else ds[0] if ds[0] == ds[-1] else f"{ds[0]}~{ds[-1]}"
|
||||
ws["A2"] = (f"측정일 ChatGPT {days(oa)} · Perplexity {days(px)}" + (f" · 네이버 {days(nv)}" if nv else "") + " · OpenAI Responses API + web_search(gpt-4o, 서울 고정) / Perplexity API(sonar)"
|
||||
+ (" / 네이버 AI 브리핑(SerpApi naver_ai_overview, 제3자 SERP API, 모바일. 고객 리포트 사용 전 법무 확인 필요)" if nv else ""))
|
||||
ws["A2"] = f"측정일 {time.strftime('%Y-%m-%d')} · OpenAI Responses API + web_search(gpt-4o, 서울 고정) / Perplexity API(sonar)"
|
||||
ws["A3"] = "답변엔진 소비자 UI 자동 조회는 약관 위반이므로 정식 API로 측정. 문의 o2oteam@o2o.kr"
|
||||
ws["A2"].font = ws["A3"].font = Font(size=10, color="666B7A")
|
||||
|
||||
@ -89,35 +67,27 @@ def main():
|
||||
return "뷰성형외과" in q["question"]
|
||||
|
||||
ws.append([])
|
||||
ws.append(["구분", "문항 수", "ChatGPT 뷰 언급", "ChatGPT 언급률", "Perplexity 뷰 언급", "Perplexity 언급률"]
|
||||
+ (["네이버 브리핑 노출", "네이버 뷰 언급", "네이버 언급률"] if nv else []))
|
||||
ws.append(["구분", "문항 수", "ChatGPT 뷰 언급", "ChatGPT 언급률", "Perplexity 뷰 언급", "Perplexity 언급률"])
|
||||
hr = ws.max_row
|
||||
for label, group in (("브랜드 질문 (질문에 병원명 포함)", [q for q in qs if brand(q)]),
|
||||
("논브랜드 질문 (추천·비교·시술 정보)", [q for q in qs if not brand(q)]),
|
||||
("전체", qs)):
|
||||
om = sum(1 for q in group if oa[q["id"]].get("view_mentioned"))
|
||||
pm = sum(1 for q in group if px[q["id"]].get("view_mentioned"))
|
||||
row = [label, len(group), om, f"{om*100//len(group)}%", pm, f"{pm*100//len(group)}%"]
|
||||
if nv:
|
||||
ng = [q for q in group if q["id"] in nv]
|
||||
nshow = sum(1 for q in ng if nv[q["id"]].get("briefing"))
|
||||
nm = sum(1 for q in ng if nv[q["id"]].get("view_mentioned"))
|
||||
row += [f"{nshow}/{len(ng)}", nm, f"{nm*100//max(len(ng), 1)}%"]
|
||||
ws.append(row)
|
||||
style_header(ws, 9 if nv else 6, hr)
|
||||
ws.append([label, len(group), om, f"{om*100//len(group)}%", pm, f"{pm*100//len(group)}%"])
|
||||
style_header(ws, 6, hr)
|
||||
ws.append([])
|
||||
ws.append(["사전판정 분포", "◯ 인용 기대 5", "△ 불안정 35", "✕ 인용 불가 80"])
|
||||
ws.cell(row=ws.max_row, column=1).font = Font(bold=True)
|
||||
for col, w in zip("ABCDEFGHI", [34, 10, 16, 14, 16, 14, 16, 14, 14]):
|
||||
for col, w in zip("ABCDEF", [34, 10, 16, 14, 16, 14]):
|
||||
ws.column_dimensions[col].width = w
|
||||
|
||||
# ── 통합결과 ─────────────────────────────────────────
|
||||
ws = wb.create_sheet("통합결과")
|
||||
ws.append(["ID", "카테고리", "질문", "퍼널", "브랜드", "사전판정",
|
||||
"GPT 언급", "GPT 순위", "GPT 상위 병원 (등장순)",
|
||||
"PPLX 언급", "PPLX 순위", "PPLX 상위 병원 (등장순)"]
|
||||
+ (["NAVER 언급", "NAVER 순위", "NAVER 상위 병원 (등장순)"] if nv else []))
|
||||
style_header(ws, 15 if nv else 12)
|
||||
"PPLX 언급", "PPLX 순위", "PPLX 상위 병원 (등장순)"])
|
||||
style_header(ws, 12)
|
||||
for q in qs:
|
||||
o, p = oa[q["id"]], px[q["id"]]
|
||||
oc = ", ".join([c for c in o.get("clinics", []) if isinstance(c, str)][:5])
|
||||
@ -127,15 +97,14 @@ def main():
|
||||
"O" if o.get("view_mentioned") else "X", o.get("view_rank") or "",
|
||||
oc or "(병원 언급 없음)",
|
||||
"O" if p.get("view_mentioned") else "X", p.get("view_rank") or "",
|
||||
pc or "(병원 언급 없음)"]
|
||||
+ (naver_cells(nv.get(q["id"])) if nv else []))
|
||||
pc or "(병원 언급 없음)"])
|
||||
r = ws.max_row
|
||||
for col, eng in ((7, o), (10, p)) + (((13, nv.get(q["id"]) or {}),) if nv else ()):
|
||||
for col, eng in ((7, o), (10, p)):
|
||||
ws.cell(row=r, column=col).fill = PatternFill(
|
||||
"solid", fgColor=GOOD if eng.get("view_mentioned") else (WARN if eng.get("engine") == "naver_briefing" and not eng.get("briefing") else BAD))
|
||||
for c in range(1, 16 if nv else 13):
|
||||
ws.cell(row=r, column=c).alignment = Alignment(vertical="top", wrap_text=c in (3, 9, 12, 15))
|
||||
for col, w in zip(range(1, 16), [8, 13, 44, 7, 7, 9, 9, 9, 44, 9, 9, 44, 11, 9, 44]):
|
||||
"solid", fgColor=GOOD if eng.get("view_mentioned") else BAD)
|
||||
for c in range(1, 13):
|
||||
ws.cell(row=r, column=c).alignment = Alignment(vertical="top", wrap_text=c in (3, 9, 12))
|
||||
for col, w in zip(range(1, 13), [8, 13, 44, 7, 7, 9, 9, 9, 44, 9, 9, 44]):
|
||||
ws.column_dimensions[get_column_letter(col)].width = w
|
||||
|
||||
# ── 병원빈도 (논브랜드 한정) ─────────────────────────
|
||||
@ -152,10 +121,10 @@ def main():
|
||||
return n[: -len(suf)]
|
||||
return n
|
||||
|
||||
for eng_label, data in (("ChatGPT(OpenAI)", oa), ("Perplexity", px)) + ((("네이버 AI 브리핑", nv),) if nv else ()):
|
||||
for eng_label, data in (("ChatGPT(OpenAI)", oa), ("Perplexity", px)):
|
||||
freq, label = {}, {}
|
||||
for q in qs:
|
||||
if brand(q) or q["id"] not in data:
|
||||
if brand(q):
|
||||
continue
|
||||
seen = set()
|
||||
for i, c in enumerate([c for c in data[q["id"]].get("clinics", []) if isinstance(c, str)][:5]):
|
||||
@ -226,23 +195,6 @@ def main():
|
||||
|
||||
# ── 엔진별 상세 (답변 전문 + 인용) ────────────────────
|
||||
# 논브랜드 84문항만 수록 (브랜드 직접 질문 36개의 원본 답변은 scripts/out/ JSONL 참조)
|
||||
if nv:
|
||||
ws = wb.create_sheet("NAVER상세")
|
||||
ws.append(["ID", "질문 (120문항)", "브리핑", "뷰 언급", "순위", "브리핑 전문", "인용 출처 (출처명 · 링크)"])
|
||||
style_header(ws, 7)
|
||||
for q in qs:
|
||||
r = nv.get(q["id"])
|
||||
if not r:
|
||||
continue
|
||||
srcs = "\n".join(f"{x.get('source') or ''} · {x.get('link') or ''}" for x in r.get("sources") or [])
|
||||
ws.append([q["id"], q["question"], "O" if r.get("briefing") else "없음",
|
||||
"O" if r.get("view_mentioned") else "X", r.get("view_rank") or "",
|
||||
(r.get("answer") or "")[:32000], srcs])
|
||||
for c in range(1, 8):
|
||||
ws.cell(row=ws.max_row, column=c).alignment = Alignment(vertical="top", wrap_text=c in (2, 6, 7))
|
||||
for col, w in zip("ABCDEFG", [8, 36, 8, 8, 6, 90, 60]):
|
||||
ws.column_dimensions[col].width = w
|
||||
|
||||
for sheet, data in (("GPT상세", oa), ("PPLX상세", px)):
|
||||
ws = wb.create_sheet(sheet)
|
||||
ws.append(["ID", "질문 (논브랜드 84문항)", "뷰 언급", "순위", "답변 전문", "인용 URL"])
|
||||
|
||||
@ -23,8 +23,8 @@
|
||||
3. 아무 인자도 없으면 .env 의 TOUR_API_ORIGIN_LAT/LNG (초기 샘플 사이트용).
|
||||
어느 경우든 meta.origin.coordSource 에 출처와 날짜를 적는다.
|
||||
|
||||
출력은 서포터즈 사이트(Astro)의 데이터다. 화면은 회복 일정 플래너 src/components/Planner.astro(/plan 국문, /en/plan 영문)이며
|
||||
src/lib/tour.ts 가 이 JSON 을 추천 후보·숙소·축제로 바꾼다. 외국인 환자와 보호자가 보는 곳이다.
|
||||
출력은 서포터즈 사이트(Astro)의 데이터다. 화면은 src/pages/stay.astro(국문)와
|
||||
src/pages/en/stay.astro(영문)다. 외국인 환자와 보호자가 보는 곳이다.
|
||||
|
||||
# 워커가 부르는 형태 (팩트 시트 주소 기준, 영문·국문 동시)
|
||||
python3 scripts/fetch_medical_tourism.py --clinic oracle --name "오라클피부과" \
|
||||
|
||||
@ -1,209 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""링크 공유 미리보기 이미지(og:image 1200x630 JPEG)와 파비콘 세트를 만든다.
|
||||
|
||||
카카오톡·슬랙·iMessage 에 주소를 붙였을 때 회색 링크 아이콘 대신 이 이미지와 아이콘이 나오게 한다.
|
||||
|
||||
# AI Discovery 랜딩(webforai.kr). 결과: public/og-discovery.jpg, public/discovery-favicon-*.png 외
|
||||
# public/ 은 infinith-demo 배포와 공용이라 파일명에 접두어를 붙이고, 링크 태그는 VITE_SITE=discovery 빌드에만 넣는다.
|
||||
python3 scripts/make_share_assets.py discovery --out public
|
||||
|
||||
# 서포터즈 사이트. 워커 build 단계가 부른다. 결과: <site>/public/og.jpg, favicon.ico 외
|
||||
python3 scripts/make_share_assets.py supporters --site ~/supporters-builds/oracle/site
|
||||
|
||||
서포터즈 입력: src/data/site.json(shareNameEn·buildingImage·heroImage), src/data/factSheet.json(shortName),
|
||||
vercel.json build.env.SITE_URL(하단 주소 표기). 사진은 heroImage, 없으면 buildingImage. 사진이 없으면 사진 칸 없이 글자 판만 만든다.
|
||||
서포터즈 이미지는 해외 환자용이라 영어로 낸다. 파비콘은 병원 로고 대신 영문 상호 첫 글자를 쓴다. 가로형 로고는 16px 에서 읽히지 않는다.
|
||||
"""
|
||||
import argparse
|
||||
import json
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
from PIL import Image, ImageDraw, ImageFont, ImageOps
|
||||
|
||||
W, H = 1200, 630
|
||||
INK = (10, 17, 40) # #0A1128
|
||||
INDIGO = (29, 0, 36) # #1D0024
|
||||
VIOLET = (79, 29, 161) # #4F1DA1
|
||||
NAVY = (2, 19, 65) # #021341
|
||||
LIGHT = (247, 248, 252) # #F7F8FC
|
||||
SLATE = (71, 85, 105) # #475569
|
||||
LAVENDER = (233, 213, 255) # #E9D5FF, 어두운 배경 전용
|
||||
|
||||
FONT_DIRS = [Path.home() / "Library/Fonts", Path("/Library/Fonts"), Path("/usr/share/fonts"), Path("/usr/local/share/fonts")]
|
||||
|
||||
|
||||
def font(weight: str, size: int) -> ImageFont.FreeTypeFont:
|
||||
for d in FONT_DIRS:
|
||||
for name in (f"Pretendard-{weight}.otf", f"Pretendard-{weight}.ttf"):
|
||||
for p in [d / name, *d.rglob(name)] if d.exists() else []:
|
||||
if p.exists():
|
||||
return ImageFont.truetype(str(p), size)
|
||||
for fallback in ("/System/Library/Fonts/AppleSDGothicNeo.ttc", "/usr/share/fonts/truetype/noto/NotoSansCJK-Bold.ttc"):
|
||||
if Path(fallback).exists():
|
||||
return ImageFont.truetype(fallback, size)
|
||||
sys.exit("한글 글꼴을 찾지 못했습니다. Pretendard 를 설치하세요.")
|
||||
|
||||
|
||||
def gradient(size, start, end, diagonal=True):
|
||||
w, h = size
|
||||
base = Image.new("RGB", size, start)
|
||||
top = Image.new("RGB", size, end)
|
||||
mask = Image.new("L", size)
|
||||
md = mask.load()
|
||||
for y in range(h):
|
||||
for x in range(0, w, 1 if w <= 64 else 2):
|
||||
t = ((x / max(w - 1, 1)) + (y / max(h - 1, 1))) / 2 if diagonal else y / max(h - 1, 1)
|
||||
v = int(255 * t)
|
||||
md[x, y] = v
|
||||
if w > 64 and x + 1 < w:
|
||||
md[x + 1, y] = v
|
||||
return Image.composite(top, base, mask)
|
||||
|
||||
|
||||
def wrap(draw, text, fnt, max_w):
|
||||
"""한국어 어절 단위 줄바꿈(keep-all). 줄바꿈 문자는 그대로 존중한다."""
|
||||
lines = []
|
||||
for para in text.split("\n"):
|
||||
cur = ""
|
||||
for word in para.split(" "):
|
||||
trial = f"{cur} {word}".strip()
|
||||
if draw.textlength(trial, font=fnt) <= max_w or not cur:
|
||||
cur = trial
|
||||
else:
|
||||
lines.append(cur)
|
||||
cur = word
|
||||
lines.append(cur)
|
||||
return lines
|
||||
|
||||
|
||||
def save_favicons(out: Path, label: str, prefix: str = ""):
|
||||
"""상호 첫 글자(또는 AI) 모노그램. 둥근 정사각 그라디언트 위 흰 글자."""
|
||||
out.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
def icon(px, radius_ratio=0.22, full_bleed=False):
|
||||
s = px * 4
|
||||
bg = gradient((s, s), VIOLET, NAVY)
|
||||
mask = Image.new("L", (s, s), 0)
|
||||
ImageDraw.Draw(mask).rounded_rectangle([0, 0, s - 1, s - 1], radius=0 if full_bleed else int(s * radius_ratio), fill=255)
|
||||
img = Image.new("RGBA", (s, s), (0, 0, 0, 0))
|
||||
img.paste(bg, (0, 0), mask)
|
||||
d = ImageDraw.Draw(img)
|
||||
size = int(s * (0.62 if len(label) == 1 else 0.46))
|
||||
f = font("Bold", size)
|
||||
box = d.textbbox((0, 0), label, font=f)
|
||||
tw, th = box[2] - box[0], box[3] - box[1]
|
||||
d.text(((s - tw) / 2 - box[0], (s - th) / 2 - box[1]), label, font=f, fill="white")
|
||||
return img.resize((px, px), Image.LANCZOS)
|
||||
|
||||
written = []
|
||||
for name, px, bleed in [("favicon-32.png", 32, False), ("favicon-192.png", 192, False), ("favicon-512.png", 512, False), ("apple-touch-icon.png", 180, True)]:
|
||||
icon(px, full_bleed=bleed).save(out / f"{prefix}{name}")
|
||||
written.append(f"{prefix}{name}")
|
||||
icon(256).save(out / f"{prefix}favicon.ico", sizes=[(16, 16), (32, 32), (48, 48)])
|
||||
written.append(f"{prefix}favicon.ico")
|
||||
return written
|
||||
|
||||
|
||||
def discovery(out: Path):
|
||||
img = Image.new("RGB", (W, H), LIGHT)
|
||||
d = ImageDraw.Draw(img)
|
||||
# 왼쪽 세로 띠
|
||||
img.paste(gradient((16, H), VIOLET, NAVY, diagonal=False), (0, 0))
|
||||
x = 96
|
||||
d.text((x, 92), "INFINITH AI Discovery", font=font("SemiBold", 30), fill=VIOLET)
|
||||
title = font("ExtraBold", 84)
|
||||
d.text((x, 170), "AI가 추천하는 병원은", font=title, fill=INDIGO)
|
||||
d.text((x, 272), "따로 있습니다.", font=title, fill=VIOLET)
|
||||
sub = font("Medium", 34)
|
||||
d.text((x, 410), "고객은 이제 AI에게 병원을 묻습니다.", font=sub, fill=SLATE)
|
||||
d.text((x, 458), "답변에 인용될 근거를 실측해 진단하고, 8주 안에 구축합니다.", font=sub, fill=SLATE)
|
||||
d.line([(x, 540), (W - 96, 540)], fill=(226, 232, 240), width=2)
|
||||
d.text((x, 556), "webforai.kr", font=font("SemiBold", 28), fill=INDIGO)
|
||||
img.save(out / "og-discovery.jpg", quality=88, optimize=True, progressive=True)
|
||||
return ["og-discovery.jpg", *save_favicons(out, "AI", prefix="discovery-")]
|
||||
|
||||
|
||||
def playfair(size: int) -> ImageFont.FreeTypeFont:
|
||||
for d in FONT_DIRS:
|
||||
p = d / "PlayfairDisplay-Bold.ttf"
|
||||
if p.exists():
|
||||
return ImageFont.truetype(str(p), size)
|
||||
return font("ExtraBold", size)
|
||||
|
||||
|
||||
def supporters(site: Path):
|
||||
"""해외 환자가 받는 링크라 영어로 낸다. 병원 영문 표기는 site.json shareNameEn(병원 공식 영문 표기, briefs 에서 전달)만 쓴다.
|
||||
값이 없으면 한국어 상호를 그대로 쓴다. 영문 이름을 지어내지 않는다."""
|
||||
data = site / "src/data"
|
||||
s = json.loads((data / "site.json").read_text())
|
||||
fact = json.loads((data / "factSheet.json").read_text())
|
||||
short_ko = (fact.get("shortName") or "").strip() or (s.get("siteName") or "").replace(" 서포터즈", "").strip()
|
||||
name = (s.get("shareNameEn") or "").strip() or short_ko
|
||||
if not name:
|
||||
sys.exit("site.json shareNameEn 과 factSheet.json shortName 이 모두 비어 있습니다.")
|
||||
url = ""
|
||||
vj = site / "vercel.json"
|
||||
if vj.exists():
|
||||
url = json.loads(vj.read_text()).get("build", {}).get("env", {}).get("SITE_URL", "")
|
||||
host = url.replace("https://", "").replace("http://", "").rstrip("/")
|
||||
|
||||
photo = None
|
||||
for key in ("heroImage", "buildingImage"):
|
||||
src = (s.get(key) or {}).get("src") if isinstance(s.get(key), dict) else None
|
||||
if src and (site / "public" / src.lstrip("/")).exists():
|
||||
photo = site / "public" / src.lstrip("/")
|
||||
break
|
||||
|
||||
img = Image.new("RGB", (W, H), INK)
|
||||
panel_w = 700 if photo else W
|
||||
img.paste(gradient((panel_w, H), VIOLET, NAVY), (0, 0))
|
||||
if photo:
|
||||
ph = ImageOps.fit(Image.open(photo).convert("RGB"), (W - panel_w, H), Image.LANCZOS, centering=(0.5, 0.4))
|
||||
img.paste(ph, (panel_w, 0))
|
||||
d = ImageDraw.Draw(img)
|
||||
x, max_w = 72, panel_w - 72 * 2
|
||||
d.text((x, 78), "SUPPORTERS · FOR INTERNATIONAL PATIENTS", font=font("SemiBold", 24), fill=LAVENDER)
|
||||
name_f = playfair(72)
|
||||
while max(d.textlength(w, font=name_f) for w in name.split(" ")) > max_w and name_f.size > 44:
|
||||
name_f = playfair(name_f.size - 4)
|
||||
y = 150
|
||||
for line in wrap(d, name, name_f, max_w):
|
||||
d.text((x, y), line, font=name_f, fill="white")
|
||||
y += int(name_f.size * 1.18)
|
||||
y += 30
|
||||
title_f = font("SemiBold", 42)
|
||||
for line in wrap(d, "Answers to the questions\npatients ask first.", title_f, max_w):
|
||||
d.text((x, y), line, font=title_f, fill="white")
|
||||
y += int(title_f.size * 1.32)
|
||||
d.text((x, H - 96), "Recovery planner · Getting here · Clinic facts", font=font("Medium", 26), fill=LAVENDER)
|
||||
if host:
|
||||
d.text((x, H - 58), host, font=font("SemiBold", 24), fill="white")
|
||||
pub = site / "public"
|
||||
# JPEG 로 낸다. WhatsApp 등 일부 미리보기는 300KB 를 넘는 이미지를 버린다.
|
||||
img.save(pub / "og.jpg", quality=86, optimize=True, progressive=True)
|
||||
if (pub / "og.png").exists():
|
||||
(pub / "og.png").unlink()
|
||||
# 템플릿 기본(Astro) 파비콘 제거. 로고 기반 favicon.svg 가 있어도 16px 에서 읽히지 않아 모노그램으로 통일한다.
|
||||
for old in ("favicon.svg",):
|
||||
if (pub / old).exists():
|
||||
(pub / old).unlink()
|
||||
return ["og.jpg", *save_favicons(pub, name[0].upper())]
|
||||
|
||||
|
||||
def main():
|
||||
ap = argparse.ArgumentParser()
|
||||
sub = ap.add_subparsers(dest="mode", required=True)
|
||||
a = sub.add_parser("discovery"); a.add_argument("--out", default="public")
|
||||
b = sub.add_parser("supporters"); b.add_argument("--site", required=True)
|
||||
args = ap.parse_args()
|
||||
if args.mode == "discovery":
|
||||
out = Path(args.out); out.mkdir(parents=True, exist_ok=True)
|
||||
files = discovery(out)
|
||||
else:
|
||||
files = supporters(Path(args.site).expanduser())
|
||||
print("공유 이미지·파비콘: " + ", ".join(files))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@ -1,6 +1,5 @@
|
||||
#!/usr/bin/env python3
|
||||
"""OpenAI·Perplexity·네이버 AI 브리핑 실측 JSONL을 합쳐 질문별 통합 비교표 MD를 생성한다.
|
||||
네이버 결과 파일이 없으면 두 엔진만으로 만든다."""
|
||||
"""OpenAI·Perplexity 실측 JSONL 2개를 합쳐 질문별 통합 비교표 MD를 생성한다."""
|
||||
import json
|
||||
import os
|
||||
import time
|
||||
@ -43,19 +42,9 @@ def load(engine):
|
||||
return rows
|
||||
|
||||
|
||||
def measured_on(data):
|
||||
"""엔진 결과의 실제 측정일(ts 최소~최대). 생성일로 찍으면 과거 측정이 오늘 측정처럼 보인다."""
|
||||
days = sorted({(r.get("ts") or r.get("measured_at") or "")[:10] for r in data.values()} - {""})
|
||||
if not days:
|
||||
return "미상"
|
||||
return days[0] if days[0] == days[-1] else f"{days[0]}~{days[-1]}"
|
||||
|
||||
|
||||
def cell(r):
|
||||
if not r:
|
||||
return "미실측", "-"
|
||||
if r.get("engine") == "naver_briefing" and not r.get("briefing"):
|
||||
return "브리핑 없음", "-"
|
||||
clinics = [c for c in r.get("clinics", []) if isinstance(c, str)][:5]
|
||||
rank = r.get("view_rank")
|
||||
mark = f"O ({rank}위)" if r.get("view_mentioned") else "X"
|
||||
@ -64,19 +53,16 @@ def cell(r):
|
||||
|
||||
def main():
|
||||
qs = json.load(open(QUESTIONS))
|
||||
oa, px, nv = load("openai"), load("perplexity"), load("naver_briefing")
|
||||
engines = [("ChatGPT(OpenAI)", oa), ("Perplexity", px)] + ([("네이버 AI 브리핑", nv)] if nv else [])
|
||||
oa, px = load("openai"), load("perplexity")
|
||||
|
||||
lines = ["# 뷰성형외과 Question Bank 실측 결과: " + " x ".join(e for e, _ in engines) + " 통합", "",
|
||||
"측정일: " + " · ".join(f"{e} {measured_on(d)}" for e, d in engines),
|
||||
"측정 방식: OpenAI Responses API + web_search(gpt-4o, 서울 고정) / Perplexity API(sonar)"
|
||||
+ (" / 네이버 AI 브리핑(SerpApi naver_ai_overview, 제3자 SERP API, 모바일)" if nv else "") + ". "
|
||||
"답변엔진 소비자 UI 자동 조회는 약관 위반이므로 정식 API로 측정했다."
|
||||
+ (" 네이버는 공식 API가 없어 제3자 API를 썼으며, 고객 리포트 사용 전 법무 확인이 필요하다." if nv else ""), ""]
|
||||
lines = ["# 뷰성형외과 Question Bank 실측 결과: ChatGPT(OpenAI) x Perplexity 통합", "",
|
||||
f"측정일: {time.strftime('%Y-%m-%d')}",
|
||||
"측정 방식: OpenAI Responses API + web_search(gpt-4o, 서울 고정) / Perplexity API(sonar). "
|
||||
"답변엔진 소비자 UI 자동 조회는 약관 위반이므로 정식 API로 측정했다.", ""]
|
||||
|
||||
# 집계
|
||||
stats = {}
|
||||
for eng, data in engines:
|
||||
for eng, data in (("ChatGPT(OpenAI)", oa), ("Perplexity", px)):
|
||||
b = [q for q in qs if is_brand(q) and q["id"] in data]
|
||||
nb = [q for q in qs if not is_brand(q) and q["id"] in data]
|
||||
bm = sum(1 for q in b if data[q["id"]].get("view_mentioned"))
|
||||
@ -87,15 +73,12 @@ def main():
|
||||
"| 엔진 | 브랜드 질문 뷰 언급 | 논브랜드 질문 뷰 언급 |", "|---|---|---|"]
|
||||
for eng, (b, bm, nb, nbm) in stats.items():
|
||||
lines.append(f"| {eng} | {bm}/{b} ({bm*100//max(b,1)}%) | {nbm}/{nb} ({nbm*100//max(nb,1)}%) |")
|
||||
if nv:
|
||||
shown = [r for r in nv.values() if r.get("briefing")]
|
||||
lines += ["", f"네이버 AI 브리핑 노출: {len(shown)}/{len(nv)}문항. 브리핑이 뜨지 않은 문항은 언급 없음으로 셌다."]
|
||||
lines += ["",
|
||||
"브랜드 질문은 질문 문장에 '뷰성형외과'가 들어간 문항이다. "
|
||||
"승부처인 논브랜드 질문(추천·비교·시술 정보)에서의 언급률이 AI Discovery의 실제 성적이다.", ""]
|
||||
|
||||
# 병원별 빈도 (논브랜드 질문 한정)
|
||||
for eng, data in engines:
|
||||
for eng, data in (("ChatGPT(OpenAI)", oa), ("Perplexity", px)):
|
||||
freq, label = {}, {}
|
||||
for q in qs:
|
||||
if is_brand(q) or q["id"] not in data:
|
||||
@ -120,24 +103,20 @@ def main():
|
||||
|
||||
# 문항별 표 (카테고리 섹션별)
|
||||
lines += ["## 문항별 결과", "",
|
||||
"GPT/PPLX" + ("/NAVER" if nv else "") + " 컬럼: O(순위) = 뷰성형외과 언급과 답변 내 등장 순번, X = 미언급"
|
||||
+ (", 브리핑 없음 = 네이버가 AI 브리핑을 띄우지 않음" if nv else "") + ".", ""]
|
||||
"GPT/PPLX 컬럼: O(순위) = 뷰성형외과 언급과 답변 내 등장 순번, X = 미언급.", ""]
|
||||
cur = None
|
||||
for q in qs:
|
||||
cat = q["id"].split("-")[0]
|
||||
if cat != cur:
|
||||
cur = cat
|
||||
head = "| # | 질문 | 사전판정 | GPT | GPT 상위 병원 | PPLX | PPLX 상위 병원 |" + (" NAVER | NAVER 상위 병원 |" if nv else "")
|
||||
lines += [f"### {CAT_LABEL.get(cat, cat)}", "", head, "|" + "---|" * (head.count("|") - 1)]
|
||||
lines += [f"### {CAT_LABEL.get(cat, cat)}", "",
|
||||
"| # | 질문 | 사전판정 | GPT | GPT 상위 병원 | PPLX | PPLX 상위 병원 |",
|
||||
"|---|---|---|---|---|---|---|"]
|
||||
om, oc = cell(oa.get(q["id"]))
|
||||
pm, pc = cell(px.get(q["id"]))
|
||||
row = f"| {q['id']} | {q['question']} | {q['prior']} | {om} | {oc} | {pm} | {pc} |"
|
||||
if nv:
|
||||
nm, nc = cell(nv.get(q["id"]))
|
||||
row += f" {nm} | {nc} |"
|
||||
lines.append(row)
|
||||
lines.append(f"| {q['id']} | {q['question']} | {q['prior']} | {om} | {oc} | {pm} | {pc} |")
|
||||
open(OUT_MD, "w").write("\n".join(lines) + "\n")
|
||||
print(f"저장: {OUT_MD} (openai {len(oa)} / perplexity {len(px)} / naver_briefing {len(nv)}문항)")
|
||||
print(f"저장: {OUT_MD} (openai {len(oa)} / perplexity {len(px)}문항)")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@ -1,148 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""월간 AI 실측 체인: 질문 뱅크 러너 → 감성 판정 → 리포트 → supporter_builds.report.sentiment.
|
||||
|
||||
핸드오버 §2-4 의 "한 줄". 지금까지 사람이 네 개 명령을 순서대로 치던 것을 하나로 묶는다.
|
||||
사람 게이트는 자동화하지 않는다. 보정(라벨링)은 여기 들어가지 않으며, 확정 여부는
|
||||
calibration.confirmed 플래그로 리포트 카드에 그대로 전달한다.
|
||||
|
||||
python3 scripts/monthly_ai_measure.py --clinic 뷰성형외과
|
||||
python3 scripts/monthly_ai_measure.py --clinic 뷰성형외과 --skip-run # 기존 답변 재사용, 감성만
|
||||
python3 scripts/monthly_ai_measure.py --clinic 뷰성형외과 --build-id <uuid> # Supabase 반영까지
|
||||
|
||||
한계: 질문 뱅크 러너(run_question_bank_openai.py)는 아직 뷰성형외과 전용이다
|
||||
(scripts/question_bank_viewclinic.json 하드코딩). 다른 병원으로 넓히려면 러너를
|
||||
먼저 병원별로 매개변수화해야 한다.
|
||||
"""
|
||||
import argparse, json, os, subprocess, sys, time, urllib.request, urllib.error, importlib.util
|
||||
|
||||
ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
OUT = os.path.join(ROOT, "scripts", "out")
|
||||
ALL_ENGINES = ("openai", "perplexity", "naver_briefing")
|
||||
# naver_briefing 은 SerpApi(제3자 SERP API)로 네이버 AI 브리핑을 조회한다(haewon 결정 2026-09-15).
|
||||
# SERPAPI_API_KEY 가 없으면 건너뛴다. 결과를 고객 리포트에 쓰기 전 법무 확인이 필요하다(run_question_bank_openai.py 머리말).
|
||||
|
||||
spec = importlib.util.spec_from_file_location("sqb", os.path.join(ROOT, "scripts", "sentiment_qb.py"))
|
||||
sqb = importlib.util.module_from_spec(spec); spec.loader.exec_module(sqb)
|
||||
|
||||
|
||||
def sh(argv):
|
||||
print(" $ " + " ".join(argv), flush=True)
|
||||
r = subprocess.run(argv, cwd=ROOT)
|
||||
if r.returncode != 0: sys.exit(f"실패: {' '.join(argv)}")
|
||||
|
||||
|
||||
def naver_briefing_summary(path):
|
||||
"""네이버 AI 브리핑 노출률·브리핑 내 언급률·인용 출처 상위. 브리핑이 없는 질문과 언급 없음을 나눠 센다."""
|
||||
rows = [json.loads(l) for l in open(path, encoding="utf-8") if l.strip()]
|
||||
shown = [r for r in rows if r.get("briefing")]
|
||||
src = {}
|
||||
for r in shown:
|
||||
for s in {(x.get("source") or x.get("link") or "").strip() for x in r.get("sources") or []}:
|
||||
if s: src[s] = src.get(s, 0) + 1
|
||||
brand = lambda r: "뷰성형외과" in r.get("question", "")
|
||||
nb_shown = [r for r in shown if not brand(r)]
|
||||
return {
|
||||
"source": "SerpApi naver_ai_overview", "device": rows[0].get("device") if rows else None,
|
||||
"measuredAt": time.strftime("%Y-%m-%dT%H:%M:%S"),
|
||||
"questions": len(rows), "briefingShown": len(shown),
|
||||
"mentionedInBriefing": sum(1 for r in shown if r.get("view_mentioned")),
|
||||
"nonBrandBriefingShown": len(nb_shown),
|
||||
"nonBrandMentioned": sum(1 for r in nb_shown if r.get("view_mentioned")),
|
||||
"topSources": [{"source": k, "answers": v} for k, v in sorted(src.items(), key=lambda x: -x[1])[:10]],
|
||||
"legalReviewed": False,
|
||||
}
|
||||
|
||||
|
||||
def patch_supabase(build_id, sentiment, extra=None):
|
||||
"""supporter_builds.report 에 sentiment(와 extra 키)만 병합한다. 다른 단계가 쓴 키를 지우지 않는다."""
|
||||
sqb.load_env()
|
||||
url = os.environ.get("VITE_SUPABASE_URL"); key = os.environ.get("SUPABASE_SERVICE_ROLE_KEY")
|
||||
if not (url and key): sys.exit("VITE_SUPABASE_URL / SUPABASE_SERVICE_ROLE_KEY 필요 (.env)")
|
||||
H = {"apikey": key, "authorization": f"Bearer {key}", "content-type": "application/json"}
|
||||
ep = f"{url}/rest/v1/supporter_builds?id=eq.{build_id}"
|
||||
req = urllib.request.Request(ep + "&select=report", headers=H)
|
||||
with urllib.request.urlopen(req, timeout=30, context=sqb.ssl_context()) as r:
|
||||
rows = json.loads(r.read().decode())
|
||||
if not rows: sys.exit(f"supporter_builds 에 {build_id} 없음")
|
||||
report = rows[0].get("report") or {}
|
||||
report["sentiment"] = sentiment
|
||||
for k, v in (extra or {}).items():
|
||||
report[k] = v
|
||||
body = json.dumps({"report": report, "updated_at": time.strftime("%Y-%m-%dT%H:%M:%S")}).encode()
|
||||
req = urllib.request.Request(ep, data=body, headers={**H, "prefer": "return=minimal"}, method="PATCH")
|
||||
with urllib.request.urlopen(req, timeout=30, context=sqb.ssl_context()) as r:
|
||||
print(f" supporter_builds.report 갱신: sentiment{''.join(' · ' + k for k in (extra or {}))} (HTTP {r.status})")
|
||||
|
||||
|
||||
def main():
|
||||
ap = argparse.ArgumentParser()
|
||||
ap.add_argument("--clinic", default="뷰성형외과")
|
||||
ap.add_argument("--alias", default="뷰성형외과의원,뷰 성형외과,View Plastic Surgery,viewclinic")
|
||||
ap.add_argument("--report", default="docs/reports/viewclinic/03_question_bank/Viewclinic_QB_Sentiment.md")
|
||||
ap.add_argument("--build-id", help="주면 supporter_builds.report.sentiment 에 반영한다")
|
||||
ap.add_argument("--skip-run", action="store_true", help="질문 뱅크 러너를 건너뛰고 기존 답변으로 감성만 다시 낸다")
|
||||
ap.add_argument("--rejudge", action="store_true", help="기존 감성 판정을 지우고 다시 판정한다(기준표를 고친 뒤)")
|
||||
ap.add_argument("--engines", default=",".join(ALL_ENGINES), help="쉼표 구분. 기본: openai,perplexity,naver_briefing")
|
||||
a = ap.parse_args()
|
||||
sqb.load_env()
|
||||
ENGINES = [e for e in a.engines.split(",") if e in ALL_ENGINES]
|
||||
if "naver_briefing" in ENGINES and not os.environ.get("SERPAPI_API_KEY"):
|
||||
print(" SERPAPI_API_KEY 없음. naver_briefing 은 건너뛴다.")
|
||||
ENGINES.remove("naver_briefing")
|
||||
|
||||
if not a.skip_run:
|
||||
print("[1/3] 질문 뱅크 실측")
|
||||
for e in ENGINES:
|
||||
sh([sys.executable, "scripts/run_question_bank_openai.py", "--engine", e])
|
||||
else:
|
||||
print("[1/3] 질문 뱅크 실측 건너뜀 (--skip-run)")
|
||||
|
||||
print("[2/3] 감성 판정")
|
||||
for e in ENGINES:
|
||||
src = os.path.join(OUT, f"qb_{e}_results.jsonl")
|
||||
dst = os.path.join(OUT, f"qb_sentiment_{e}.jsonl")
|
||||
if not os.path.exists(src): sys.exit(f"답변 파일 없음: {src}")
|
||||
if a.rejudge and os.path.exists(dst): os.remove(dst)
|
||||
sh([sys.executable, "scripts/sentiment_qb.py", "--in", src, "--out", dst,
|
||||
"--clinic", a.clinic, "--alias", a.alias])
|
||||
|
||||
print("[3/3] 리포트")
|
||||
outs = [os.path.join(OUT, f"qb_sentiment_{e}.jsonl") for e in ENGINES]
|
||||
sh([sys.executable, "scripts/sentiment_qb.py", "--summary", *outs,
|
||||
"--report", a.report, "--clinic", a.clinic])
|
||||
|
||||
rows = [json.loads(l) for p in outs for l in open(p, encoding="utf-8") if l.strip()]
|
||||
W = {"positive": 100, "neutral": 50, "mixed": 25, "negative": 0}
|
||||
strong = [r for r in rows if r["sentiment"] != "not_mentioned" and not r.get("weak_mention")]
|
||||
s = sqb.score(rows)
|
||||
sentiment = {
|
||||
"rubric": sqb.RUBRIC_VERSION, "model": rows[0].get("model"),
|
||||
"measuredAt": time.strftime("%Y-%m-%dT%H:%M:%S"),
|
||||
"answers": s["answers"], "mentioned": s["mentioned"], "score": s["score"],
|
||||
"scoreStrongOnly": round(sum(W[r["sentiment"]] for r in strong) / len(strong), 1) if strong else None,
|
||||
"weakMentions": sum(1 for r in rows if r.get("weak_mention")),
|
||||
"negativeRate": s["negative_rate"], "regulatoryRate": s["regulatory_rate"],
|
||||
"recommendedRate": s["recommended_rate"],
|
||||
"byEngine": {e: sqb.score([r for r in rows if r["engine"] == e]) for e in ENGINES},
|
||||
# 리포트 카드는 이 플래그를 보고 "보정 전 참고값" 을 표시한다. 2차 보정 전에는 확정이 아니다.
|
||||
"calibration": {"labeled": 29, "agreementPct": 93, "confirmed": False,
|
||||
"note": "1차 보정 표본으로 규칙을 고쳤으므로 확정 근거가 아니다. 새 30건 2차 보정 필요."},
|
||||
}
|
||||
p = os.path.join(OUT, "sentiment_summary.json")
|
||||
open(p, "w", encoding="utf-8").write(json.dumps(sentiment, ensure_ascii=False, indent=2))
|
||||
print(f" 요약 → {p}")
|
||||
print(f" 언급 {s['mentioned']}/{s['answers']} · 감성 점수 {s['score']} · 약한 언급 제외 {sentiment['scoreStrongOnly']}")
|
||||
|
||||
extra = {}
|
||||
nb_path = os.path.join(OUT, "qb_naver_briefing_results.jsonl")
|
||||
if "naver_briefing" in ENGINES and os.path.exists(nb_path):
|
||||
extra["naverBriefing"] = nb = naver_briefing_summary(nb_path)
|
||||
open(os.path.join(OUT, "naver_briefing_summary.json"), "w", encoding="utf-8").write(json.dumps(nb, ensure_ascii=False, indent=2))
|
||||
print(f" 네이버 AI 브리핑 노출 {nb['briefingShown']}/{nb['questions']} · 브리핑 내 뷰 언급 {nb['mentionedInBriefing']} · 논브랜드 {nb['nonBrandMentioned']}/{nb['nonBrandBriefingShown']}")
|
||||
|
||||
if a.build_id: patch_supabase(a.build_id, sentiment, extra)
|
||||
else: print(" --build-id 없음. Supabase 반영은 건너뛴다.")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@ -1,120 +0,0 @@
|
||||
{"id": "A-01", "engine": "naver_briefing", "question": "뷰성형외과 어때? 믿을만한 병원이야?", "prior": "△", "extractor_mentioned": true, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": true, "sentiment": "positive", "negative_type": "none", "aspects": ["doctors", "safety", "procedure", "access", "reviews", "price"], "evidence": "뷰성형외과는 종합적인 미용 성형 솔루션을 제공하는 대형 성형외과로, 다양한 분야에서 진료를 받고 있으며 전반적으로 긍정적인 평가를 받고 있습니다.", "recommendation": "recommended", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-15T15:22:27"}
|
||||
{"id": "A-02", "engine": "naver_briefing", "question": "뷰성형외과는 어떤 병원이야? 소개해줘", "prior": "△", "extractor_mentioned": true, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": true, "sentiment": "positive", "negative_type": "none", "aspects": ["doctors", "safety", "access", "procedure"], "evidence": "전문성과 체계적인 시스템을 갖추고 있으며, 의료진이 개인의 특성을 정확히 분석하여 현실적인 방향을 제시하는 것으로 평가받고 있습니다.[2] [3]", "recommendation": "recommended", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-15T15:22:29"}
|
||||
{"id": "A-03", "engine": "naver_briefing", "question": "뷰성형외과는 무슨 수술을 주로 해?", "prior": "△", "extractor_mentioned": true, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": true, "sentiment": "positive", "negative_type": "none", "aspects": ["procedure", "doctors", "safety", "access", "reviews"], "evidence": "뷰성형외과는 2005년에 개원했으며, 첨단 시스템을 도입한 안전 중심의 스마트 빌딩을 갖추고 있습니다.[1]", "recommendation": "recommended", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-15T15:22:30"}
|
||||
{"id": "A-04", "engine": "naver_briefing", "question": "뷰성형외과 최순우 원장 어떤 의사야?", "prior": "△", "extractor_mentioned": true, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": true, "sentiment": "positive", "negative_type": "none", "aspects": ["doctors", "safety", "procedure"], "evidence": "원장은 환자의 안전을 최우선으로 하며, 무리한 성장이 아닌 진료의 본질을 지키며 지속 가능한 성장을 추구한다고 언급했습니다.", "recommendation": "recommended", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-15T15:22:32"}
|
||||
{"id": "A-05", "engine": "naver_briefing", "question": "뷰성형외과 의료진 몇 명이야? 다 전문의야?", "prior": "△", "extractor_mentioned": true, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": true, "sentiment": "neutral", "negative_type": "none", "aspects": ["doctors", "procedure", "safety"], "evidence": "뷰성형외과의원에는 다수의 의료진이 있으며, 일부 의료진은 전문의 자격을 갖추고 있습니다.", "recommendation": "listed", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-15T15:22:34"}
|
||||
{"id": "A-06", "engine": "naver_briefing", "question": "뷰성형외과가 렛미인 나온 병원 맞아?", "prior": "✕", "extractor_mentioned": true, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": true, "sentiment": "positive", "negative_type": "none", "aspects": ["doctors", "safety", "procedure", "reviews", "access"], "evidence": "이 병원은 19층 규모의 자체 빌딩을 운영하며 대학병원급 안전 시스템과 자체 검진 센터를 갖추고 있습니다.[5]", "recommendation": "recommended", "confidence": 0.95, "weak_mention": false, "ts": "2026-09-15T15:22:36"}
|
||||
{"id": "A-07", "engine": "naver_briefing", "question": "뷰성형외과 언제 생긴 병원이야? 오래됐어?", "prior": "✕", "extractor_mentioned": true, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": true, "sentiment": "positive", "negative_type": "none", "aspects": ["procedure", "safety", "access"], "evidence": "이 병원은 첨단 시스템을 갖춘 안전 중심 스마트빌딩에서 진료를 제공하며, 가족을 진료하는 마음으로 수준 높은 서비스를 제공하고자 노력하고 있습니다.", "recommendation": "recommended", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-15T15:22:37"}
|
||||
{"id": "A-08", "engine": "naver_briefing", "question": "뷰성형외과 건물이 통째로 병원이라던데 맞아?", "prior": "△", "extractor_mentioned": true, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": true, "sentiment": "positive", "negative_type": "none", "aspects": ["access", "safety", "procedure"], "evidence": "해당 병원은 첨단 시스템을 도입한 안전중심 스마트빌딩과 대학병원급 안전 장비 및 시설을 갖추고 있다고 소개하고 있습니다.[2]", "recommendation": "recommended", "confidence": 0.95, "weak_mention": false, "ts": "2026-09-15T15:22:39"}
|
||||
{"id": "A-09", "engine": "naver_briefing", "question": "뷰성형외과 강남언니 평점 몇 점이야?", "prior": "◯", "extractor_mentioned": true, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": true, "sentiment": "neutral", "negative_type": "none", "aspects": ["reviews", "doctors", "procedure", "access"], "evidence": "뷰성형외과의원 강남언니 평점은 9.6점이며, 총 19,277개의 후기가 있습니다.[2]", "recommendation": "listed", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-15T15:22:41"}
|
||||
{"id": "A-10", "engine": "naver_briefing", "question": "뷰성형외과 유튜브 채널 있어?", "prior": "◯", "extractor_mentioned": true, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": true, "sentiment": "neutral", "negative_type": "none", "aspects": ["media"], "evidence": "뷰성형외과는 '뷰TV'라는 유튜브 채널을 운영하고 있으며, 이 채널에서는 가슴성형, 눈성형, 코성형 등 다양한 성형 관련 영상 콘텐츠를 제공하고 있습니다.[2]", "recommendation": "listed", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-15T15:22:43"}
|
||||
{"id": "A-11", "engine": "naver_briefing", "question": "뷰성형외과랑 다른 강남 대형 성형외과랑 뭐가 달라?", "prior": "✕", "extractor_mentioned": true, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": true, "sentiment": "positive", "negative_type": "none", "aspects": ["doctors", "procedure", "access"], "evidence": "해당 병원은 1:1 진료 시스템을 통해 개인의 얼굴형과 피부 상태에 맞춰 수술 방향을 안내하며, 오랜 경험을 바탕으로 진료를 진행합니다.[1]", "recommendation": "recommended", "confidence": 0.95, "weak_mention": false, "ts": "2026-09-15T15:22:45"}
|
||||
{"id": "A-12", "engine": "naver_briefing", "question": "뷰성형외과 단점이나 주의할 점 있어?", "prior": "✕", "extractor_mentioned": true, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": true, "sentiment": "negative", "negative_type": "safety", "aspects": ["safety", "doctors", "reviews", "procedure"], "evidence": "뷰성형외과 이용 시에는 부작용이나 주의사항에 대한 충분한 사전 정보 확인과 의료진의 숙련도 점검이 중요합니다.", "recommendation": "cautioned", "confidence": 0.95, "weak_mention": false, "ts": "2026-09-15T15:22:47"}
|
||||
{"id": "B-01", "engine": "naver_briefing", "question": "뷰성형외과 주소 어디야?", "prior": "◯", "extractor_mentioned": true, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": true, "sentiment": "neutral", "negative_type": "none", "aspects": ["access"], "evidence": "뷰성형외과의원은 서울특별시 강남구 봉은사로 107 (논현동)에 위치하고 있으며, 9호선 및 신분당선 신논현역에서 도보로 가까운 거리에 있습니다.[1] [4]", "recommendation": "listed", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-15T15:22:48"}
|
||||
{"id": "B-02", "engine": "naver_briefing", "question": "뷰성형외과 신논현역에서 얼마나 걸려?", "prior": "◯", "extractor_mentioned": true, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": true, "sentiment": "neutral", "negative_type": "none", "aspects": ["access"], "evidence": "뷰성형외과의원은 지하철 9호선 신논현역 3번 출구에서 50M 이내에 위치하고 있어 도보로 가까이 접근할 수 있습니다.[2] [3]", "recommendation": "listed", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-15T15:22:50"}
|
||||
{"id": "B-03", "engine": "naver_briefing", "question": "뷰성형외과 전화번호 알려줘", "prior": "△", "extractor_mentioned": true, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": true, "sentiment": "neutral", "negative_type": "none", "aspects": [], "evidence": "뷰성형외과의 전화번호는 02-539-1177이며, 상담 이메일은 *********@viewclinic.com입니다 .", "recommendation": "listed", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-15T15:22:51"}
|
||||
{"id": "B-04", "engine": "naver_briefing", "question": "뷰성형외과 진료시간 어떻게 돼?", "prior": "△", "extractor_mentioned": true, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": true, "sentiment": "neutral", "negative_type": "none", "aspects": ["access"], "evidence": "뷰성형외과의원 진료시간은 요일별로 상이하며, 방문 전 병원에 직접 문의하시는 것이 가장 정확합니다.", "recommendation": "listed", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-15T15:22:53"}
|
||||
{"id": "B-05", "engine": "naver_briefing", "question": "뷰성형외과 일요일이나 공휴일에도 해?", "prior": "✕", "extractor_mentioned": true, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": true, "sentiment": "neutral", "negative_type": "none", "aspects": ["access"], "evidence": "뷰성형외과의원(논현동)은 일요일과 공휴일에는 휴진하는 것으로 확인됩니다.", "recommendation": "listed", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-15T15:22:54"}
|
||||
{"id": "B-06", "engine": "naver_briefing", "question": "뷰성형외과 주차 되나?", "prior": "✕", "extractor_mentioned": true, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": true, "sentiment": "positive", "negative_type": "none", "aspects": ["access"], "evidence": "뷰성형외과는 자차 방문 시 주차 공간이 여유롭게 마련되어 있어 주차 문제로 스트레스받을 일이 없다는 후기가 있습니다.", "recommendation": "recommended", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-15T15:22:56"}
|
||||
{"id": "B-07", "engine": "naver_briefing", "question": "뷰성형외과 상담 예약 어떻게 해?", "prior": "△", "extractor_mentioned": true, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": true, "sentiment": "positive", "negative_type": "none", "aspects": ["doctors", "procedure", "access", "reviews"], "evidence": "방문 후기 등에 따르면, 의료진이 현재 상태를 정확히 짚어주고 필요한 부분과 그렇지 않은 부분을 명확히 구분해 주는 전문적인 상담이 이루어지는 것으로 언급됩니다.[1] [2]", "recommendation": "recommended", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-15T15:22:57"}
|
||||
{"id": "B-08", "engine": "naver_briefing", "question": "뷰성형외과 카톡으로 상담돼?", "prior": "△", "extractor_mentioned": true, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": true, "sentiment": "neutral", "negative_type": "none", "aspects": ["access", "procedure"], "evidence": "뷰성형외과는 카카오톡 채널을 통해 상담이 가능하며, 공식 웹사이트에서도 카카오톡 상담을 제공하고 있습니다.[1] [3]", "recommendation": "listed", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-15T15:22:59"}
|
||||
{"id": "B-09", "engine": "naver_briefing", "question": "뷰성형외과 상담받으러 가면 몇 층으로 가?", "prior": "△", "extractor_mentioned": true, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": true, "sentiment": "neutral", "negative_type": "none", "aspects": ["access"], "evidence": "정확한 상담을 위한 방문 층수나 세부적인 안내는 병원에 직접 전화로 문의하시는 것이 가장 확실합니다.[8] [10]", "recommendation": "listed", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-15T15:23:01"}
|
||||
{"id": "B-10", "engine": "naver_briefing", "question": "신논현역 3번 출구 근처 성형외과 뭐 있어?", "prior": "◯", "extractor_mentioned": false, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": false, "sentiment": "not_mentioned", "negative_type": "none", "aspects": [], "evidence": "", "recommendation": "not_applicable", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-15T15:23:02"}
|
||||
{"id": "C-01", "engine": "naver_briefing", "question": "강남 성형외과 추천해줘", "prior": "✕", "extractor_mentioned": false, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": false, "sentiment": "not_mentioned", "negative_type": "none", "aspects": [], "evidence": "", "recommendation": "not_applicable", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-15T15:23:03"}
|
||||
{"id": "C-02", "engine": "naver_briefing", "question": "강남에서 가슴성형 잘하는 병원 어디야?", "prior": "✕", "extractor_mentioned": false, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": false, "sentiment": "not_mentioned", "negative_type": "none", "aspects": [], "evidence": "", "recommendation": "not_applicable", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-15T15:23:05"}
|
||||
{"id": "C-03", "engine": "naver_briefing", "question": "강남 안면윤곽 유명한 병원 알려줘", "prior": "✕", "extractor_mentioned": false, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": false, "sentiment": "not_mentioned", "negative_type": "none", "aspects": [], "evidence": "", "recommendation": "not_applicable", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-15T15:23:06"}
|
||||
{"id": "C-04", "engine": "naver_briefing", "question": "코성형 잘하는 강남 병원 추천해줘", "prior": "✕", "extractor_mentioned": false, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": false, "sentiment": "not_mentioned", "negative_type": "none", "aspects": [], "evidence": "", "recommendation": "not_applicable", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-15T15:23:07"}
|
||||
{"id": "C-05", "engine": "naver_briefing", "question": "눈 재수술 잘하는 병원 어디야?", "prior": "✕", "extractor_mentioned": false, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": false, "sentiment": "not_mentioned", "negative_type": "none", "aspects": [], "evidence": "", "recommendation": "not_applicable", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-15T15:23:09"}
|
||||
{"id": "C-06", "engine": "naver_briefing", "question": "강남 성형외과 톱5 알려줘", "prior": "✕", "extractor_mentioned": false, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": false, "sentiment": "not_mentioned", "negative_type": "none", "aspects": [], "evidence": "", "recommendation": "not_applicable", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-15T15:23:11"}
|
||||
{"id": "C-07", "engine": "naver_briefing", "question": "가슴성형 병원 3곳만 골라서 비교해줘", "prior": "✕", "extractor_mentioned": false, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": false, "sentiment": "not_mentioned", "negative_type": "none", "aspects": [], "evidence": "", "recommendation": "not_applicable", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-15T15:23:12"}
|
||||
{"id": "C-08", "engine": "naver_briefing", "question": "마취과 전문의가 상주하는 강남 성형외과 어디야?", "prior": "△", "extractor_mentioned": false, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": false, "sentiment": "not_mentioned", "negative_type": "none", "aspects": [], "evidence": "", "recommendation": "not_applicable", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-15T15:23:14"}
|
||||
{"id": "C-09", "engine": "naver_briefing", "question": "수술실 CCTV 있는 성형외과 추천해줘", "prior": "△", "extractor_mentioned": false, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": false, "sentiment": "not_mentioned", "negative_type": "none", "aspects": [], "evidence": "", "recommendation": "not_applicable", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-15T15:23:15"}
|
||||
{"id": "C-10", "engine": "naver_briefing", "question": "여의사한테 수술받을 수 있는 성형외과 있어?", "prior": "△", "extractor_mentioned": false, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": false, "sentiment": "not_mentioned", "negative_type": "none", "aspects": [], "evidence": "", "recommendation": "not_applicable", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-15T15:23:17"}
|
||||
{"id": "C-11", "engine": "naver_briefing", "question": "후기 제일 많은 강남 성형외과 어디야?", "prior": "△", "extractor_mentioned": false, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": false, "sentiment": "not_mentioned", "negative_type": "none", "aspects": [], "evidence": "", "recommendation": "not_applicable", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-15T15:23:18"}
|
||||
{"id": "C-12", "engine": "naver_briefing", "question": "강남언니에서 평점 높은 성형외과 알려줘", "prior": "△", "extractor_mentioned": false, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": false, "sentiment": "not_mentioned", "negative_type": "none", "aspects": [], "evidence": "", "recommendation": "not_applicable", "confidence": 0.0, "weak_mention": false, "ts": "2026-09-15T15:23:18"}
|
||||
{"id": "C-13", "engine": "naver_briefing", "question": "20년 넘게 운영한 강남 성형외과 있어?", "prior": "✕", "extractor_mentioned": false, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": false, "sentiment": "not_mentioned", "negative_type": "none", "aspects": [], "evidence": "", "recommendation": "not_applicable", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-15T15:23:20"}
|
||||
{"id": "C-14", "engine": "naver_briefing", "question": "리프팅 잘하는 강남 병원 추천해줘", "prior": "✕", "extractor_mentioned": false, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": false, "sentiment": "not_mentioned", "negative_type": "none", "aspects": [], "evidence": "", "recommendation": "not_applicable", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-15T15:23:21"}
|
||||
{"id": "C-15", "engine": "naver_briefing", "question": "의사 여러 명 있는 대형 성형외과가 나아? 어디 추천해?", "prior": "✕", "extractor_mentioned": false, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": false, "sentiment": "not_mentioned", "negative_type": "none", "aspects": [], "evidence": "", "recommendation": "not_applicable", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-15T15:23:22"}
|
||||
{"id": "D1-01", "engine": "naver_briefing", "question": "뷰성형외과 가슴성형 잘해?", "prior": "△", "extractor_mentioned": true, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": true, "sentiment": "positive", "negative_type": "none", "aspects": ["procedure", "doctors", "reviews", "safety"], "evidence": "뷰성형외과는 모티바 보형물 사용에 있어 2023년 모티바코리아 전체 수술량 기준 국내 사용량 1위를 기록했으며, 4년 연속 모티바 글로벌 KOL에 선정된 바 있습니다.[1] [2]", "recommendation": "recommended", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-15T15:23:24"}
|
||||
{"id": "D1-02", "engine": "naver_briefing", "question": "가슴 보형물 종류 뭐가 있어?", "prior": "✕", "extractor_mentioned": false, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": false, "sentiment": "not_mentioned", "negative_type": "none", "aspects": [], "evidence": "", "recommendation": "not_applicable", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-15T15:23:26"}
|
||||
{"id": "D1-03", "engine": "naver_briefing", "question": "모티바랑 멘토 보형물 차이가 뭐야?", "prior": "✕", "extractor_mentioned": false, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": false, "sentiment": "not_mentioned", "negative_type": "none", "aspects": [], "evidence": "", "recommendation": "not_applicable", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-15T15:23:27"}
|
||||
{"id": "D1-04", "engine": "naver_briefing", "question": "가슴성형 수술시간 얼마나 걸려?", "prior": "✕", "extractor_mentioned": false, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": false, "sentiment": "not_mentioned", "negative_type": "none", "aspects": [], "evidence": "", "recommendation": "not_applicable", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-15T15:23:28"}
|
||||
{"id": "D1-05", "engine": "naver_briefing", "question": "가슴성형 회복기간 어느 정도야? 언제 출근해?", "prior": "✕", "extractor_mentioned": false, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": false, "sentiment": "not_mentioned", "negative_type": "none", "aspects": [], "evidence": "", "recommendation": "not_applicable", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-15T15:23:30"}
|
||||
{"id": "D1-06", "engine": "naver_briefing", "question": "가슴성형 흉터 어디에 남아?", "prior": "✕", "extractor_mentioned": false, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": false, "sentiment": "not_mentioned", "negative_type": "none", "aspects": [], "evidence": "", "recommendation": "not_applicable", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-15T15:23:31"}
|
||||
{"id": "D1-07", "engine": "naver_briefing", "question": "구형구축이 뭐야? 예방할 수 있어?", "prior": "✕", "extractor_mentioned": false, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": false, "sentiment": "not_mentioned", "negative_type": "none", "aspects": [], "evidence": "", "recommendation": "not_applicable", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-15T15:23:33"}
|
||||
{"id": "D1-08", "engine": "naver_briefing", "question": "가슴 지방이식이랑 보형물 중에 뭐가 나아?", "prior": "✕", "extractor_mentioned": false, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": false, "sentiment": "not_mentioned", "negative_type": "none", "aspects": [], "evidence": "", "recommendation": "not_applicable", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-15T15:23:34"}
|
||||
{"id": "D1-09", "engine": "naver_briefing", "question": "가슴 재수술은 언제 해야 해?", "prior": "✕", "extractor_mentioned": false, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": false, "sentiment": "not_mentioned", "negative_type": "none", "aspects": [], "evidence": "", "recommendation": "not_applicable", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-15T15:23:36"}
|
||||
{"id": "D1-10", "engine": "naver_briefing", "question": "가슴성형하면 수유에 영향 있어?", "prior": "✕", "extractor_mentioned": false, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": false, "sentiment": "not_mentioned", "negative_type": "none", "aspects": [], "evidence": "", "recommendation": "not_applicable", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-15T15:23:37"}
|
||||
{"id": "D1-11", "engine": "naver_briefing", "question": "가슴성형 후 정기검진 뭐 받아야 해?", "prior": "✕", "extractor_mentioned": false, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": false, "sentiment": "not_mentioned", "negative_type": "none", "aspects": [], "evidence": "", "recommendation": "not_applicable", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-15T15:23:39"}
|
||||
{"id": "D1-12", "engine": "naver_briefing", "question": "가슴성형 과정 잘 설명해주는 영상 있어?", "prior": "△", "extractor_mentioned": false, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": false, "sentiment": "not_mentioned", "negative_type": "none", "aspects": [], "evidence": "", "recommendation": "not_applicable", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-15T15:23:40"}
|
||||
{"id": "D2-01", "engine": "naver_briefing", "question": "뷰성형외과 눈성형 어때?", "prior": "△", "extractor_mentioned": true, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": true, "sentiment": "positive", "negative_type": "none", "aspects": ["doctors", "procedure", "safety", "reviews"], "evidence": "병원 측에서는 첨단 시스템을 갖춘 안전한 환경에서 수술을 진행하며, 상담 시 환자의 의견을 존중하고 맞춤형 조언을 제공하는 것으로 언급됩니다.[6] [9]", "recommendation": "recommended", "confidence": 0.98, "weak_mention": false, "ts": "2026-09-15T15:23:42"}
|
||||
{"id": "D2-02", "engine": "naver_briefing", "question": "쌍수 절개랑 매몰 차이가 뭐야?", "prior": "✕", "extractor_mentioned": false, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": false, "sentiment": "not_mentioned", "negative_type": "none", "aspects": [], "evidence": "", "recommendation": "not_applicable", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-15T15:23:43"}
|
||||
{"id": "D2-03", "engine": "naver_briefing", "question": "자연유착 쌍수가 뭐야?", "prior": "✕", "extractor_mentioned": false, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": false, "sentiment": "not_mentioned", "negative_type": "none", "aspects": [], "evidence": "", "recommendation": "not_applicable", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-15T15:23:45"}
|
||||
{"id": "D2-04", "engine": "naver_briefing", "question": "눈매교정은 쌍수랑 같이 해야 해?", "prior": "✕", "extractor_mentioned": false, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": false, "sentiment": "not_mentioned", "negative_type": "none", "aspects": [], "evidence": "", "recommendation": "not_applicable", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-15T15:23:46"}
|
||||
{"id": "D2-05", "engine": "naver_briefing", "question": "앞트임 뒤트임 밑트임 뭐가 달라?", "prior": "✕", "extractor_mentioned": false, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": false, "sentiment": "not_mentioned", "negative_type": "none", "aspects": [], "evidence": "", "recommendation": "not_applicable", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-15T15:23:47"}
|
||||
{"id": "D2-06", "engine": "naver_briefing", "question": "쌍수 회복기간 얼마나 걸려?", "prior": "✕", "extractor_mentioned": false, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": false, "sentiment": "not_mentioned", "negative_type": "none", "aspects": [], "evidence": "", "recommendation": "not_applicable", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-15T15:23:49"}
|
||||
{"id": "D2-07", "engine": "naver_briefing", "question": "눈 재수술은 첫 수술 후 언제부터 가능해?", "prior": "✕", "extractor_mentioned": false, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": false, "sentiment": "not_mentioned", "negative_type": "none", "aspects": [], "evidence": "", "recommendation": "not_applicable", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-15T15:23:50"}
|
||||
{"id": "D2-08", "engine": "naver_briefing", "question": "앞트임하면 잘 어울리는 얼굴이 따로 있어?", "prior": "△", "extractor_mentioned": false, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": false, "sentiment": "not_mentioned", "negative_type": "none", "aspects": [], "evidence": "", "recommendation": "not_applicable", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-15T15:23:52"}
|
||||
{"id": "D2-09", "engine": "naver_briefing", "question": "쌍수 붓기 빨리 빼는 법 있어?", "prior": "✕", "extractor_mentioned": false, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": false, "sentiment": "not_mentioned", "negative_type": "none", "aspects": [], "evidence": "", "recommendation": "not_applicable", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-15T15:23:53"}
|
||||
{"id": "D3-01", "engine": "naver_briefing", "question": "뷰성형외과 코성형 어때?", "prior": "△", "extractor_mentioned": true, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": true, "sentiment": "positive", "negative_type": "none", "aspects": ["procedure", "safety", "doctors", "price"], "evidence": "뷰성형외과의 코성형은 3D-CT 영상 분석을 기반으로 환자 개개인에게 맞춘 커스터마이징 보형물을 제작하는 것이 특징입니다.[1]", "recommendation": "recommended", "confidence": 0.95, "weak_mention": false, "ts": "2026-09-15T15:23:55"}
|
||||
{"id": "D3-02", "engine": "naver_briefing", "question": "코성형 재료 뭐뭐 있어?", "prior": "✕", "extractor_mentioned": false, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": false, "sentiment": "not_mentioned", "negative_type": "none", "aspects": [], "evidence": "", "recommendation": "not_applicable", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-15T15:23:57"}
|
||||
{"id": "D3-03", "engine": "naver_briefing", "question": "실리콘이랑 자가연골 중에 뭐가 나아?", "prior": "✕", "extractor_mentioned": false, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": false, "sentiment": "not_mentioned", "negative_type": "none", "aspects": [], "evidence": "", "recommendation": "not_applicable", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-15T15:23:58"}
|
||||
{"id": "D3-04", "engine": "naver_briefing", "question": "콧대만 할까 코끝까지 같이 할까?", "prior": "✕", "extractor_mentioned": false, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": false, "sentiment": "not_mentioned", "negative_type": "none", "aspects": [], "evidence": "", "recommendation": "not_applicable", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-15T15:23:59"}
|
||||
{"id": "D3-05", "engine": "naver_briefing", "question": "코성형 회복기간 얼마나 돼?", "prior": "✕", "extractor_mentioned": false, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": false, "sentiment": "not_mentioned", "negative_type": "none", "aspects": [], "evidence": "", "recommendation": "not_applicable", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-15T15:24:01"}
|
||||
{"id": "D3-06", "engine": "naver_briefing", "question": "코 재수술은 언제부터 할 수 있어?", "prior": "✕", "extractor_mentioned": false, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": false, "sentiment": "not_mentioned", "negative_type": "none", "aspects": [], "evidence": "", "recommendation": "not_applicable", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-15T15:24:02"}
|
||||
{"id": "D3-07", "engine": "naver_briefing", "question": "휜코 교정도 성형외과에서 해?", "prior": "✕", "extractor_mentioned": false, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": false, "sentiment": "not_mentioned", "negative_type": "none", "aspects": [], "evidence": "", "recommendation": "not_applicable", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-15T15:24:04"}
|
||||
{"id": "D3-08", "engine": "naver_briefing", "question": "복코 수술 어떻게 하는 거야?", "prior": "✕", "extractor_mentioned": false, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": false, "sentiment": "not_mentioned", "negative_type": "none", "aspects": [], "evidence": "", "recommendation": "not_applicable", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-15T15:24:05"}
|
||||
{"id": "D3-09", "engine": "naver_briefing", "question": "내 얼굴형에 맞는 코 모양 어떻게 정해?", "prior": "△", "extractor_mentioned": false, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": false, "sentiment": "not_mentioned", "negative_type": "none", "aspects": [], "evidence": "", "recommendation": "not_applicable", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-15T15:24:06"}
|
||||
{"id": "D4-01", "engine": "naver_briefing", "question": "뷰성형외과 안면윤곽 잘하는 곳 맞아?", "prior": "△", "extractor_mentioned": true, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": true, "sentiment": "positive", "negative_type": "none", "aspects": ["doctors", "procedure", "safety", "access"], "evidence": "해당 병원은 1:1 전담 마취시스템을 운영하며, 체계적인 업무 처리와 친절한 상담으로 알려져 있습니다.[6] [8]", "recommendation": "recommended", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-15T15:24:08"}
|
||||
{"id": "D4-02", "engine": "naver_briefing", "question": "광대축소 수술 어떻게 하는 거야?", "prior": "✕", "extractor_mentioned": false, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": false, "sentiment": "not_mentioned", "negative_type": "none", "aspects": [], "evidence": "", "recommendation": "not_applicable", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-15T15:24:09"}
|
||||
{"id": "D4-03", "engine": "naver_briefing", "question": "V라인 사각턱 수술 부작용 없어?", "prior": "✕", "extractor_mentioned": false, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": false, "sentiment": "not_mentioned", "negative_type": "none", "aspects": [], "evidence": "", "recommendation": "not_applicable", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-15T15:24:10"}
|
||||
{"id": "D4-04", "engine": "naver_briefing", "question": "양악수술은 어떤 경우에 해야 해?", "prior": "✕", "extractor_mentioned": false, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": false, "sentiment": "not_mentioned", "negative_type": "none", "aspects": [], "evidence": "", "recommendation": "not_applicable", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-15T15:24:12"}
|
||||
{"id": "D4-05", "engine": "naver_briefing", "question": "윤곽수술 신경 손상 위험 얼마나 돼?", "prior": "✕", "extractor_mentioned": false, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": false, "sentiment": "not_mentioned", "negative_type": "none", "aspects": [], "evidence": "", "recommendation": "not_applicable", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-15T15:24:13"}
|
||||
{"id": "D4-06", "engine": "naver_briefing", "question": "윤곽수술 회복기간 얼마나 걸려?", "prior": "✕", "extractor_mentioned": false, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": false, "sentiment": "not_mentioned", "negative_type": "none", "aspects": [], "evidence": "", "recommendation": "not_applicable", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-15T15:24:15"}
|
||||
{"id": "D4-07", "engine": "naver_briefing", "question": "윤곽수술 붓기 언제 빠져?", "prior": "✕", "extractor_mentioned": false, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": false, "sentiment": "not_mentioned", "negative_type": "none", "aspects": [], "evidence": "", "recommendation": "not_applicable", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-15T15:24:16"}
|
||||
{"id": "D4-08", "engine": "naver_briefing", "question": "윤곽이랑 코성형 같이 해도 돼?", "prior": "✕", "extractor_mentioned": false, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": false, "sentiment": "not_mentioned", "negative_type": "none", "aspects": [], "evidence": "", "recommendation": "not_applicable", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-15T15:24:18"}
|
||||
{"id": "D4-09", "engine": "naver_briefing", "question": "안면윤곽으로 상 받은 병원 있어?", "prior": "✕", "extractor_mentioned": false, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": false, "sentiment": "not_mentioned", "negative_type": "none", "aspects": [], "evidence": "", "recommendation": "not_applicable", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-15T15:24:19"}
|
||||
{"id": "D5-01", "engine": "naver_briefing", "question": "지방흡입 부위별로 뭐가 달라?", "prior": "✕", "extractor_mentioned": false, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": false, "sentiment": "not_mentioned", "negative_type": "none", "aspects": [], "evidence": "", "recommendation": "not_applicable", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-15T15:24:20"}
|
||||
{"id": "D5-02", "engine": "naver_briefing", "question": "얼굴 지방이식하면 자연스러워?", "prior": "✕", "extractor_mentioned": false, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": false, "sentiment": "not_mentioned", "negative_type": "none", "aspects": [], "evidence": "", "recommendation": "not_applicable", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-15T15:24:21"}
|
||||
{"id": "D5-03", "engine": "naver_briefing", "question": "지방이식 생착률 얼마나 돼?", "prior": "✕", "extractor_mentioned": false, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": false, "sentiment": "not_mentioned", "negative_type": "none", "aspects": [], "evidence": "", "recommendation": "not_applicable", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-15T15:24:22"}
|
||||
{"id": "D5-04", "engine": "naver_briefing", "question": "복부 지방흡입 회복 얼마나 걸려?", "prior": "✕", "extractor_mentioned": false, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": false, "sentiment": "not_mentioned", "negative_type": "none", "aspects": [], "evidence": "", "recommendation": "not_applicable", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-15T15:24:24"}
|
||||
{"id": "D5-05", "engine": "naver_briefing", "question": "뷰성형외과에서 지방성형도 해?", "prior": "△", "extractor_mentioned": true, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": true, "sentiment": "neutral", "negative_type": "none", "aspects": ["procedure"], "evidence": "뷰성형외과에서는 얼굴 지방흡입과 같은 지방 관련 시술을 제공하고 있습니다.[3]", "recommendation": "listed", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-15T15:24:25"}
|
||||
{"id": "D5-06", "engine": "naver_briefing", "question": "지방흡입 흉터 남아?", "prior": "✕", "extractor_mentioned": false, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": false, "sentiment": "not_mentioned", "negative_type": "none", "aspects": [], "evidence": "", "recommendation": "not_applicable", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-15T15:24:27"}
|
||||
{"id": "D6-01", "engine": "naver_briefing", "question": "리프팅 종류별로 뭐가 달라?", "prior": "✕", "extractor_mentioned": false, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": false, "sentiment": "not_mentioned", "negative_type": "none", "aspects": [], "evidence": "", "recommendation": "not_applicable", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-15T15:24:28"}
|
||||
{"id": "D6-02", "engine": "naver_briefing", "question": "실리프팅이랑 안면거상 중에 뭐 해야 해?", "prior": "✕", "extractor_mentioned": false, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": false, "sentiment": "not_mentioned", "negative_type": "none", "aspects": [], "evidence": "", "recommendation": "not_applicable", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-15T15:24:30"}
|
||||
{"id": "D6-03", "engine": "naver_briefing", "question": "울쎄라 효과 진짜 있어?", "prior": "✕", "extractor_mentioned": false, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": false, "sentiment": "not_mentioned", "negative_type": "none", "aspects": [], "evidence": "", "recommendation": "not_applicable", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-15T15:24:31"}
|
||||
{"id": "D6-04", "engine": "naver_briefing", "question": "리프팅 효과 얼마나 유지돼?", "prior": "✕", "extractor_mentioned": false, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": false, "sentiment": "not_mentioned", "negative_type": "none", "aspects": [], "evidence": "", "recommendation": "not_applicable", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-15T15:24:33"}
|
||||
{"id": "D6-05", "engine": "naver_briefing", "question": "40대인데 안티에이징 뭐부터 시작해?", "prior": "✕", "extractor_mentioned": false, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": false, "sentiment": "not_mentioned", "negative_type": "none", "aspects": [], "evidence": "", "recommendation": "not_applicable", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-15T15:24:34"}
|
||||
{"id": "D6-06", "engine": "naver_briefing", "question": "뷰성형외과에서 리프팅도 받을 수 있어?", "prior": "△", "extractor_mentioned": true, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": true, "sentiment": "positive", "negative_type": "none", "aspects": ["procedure", "doctors"], "evidence": "뷰성형외과는 안티에이징 성형을 포함한 여러 분야에서 진료를 제공하며, 티타늄 리프팅과 같은 시술에 대한 정보가 확인됩니다.[3] [8]", "recommendation": "recommended", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-15T15:24:35"}
|
||||
{"id": "D6-07", "engine": "naver_briefing", "question": "수술 흉터 관리 어떻게 해?", "prior": "✕", "extractor_mentioned": false, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": false, "sentiment": "not_mentioned", "negative_type": "none", "aspects": [], "evidence": "", "recommendation": "not_applicable", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-15T15:24:37"}
|
||||
{"id": "D7-01", "engine": "naver_briefing", "question": "재수술 전문 병원은 뭐가 달라?", "prior": "✕", "extractor_mentioned": false, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": false, "sentiment": "not_mentioned", "negative_type": "none", "aspects": [], "evidence": "", "recommendation": "not_applicable", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-15T15:24:39"}
|
||||
{"id": "D7-02", "engine": "naver_briefing", "question": "뷰성형외과 재수술 잘해?", "prior": "△", "extractor_mentioned": true, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": true, "sentiment": "positive", "negative_type": "none", "aspects": ["doctors", "safety", "procedure", "access"], "evidence": "뷰성형외과는 코, 윤곽, 눈 등 다양한 부위의 재수술에 대해 정밀 진단과 맞춤 솔루션을 제공하는 것으로 보입니다.", "recommendation": "recommended", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-15T15:24:40"}
|
||||
{"id": "D7-03", "engine": "naver_briefing", "question": "성형 재수술은 왜 하게 되는 거야?", "prior": "✕", "extractor_mentioned": false, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": false, "sentiment": "not_mentioned", "negative_type": "none", "aspects": [], "evidence": "", "recommendation": "not_applicable", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-15T15:24:42"}
|
||||
{"id": "D7-04", "engine": "naver_briefing", "question": "재수술은 처음보다 비용 더 비싸?", "prior": "✕", "extractor_mentioned": false, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": false, "sentiment": "not_mentioned", "negative_type": "none", "aspects": [], "evidence": "", "recommendation": "not_applicable", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-15T15:24:44"}
|
||||
{"id": "D7-05", "engine": "naver_briefing", "question": "재수술 하려면 첫 수술 후 얼마나 기다려야 해?", "prior": "✕", "extractor_mentioned": false, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": false, "sentiment": "not_mentioned", "negative_type": "none", "aspects": [], "evidence": "", "recommendation": "not_applicable", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-15T15:24:45"}
|
||||
{"id": "D7-06", "engine": "naver_briefing", "question": "다른 병원에서 한 수술도 재수술 상담해줘?", "prior": "✕", "extractor_mentioned": false, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": false, "sentiment": "not_mentioned", "negative_type": "none", "aspects": [], "evidence": "", "recommendation": "not_applicable", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-15T15:24:46"}
|
||||
{"id": "E-01", "engine": "naver_briefing", "question": "성형외과 고를 때 뭘 확인해야 해?", "prior": "✕", "extractor_mentioned": false, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": false, "sentiment": "not_mentioned", "negative_type": "none", "aspects": [], "evidence": "", "recommendation": "not_applicable", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-15T15:24:48"}
|
||||
{"id": "E-02", "engine": "naver_briefing", "question": "성형수술 마취 사고 안 나려면 어떤 병원 가야 해?", "prior": "✕", "extractor_mentioned": false, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": false, "sentiment": "not_mentioned", "negative_type": "none", "aspects": [], "evidence": "", "recommendation": "not_applicable", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-15T15:24:49"}
|
||||
{"id": "E-03", "engine": "naver_briefing", "question": "수술실 CCTV 있는지 어떻게 확인해?", "prior": "✕", "extractor_mentioned": false, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": false, "sentiment": "not_mentioned", "negative_type": "none", "aspects": [], "evidence": "", "recommendation": "not_applicable", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-15T15:24:50"}
|
||||
{"id": "E-04", "engine": "naver_briefing", "question": "전신마취랑 수면마취 차이가 뭐야?", "prior": "✕", "extractor_mentioned": false, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": false, "sentiment": "not_mentioned", "negative_type": "none", "aspects": [], "evidence": "", "recommendation": "not_applicable", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-15T15:24:52"}
|
||||
{"id": "E-05", "engine": "naver_briefing", "question": "성형외과 전문의인지 어떻게 확인해?", "prior": "✕", "extractor_mentioned": false, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": false, "sentiment": "not_mentioned", "negative_type": "none", "aspects": [], "evidence": "", "recommendation": "not_applicable", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-15T15:24:53"}
|
||||
{"id": "E-06", "engine": "naver_briefing", "question": "응급상황 대응 시스템 갖춘 성형외과 어디야?", "prior": "△", "extractor_mentioned": false, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": false, "sentiment": "not_mentioned", "negative_type": "none", "aspects": [], "evidence": "", "recommendation": "not_applicable", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-15T15:24:55"}
|
||||
{"id": "E-07", "engine": "naver_briefing", "question": "성형 부작용 생기면 어떻게 해야 해?", "prior": "✕", "extractor_mentioned": false, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": false, "sentiment": "not_mentioned", "negative_type": "none", "aspects": [], "evidence": "", "recommendation": "not_applicable", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-15T15:24:57"}
|
||||
{"id": "E-08", "engine": "naver_briefing", "question": "상담실장 말고 의사가 직접 상담해주는 병원 있어?", "prior": "✕", "extractor_mentioned": false, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": false, "sentiment": "not_mentioned", "negative_type": "none", "aspects": [], "evidence": "", "recommendation": "not_applicable", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-15T15:24:58"}
|
||||
{"id": "E-09", "engine": "naver_briefing", "question": "뷰성형외과 안전 시스템 어때?", "prior": "△", "extractor_mentioned": true, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": true, "sentiment": "positive", "negative_type": "none", "aspects": ["safety", "doctors", "procedure"], "evidence": "뷰성형외과는 첨단 시스템을 도입한 안전중심 스마트빌딩과 대학병원급 안전 장비 및 시설을 갖추고 안전한 성형을 제공하고 있습니다.[1] [2]", "recommendation": "recommended", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-15T15:25:00"}
|
||||
{"id": "F-01", "engine": "naver_briefing", "question": "강남 가슴성형 평균 비용 얼마야?", "prior": "✕", "extractor_mentioned": false, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": false, "sentiment": "not_mentioned", "negative_type": "none", "aspects": [], "evidence": "", "recommendation": "not_applicable", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-15T15:25:01"}
|
||||
{"id": "F-02", "engine": "naver_briefing", "question": "뷰성형외과 가슴성형 얼마야?", "prior": "△", "extractor_mentioned": true, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": true, "sentiment": "positive", "negative_type": "none", "aspects": ["doctors", "procedure", "reviews", "safety"], "evidence": "뷰성형외과는 모티바, 멘토, 세빈 등 다양한 종류의 보형물을 보유하고 있으며, 원내에 유방외과 전문의 등이 상주하여 검진을 진행합니다.[1] [3]", "recommendation": "recommended", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-15T15:25:02"}
|
||||
{"id": "F-03", "engine": "naver_briefing", "question": "쌍수 가격 보통 얼마 해?", "prior": "✕", "extractor_mentioned": false, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": false, "sentiment": "not_mentioned", "negative_type": "none", "aspects": [], "evidence": "", "recommendation": "not_applicable", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-15T15:25:04"}
|
||||
{"id": "F-04", "engine": "naver_briefing", "question": "코성형 견적 어느 정도 잡아야 해?", "prior": "✕", "extractor_mentioned": false, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": false, "sentiment": "not_mentioned", "negative_type": "none", "aspects": [], "evidence": "", "recommendation": "not_applicable", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-15T15:25:05"}
|
||||
{"id": "F-05", "engine": "naver_briefing", "question": "뷰성형외과 지금 하는 이벤트 있어?", "prior": "△", "extractor_mentioned": true, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": true, "sentiment": "neutral", "negative_type": "none", "aspects": ["price", "procedure", "access"], "evidence": "뷰성형외과에서 진행하는 이벤트 정보는 시술 종류와 시기에 따라 상이하므로, 정확한 내용은 병원에 직접 문의하시는 것이 가장 좋습니다.", "recommendation": "listed", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-15T15:25:08"}
|
||||
{"id": "F-06", "engine": "naver_briefing", "question": "성형외과 이벤트가 믿어도 돼? 싼 데는 왜 싸?", "prior": "✕", "extractor_mentioned": false, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": false, "sentiment": "not_mentioned", "negative_type": "none", "aspects": [], "evidence": "", "recommendation": "not_applicable", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-15T15:25:09"}
|
||||
{"id": "F-07", "engine": "naver_briefing", "question": "성형 비용 카드 할부나 분할 돼?", "prior": "✕", "extractor_mentioned": false, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": false, "sentiment": "not_mentioned", "negative_type": "none", "aspects": [], "evidence": "", "recommendation": "not_applicable", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-15T15:25:10"}
|
||||
{"id": "F-08", "engine": "naver_briefing", "question": "성형 견적 여러 병원 비교하려면 어떻게 해?", "prior": "✕", "extractor_mentioned": false, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": false, "sentiment": "not_mentioned", "negative_type": "none", "aspects": [], "evidence": "", "recommendation": "not_applicable", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-15T15:25:12"}
|
||||
{"id": "G-01", "engine": "naver_briefing", "question": "뷰성형외과 실제 후기 어때?", "prior": "△", "extractor_mentioned": true, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": true, "sentiment": "positive", "negative_type": "none", "aspects": ["doctors", "reviews", "procedure", "safety"], "evidence": "뷰성형외과에 대한 후기들은 의료진의 전문성, 친절한 응대 서비스, 그리고 수술 결과에 대한 전반적인 만족도를 보여주고 있습니다.", "recommendation": "recommended", "confidence": 0.98, "weak_mention": false, "ts": "2026-09-15T15:25:13"}
|
||||
{"id": "G-02", "engine": "naver_briefing", "question": "뷰성형외과 부작용 후기나 안 좋은 얘기 있어?", "prior": "✕", "extractor_mentioned": true, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": true, "sentiment": "negative", "negative_type": "reputation", "aspects": ["reviews", "regulatory", "safety"], "evidence": "일부 성형외과들이 수술비 할인 등의 경제적 대가를 받고 작성한 후기를 마치 자발적인 후기인 것처럼 게시하는 '뒷광고' 사례가 공정거래위원회에 의해 적발된 바 있습니다.[2] [9]", "recommendation": "cautioned", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-15T15:25:15"}
|
||||
{"id": "G-03", "engine": "naver_briefing", "question": "강남언니 후기 믿을만해?", "prior": "✕", "extractor_mentioned": false, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": false, "sentiment": "not_mentioned", "negative_type": "none", "aspects": [], "evidence": "", "recommendation": "not_applicable", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-15T15:25:16"}
|
||||
{"id": "G-04", "engine": "naver_briefing", "question": "뷰성형외과 수술 전후 사진 어디서 봐?", "prior": "△", "extractor_mentioned": true, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": true, "sentiment": "negative", "negative_type": "reputation", "aspects": ["reviews", "regulatory"], "evidence": "다만, 해당 홈페이지에 게재된 후기는 소정의 대가를 제공받아 작성된 후기임을 명시하고 있습니다.[1] [4]", "recommendation": "cautioned", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-15T15:25:18"}
|
||||
{"id": "G-05", "engine": "naver_briefing", "question": "성형 후기는 어디서 봐야 정확해?", "prior": "✕", "extractor_mentioned": false, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": false, "sentiment": "not_mentioned", "negative_type": "none", "aspects": [], "evidence": "", "recommendation": "not_applicable", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-15T15:25:20"}
|
||||
{"id": "G-06", "engine": "naver_briefing", "question": "뷰성형외과 인스타그램 있어?", "prior": "△", "extractor_mentioned": true, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": true, "sentiment": "positive", "negative_type": "none", "aspects": ["procedure", "reviews", "doctors", "safety", "media"], "evidence": "뷰성형외과는 강남의 대표적인 프리미엄 성형외과로, 가슴성형 분야에서 '모티바 최다 시술 병원'으로 선정된 바 있습니다.[8] [9]", "recommendation": "recommended", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-15T15:25:21"}
|
||||
{"id": "G-07", "engine": "naver_briefing", "question": "뷰성형외과는 뭘로 유명한 병원이야?", "prior": "△", "extractor_mentioned": true, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": true, "sentiment": "positive", "negative_type": "none", "aspects": ["doctors", "procedure", "safety", "access"], "evidence": "뷰성형외과는 눈밑지방재배치 수술을 비롯하여 가슴성형, 안면윤곽, 양악수술 등 다양한 분야에서 성형 종합병원으로서의 면모를 갖추고 있습니다.[3] [6]", "recommendation": "recommended", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-15T15:25:23"}
|
||||
{"id": "G-08", "engine": "naver_briefing", "question": "가슴성형 후기 많은 병원 어디야?", "prior": "△", "extractor_mentioned": false, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": false, "sentiment": "not_mentioned", "negative_type": "none", "aspects": [], "evidence": "", "recommendation": "not_applicable", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-15T15:25:24"}
|
||||
@ -1,120 +0,0 @@
|
||||
{"id": "A-01", "engine": "openai", "question": "뷰성형외과 어때? 믿을만한 병원이야?", "prior": "△", "extractor_mentioned": true, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": true, "sentiment": "negative", "negative_type": "regulatory", "aspects": ["reviews", "regulatory", "procedure", "safety", "doctors", "access"], "evidence": "그러나 2026년 7월 공정거래위원회의 제재 사례는, 병원 마케팅 방식에 대한 경계가 필요하다는 점을 보여줍니다.", "recommendation": "cautioned", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-09T10:34:55", "mention_kind": "strong"}
|
||||
{"id": "A-02", "engine": "openai", "question": "뷰성형외과는 어떤 병원이야? 소개해줘", "prior": "△", "extractor_mentioned": true, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": true, "sentiment": "positive", "negative_type": "none", "aspects": ["procedure", "safety", "doctors"], "evidence": "환자의 안전과 만족을 최우선으로 하며, 최신 기술과 장비를 사용하여 서비스를 제공한다고 광고합니다.", "recommendation": "recommended", "confidence": 0.95, "weak_mention": false, "ts": "2026-09-09T10:34:56", "mention_kind": "strong"}
|
||||
{"id": "A-03", "engine": "openai", "question": "뷰성형외과는 무슨 수술을 주로 해?", "prior": "△", "extractor_mentioned": true, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": true, "sentiment": "positive", "negative_type": "none", "aspects": ["doctors", "safety", "procedure", "access"], "evidence": "요약하자면, 뷰성형외과는 아래와 같은 분야에서 특히 강점을 가지고 있습니다:", "recommendation": "recommended", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-09T10:34:59", "mention_kind": "strong"}
|
||||
{"id": "A-04", "engine": "openai", "question": "뷰성형외과 최순우 원장 어떤 의사야?", "prior": "△", "extractor_mentioned": true, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": true, "sentiment": "positive", "negative_type": "none", "aspects": ["doctors", "safety", "procedure", "reviews", "media"], "evidence": "특히 협진 시스템, 정밀 검사, 안전 중심의 수술 시스템을 갖춘 병원을 운영하며, 의료관광 분야에서도 활동하는 등 의료계 내 다양한 영역에서 신뢰를 쌓아왔습니다.", "recommendation": "recommended", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-09T10:35:00", "mention_kind": "strong"}
|
||||
{"id": "A-05", "engine": "openai", "question": "뷰성형외과 의료진 몇 명이야? 다 전문의야?", "prior": "△", "extractor_mentioned": true, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": true, "sentiment": "neutral", "negative_type": "none", "aspects": ["doctors", "procedure", "safety"], "evidence": "의료진 수: **약 22명**\n- 이 중 성형외과 전문의 비율: **대부분이 성형외과 전문의**\n- 그 외: **이비인후과 전문의** (코 성형 담당), **마취통증의학과 전문의** (안전한 수술을 위한 상주 인력) 포함", "recommendation": "listed", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-09T10:35:02", "mention_kind": "strong"}
|
||||
{"id": "A-06", "engine": "openai", "question": "뷰성형외과가 렛미인 나온 병원 맞아?", "prior": "✕", "extractor_mentioned": true, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": true, "sentiment": "neutral", "negative_type": "none", "aspects": ["media"], "evidence": "즉, 뷰성형외과는 “렛미인” 프로그램에 **닥터스 참여 병원**으로 실제 출연한 경력이 있는 병원입니다.", "recommendation": "listed", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-09T10:35:04", "mention_kind": "strong"}
|
||||
{"id": "A-07", "engine": "openai", "question": "뷰성형외과 언제 생긴 병원이야? 오래됐어?", "prior": "✕", "extractor_mentioned": true, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": true, "sentiment": "neutral", "negative_type": "none", "aspects": ["regulatory", "safety"], "evidence": "정리하면, 뷰성형외과는 2005년에 문을 연 이후, 현재(2026년 기준) 약 21~22년 정도 운영되어 온 비교적 오랜 역사를 가진 병원입니다.", "recommendation": "listed", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-09T10:35:05", "mention_kind": "strong"}
|
||||
{"id": "A-08", "engine": "openai", "question": "뷰성형외과 건물이 통째로 병원이라던데 맞아?", "prior": "△", "extractor_mentioned": true, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": true, "sentiment": "neutral", "negative_type": "none", "aspects": ["access", "procedure"], "evidence": "뷰성형외과는 성형외과 진료만을 위한 **'19층 규모의 ALL‑IN‑ONE 스마트 빌딩'**으로 운영되고 있습니다.", "recommendation": "listed", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-09T10:35:07", "mention_kind": "strong"}
|
||||
{"id": "A-09", "engine": "openai", "question": "뷰성형외과 강남언니 평점 몇 점이야?", "prior": "◯", "extractor_mentioned": true, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": true, "sentiment": "neutral", "negative_type": "none", "aspects": ["reviews"], "evidence": "강남언니 내 병원 추천 목록에서는 ‘뷰성형외과’ 가 9.5점 평점을 기록 중이며, 약 17,099건의 평가가 있는 것으로 나타났습니다", "recommendation": "listed", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-09T10:35:09", "mention_kind": "strong"}
|
||||
{"id": "A-10", "engine": "openai", "question": "뷰성형외과 유튜브 채널 있어?", "prior": "◯", "extractor_mentioned": true, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": true, "sentiment": "neutral", "negative_type": "none", "aspects": ["media"], "evidence": "뷰성형외과(‘VIEW Plastic Surgery’)는 유튜브에서 공식 채널을 운영하고 있습니다.", "recommendation": "listed", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-09T10:35:10", "mention_kind": "strong"}
|
||||
{"id": "A-11", "engine": "openai", "question": "뷰성형외과랑 다른 강남 대형 성형외과랑 뭐가 달라?", "prior": "✕", "extractor_mentioned": true, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": true, "sentiment": "neutral", "negative_type": "none", "aspects": ["doctors", "safety", "reviews", "procedure", "access"], "evidence": "뷰성형외과는 다른 강남의 대형 성형외과와 비교해 몇 가지 차별점이 있을 수 있습니다. 하지만 정확한 차별점을 알기 위해서는 최신 정보를 확인하는 것이 중요합니다.", "recommendation": "listed", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-09T10:35:12", "mention_kind": "strong"}
|
||||
{"id": "A-12", "engine": "openai", "question": "뷰성형외과 단점이나 주의할 점 있어?", "prior": "✕", "extractor_mentioned": true, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": true, "sentiment": "neutral", "negative_type": "none", "aspects": [], "evidence": "뷰성형외과에 대한 구체적인 단점이나 주의할 점에 관해서는 인터넷 검색이나 직접적인 이용 후기를 참조하는 것이 가장 좋습니다.", "recommendation": "listed", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-09T10:35:13", "mention_kind": "strong"}
|
||||
{"id": "B-01", "engine": "openai", "question": "뷰성형외과 주소 어디야?", "prior": "◯", "extractor_mentioned": true, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": true, "sentiment": "neutral", "negative_type": "none", "aspects": ["access"], "evidence": "뷰성형외과(“뷰성형외과의원”)의 주소는 아래와 같습니다:", "recommendation": "listed", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-09T10:35:15", "mention_kind": "strong"}
|
||||
{"id": "B-02", "engine": "openai", "question": "뷰성형외과 신논현역에서 얼마나 걸려?", "prior": "◯", "extractor_mentioned": true, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": true, "sentiment": "neutral", "negative_type": "none", "aspects": ["access"], "evidence": "신논현역에서 뷰성형외과까지의 거리는 걸어서 약 5분에서 10분 정도 소요될 수 있습니다.", "recommendation": "listed", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-09T10:35:16", "mention_kind": "strong"}
|
||||
{"id": "B-03", "engine": "openai", "question": "뷰성형외과 전화번호 알려줘", "prior": "△", "extractor_mentioned": true, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": true, "sentiment": "neutral", "negative_type": "none", "aspects": ["access"], "evidence": "아래는 서울 강남구 논현동에 위치한 **뷰성형외과의 공식 전화번호** 정보입니다:", "recommendation": "listed", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-09T10:35:18", "mention_kind": "strong"}
|
||||
{"id": "B-04", "engine": "openai", "question": "뷰성형외과 진료시간 어떻게 돼?", "prior": "△", "extractor_mentioned": true, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": true, "sentiment": "neutral", "negative_type": "none", "aspects": ["access"], "evidence": "다음은 서울 강남구 논현동에 위치한 **뷰성형외과의 진료 시간 안내**입니다.", "recommendation": "listed", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-09T10:35:19", "mention_kind": "strong"}
|
||||
{"id": "B-05", "engine": "openai", "question": "뷰성형외과 일요일이나 공휴일에도 해?", "prior": "✕", "extractor_mentioned": true, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": true, "sentiment": "neutral", "negative_type": "none", "aspects": ["access"], "evidence": "일요일 진료: 별도로 안내된 정보가 없으며, 일반적인 진료시간표상 일요일에는 휴진일 가능성이 높습니다.", "recommendation": "listed", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-09T10:35:21", "mention_kind": "strong"}
|
||||
{"id": "B-06", "engine": "openai", "question": "뷰성형외과 주차 되나?", "prior": "✕", "extractor_mentioned": true, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": true, "sentiment": "neutral", "negative_type": "none", "aspects": ["access"], "evidence": "뷰성형외과(강남구 봉은사로 107, 신논현역 인근)는 **병원 건물 내부에 전용 주차시설이 있는 것으로 보이지 않습니다**.", "recommendation": "listed", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-09T10:35:23", "mention_kind": "strong"}
|
||||
{"id": "B-07", "engine": "openai", "question": "뷰성형외과 상담 예약 어떻게 해?", "prior": "△", "extractor_mentioned": true, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": true, "sentiment": "neutral", "negative_type": "none", "aspects": ["access"], "evidence": "정확한 정보를 위해 뷰성형외과의 최신 연락처나 예약 가능 방법을 직접 확인하는 것이 좋습니다.", "recommendation": "listed", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-09T10:35:24", "mention_kind": "strong"}
|
||||
{"id": "B-08", "engine": "openai", "question": "뷰성형외과 카톡으로 상담돼?", "prior": "△", "extractor_mentioned": true, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": true, "sentiment": "neutral", "negative_type": "none", "aspects": ["access"], "evidence": "요약하자면, 뷰성형외과는 카카오톡 채널을 운영하고 있으며, 이를 통해 상담과 예약 문의가 가능합니다.", "recommendation": "listed", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-09T10:35:26", "mention_kind": "strong"}
|
||||
{"id": "B-09", "engine": "openai", "question": "뷰성형외과 상담받으러 가면 몇 층으로 가?", "prior": "△", "extractor_mentioned": true, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": true, "sentiment": "neutral", "negative_type": "none", "aspects": ["access"], "evidence": "즉, 처음 뷰성형외과 상담을 받으러 가시면 **1층에서 접수 → (지하 촬영) → 4층으로 올라가 상담 대기 및 상담**의 순서로 이동하게 됩니다.", "recommendation": "listed", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-09T10:35:27", "mention_kind": "strong"}
|
||||
{"id": "B-10", "engine": "openai", "question": "신논현역 3번 출구 근처 성형외과 뭐 있어?", "prior": "◯", "extractor_mentioned": true, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": true, "sentiment": "neutral", "negative_type": "none", "aspects": ["access", "procedure"], "evidence": "- **VIEW Plastic Surgery (뷰 성형외과)**\n - 위치: 신논현역 3번 출구에서 약 50m 거리, 정말 가까움 ([viewplasticsurgery.com](https://www.viewplasticsurgery.com/introduction/direction/?utm_source=openai)).\n - 진료 시간: 평일 10:00–19:00, 금요일은 21:00까지, 토요일 10:00–17:00 ([viewplasticsurgery.com](https://www.viewplasticsurgery.com/introduction/direction/?utm_source=openai)).\n - 특징: 19층 규모의 병원 형태로, 전문성 높은 팀을 구성하여 다부위 수술 및 복합 시술에 적합", "recommendation": "listed", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-09T10:35:30", "mention_kind": "strong"}
|
||||
{"id": "C-01", "engine": "openai", "question": "강남 성형외과 추천해줘", "prior": "✕", "extractor_mentioned": true, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": true, "sentiment": "positive", "negative_type": "none", "aspects": ["reviews"], "evidence": "The Door Plastic Surgery, View Plastic Surgery 등도 평점 4.8로 높은 평가받고 있습니다 ([gangnambeautyguide.com](https://gangnambeautyguide.com/en/clinics/?utm_source=openai)).", "recommendation": "recommended", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-09T10:35:31", "mention_kind": "strong"}
|
||||
{"id": "C-02", "engine": "openai", "question": "강남에서 가슴성형 잘하는 병원 어디야?", "prior": "✕", "extractor_mentioned": true, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": true, "sentiment": "positive", "negative_type": "none", "aspects": ["safety", "doctors", "procedure", "access", "reviews"], "evidence": "뷰성형외과 - 협진 체계, 안전 장비, 1:1 진료 및 사후관리 강조", "recommendation": "recommended", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-09T10:35:33", "mention_kind": "strong"}
|
||||
{"id": "C-03", "engine": "openai", "question": "강남 안면윤곽 유명한 병원 알려줘", "prior": "✕", "extractor_mentioned": false, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": false, "sentiment": "not_mentioned", "negative_type": "none", "aspects": [], "evidence": "", "recommendation": "not_applicable", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-09T10:35:34", "mention_kind": "none"}
|
||||
{"id": "C-04", "engine": "openai", "question": "코성형 잘하는 강남 병원 추천해줘", "prior": "✕", "extractor_mentioned": true, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": true, "sentiment": "positive", "negative_type": "none", "aspects": ["doctors", "safety", "procedure", "access", "reviews"], "evidence": "뷰성형외과 강남 | 협진 시스템, 맞춤 설계, 대학병원급 안전 시스템", "recommendation": "recommended", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-09T10:35:36", "mention_kind": "strong"}
|
||||
{"id": "C-05", "engine": "openai", "question": "눈 재수술 잘하는 병원 어디야?", "prior": "✕", "extractor_mentioned": false, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": false, "sentiment": "not_mentioned", "negative_type": "none", "aspects": [], "evidence": "", "recommendation": "not_applicable", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-09T10:35:37", "mention_kind": "none"}
|
||||
{"id": "C-06", "engine": "openai", "question": "강남 성형외과 톱5 알려줘", "prior": "✕", "extractor_mentioned": true, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": true, "sentiment": "positive", "negative_type": "none", "aspects": ["reviews", "procedure"], "evidence": "- 평점: 4.8 / 리뷰수: 약 9,167건 ([gangnambeautyguide.com](https://gangnambeautyguide.com/en/clinics/?utm_source=openai)) \n- 강점: 유방성형 및 안면 윤곽 전문, 넓은 리뷰 기반", "recommendation": "recommended", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-09T10:35:39", "mention_kind": "strong"}
|
||||
{"id": "C-07", "engine": "openai", "question": "가슴성형 병원 3곳만 골라서 비교해줘", "prior": "✕", "extractor_mentioned": false, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": false, "sentiment": "not_mentioned", "negative_type": "none", "aspects": [], "evidence": "", "recommendation": "not_applicable", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-09T10:35:41", "mention_kind": "none"}
|
||||
{"id": "C-08", "engine": "openai", "question": "마취과 전문의가 상주하는 강남 성형외과 어디야?", "prior": "△", "extractor_mentioned": true, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": true, "sentiment": "positive", "negative_type": "none", "aspects": ["doctors", "safety", "procedure"], "evidence": "- 안면윤곽, 눈·코·가슴 성형 등을 전문으로 하며, **마취과 전문의가 365일 상주**하고 있다는 점을 강조합니다 ([blog.viewclinic.com](https://blog.viewclinic.com/?utm_source=openai)).\n- 3D-CT 등 정밀 골격 분석 장비와 체계적 사후 관리 체계를 갖추고 있는 병원입니다 ([blog.viewclinic.com](https://blog.viewclinic.com/?utm_source=openai)).", "recommendation": "recommended", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-09T10:35:42", "mention_kind": "strong"}
|
||||
{"id": "C-09", "engine": "openai", "question": "수술실 CCTV 있는 성형외과 추천해줘", "prior": "△", "extractor_mentioned": false, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": false, "sentiment": "not_mentioned", "negative_type": "none", "aspects": [], "evidence": "", "recommendation": "not_applicable", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-09T10:35:44", "mention_kind": "none"}
|
||||
{"id": "C-10", "engine": "openai", "question": "여의사한테 수술받을 수 있는 성형외과 있어?", "prior": "△", "extractor_mentioned": false, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": false, "sentiment": "not_mentioned", "negative_type": "none", "aspects": [], "evidence": "", "recommendation": "not_applicable", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-09T10:35:45", "mention_kind": "none"}
|
||||
{"id": "C-11", "engine": "openai", "question": "후기 제일 많은 강남 성형외과 어디야?", "prior": "△", "extractor_mentioned": true, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": true, "sentiment": "negative", "negative_type": "regulatory", "aspects": ["regulatory", "reviews", "price"], "evidence": "예컨대 ‘뷰성형외과’, '에이비성형외과의원', '디에이성형외과'는 후기를 조건으로 수술비 일부를 할인해 주고 광고처럼 이용한 사례로 지적된 바 있습니다 ([v.daum.net](https://v.daum.net/v/QaV6Ond8j0?utm_source=openai)).", "recommendation": "cautioned", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-09T10:35:46", "mention_kind": "strong"}
|
||||
{"id": "C-12", "engine": "openai", "question": "강남언니에서 평점 높은 성형외과 알려줘", "prior": "△", "extractor_mentioned": true, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": true, "sentiment": "positive", "negative_type": "none", "aspects": ["reviews"], "evidence": "**팝성형외과**, **티에스성형외과**, **기린성형외과**, **제이필의원**, **뷰성형외과**, **뭐 등 다양한 병원들이 높은 평점을 보이고 있습니다.", "recommendation": "recommended", "confidence": 0.95, "weak_mention": false, "ts": "2026-09-09T10:35:48", "mention_kind": "strong"}
|
||||
{"id": "C-13", "engine": "openai", "question": "20년 넘게 운영한 강남 성형외과 있어?", "prior": "✕", "extractor_mentioned": false, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": false, "sentiment": "not_mentioned", "negative_type": "none", "aspects": [], "evidence": "", "recommendation": "not_applicable", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-09T10:35:49", "mention_kind": "none"}
|
||||
{"id": "C-14", "engine": "openai", "question": "리프팅 잘하는 강남 병원 추천해줘", "prior": "✕", "extractor_mentioned": true, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": false, "sentiment": "not_mentioned", "negative_type": "none", "aspects": [], "evidence": "", "recommendation": "not_applicable", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-09T10:35:51", "mention_kind": "none"}
|
||||
{"id": "C-15", "engine": "openai", "question": "의사 여러 명 있는 대형 성형외과가 나아? 어디 추천해?", "prior": "✕", "extractor_mentioned": false, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": false, "sentiment": "not_mentioned", "negative_type": "none", "aspects": [], "evidence": "", "recommendation": "not_applicable", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-09T10:35:52", "mention_kind": "none"}
|
||||
{"id": "D1-01", "engine": "openai", "question": "뷰성형외과 가슴성형 잘해?", "prior": "△", "extractor_mentioned": true, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": true, "sentiment": "positive", "negative_type": "none", "aspects": ["doctors", "safety", "price", "reviews", "procedure"], "evidence": "뷰성형외과는 가슴성형 분야에서 경험, 전문성, 안전성, 사후관리 체계를 갖춘 병원으로 평가됩니다. 많은 환자들이 만족스럽다는 후기도 많으며, 개인의 체형·희망·상태에 맞춘 맞춤 상담과 전문적인 진료 흐름이 강점입니다.", "recommendation": "recommended", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-09T10:35:54", "mention_kind": "strong"}
|
||||
{"id": "D1-02", "engine": "openai", "question": "가슴 보형물 종류 뭐가 있어?", "prior": "✕", "extractor_mentioned": false, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": false, "sentiment": "not_mentioned", "negative_type": "none", "aspects": [], "evidence": "", "recommendation": "not_applicable", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-09T10:35:55", "mention_kind": "none"}
|
||||
{"id": "D1-03", "engine": "openai", "question": "모티바랑 멘토 보형물 차이가 뭐야?", "prior": "✕", "extractor_mentioned": false, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": false, "sentiment": "not_mentioned", "negative_type": "none", "aspects": [], "evidence": "", "recommendation": "not_applicable", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-09T10:35:56", "mention_kind": "none"}
|
||||
{"id": "D1-04", "engine": "openai", "question": "가슴성형 수술시간 얼마나 걸려?", "prior": "✕", "extractor_mentioned": false, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": false, "sentiment": "not_mentioned", "negative_type": "none", "aspects": [], "evidence": "", "recommendation": "not_applicable", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-09T10:35:57", "mention_kind": "none"}
|
||||
{"id": "D1-05", "engine": "openai", "question": "가슴성형 회복기간 어느 정도야? 언제 출근해?", "prior": "✕", "extractor_mentioned": false, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": false, "sentiment": "not_mentioned", "negative_type": "none", "aspects": [], "evidence": "", "recommendation": "not_applicable", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-09T10:35:59", "mention_kind": "none"}
|
||||
{"id": "D1-06", "engine": "openai", "question": "가슴성형 흉터 어디에 남아?", "prior": "✕", "extractor_mentioned": false, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": false, "sentiment": "not_mentioned", "negative_type": "none", "aspects": [], "evidence": "", "recommendation": "not_applicable", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-09T10:36:00", "mention_kind": "none"}
|
||||
{"id": "D1-07", "engine": "openai", "question": "구형구축이 뭐야? 예방할 수 있어?", "prior": "✕", "extractor_mentioned": false, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": false, "sentiment": "not_mentioned", "negative_type": "none", "aspects": [], "evidence": "", "recommendation": "not_applicable", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-09T10:36:01", "mention_kind": "none"}
|
||||
{"id": "D1-08", "engine": "openai", "question": "가슴 지방이식이랑 보형물 중에 뭐가 나아?", "prior": "✕", "extractor_mentioned": false, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": false, "sentiment": "not_mentioned", "negative_type": "none", "aspects": [], "evidence": "", "recommendation": "not_applicable", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-09T10:36:02", "mention_kind": "none"}
|
||||
{"id": "D1-09", "engine": "openai", "question": "가슴 재수술은 언제 해야 해?", "prior": "✕", "extractor_mentioned": false, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": false, "sentiment": "not_mentioned", "negative_type": "none", "aspects": [], "evidence": "", "recommendation": "not_applicable", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-09T10:36:04", "mention_kind": "none"}
|
||||
{"id": "D1-10", "engine": "openai", "question": "가슴성형하면 수유에 영향 있어?", "prior": "✕", "extractor_mentioned": false, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": false, "sentiment": "not_mentioned", "negative_type": "none", "aspects": [], "evidence": "", "recommendation": "not_applicable", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-09T10:36:05", "mention_kind": "none"}
|
||||
{"id": "D1-11", "engine": "openai", "question": "가슴성형 후 정기검진 뭐 받아야 해?", "prior": "✕", "extractor_mentioned": false, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": false, "sentiment": "not_mentioned", "negative_type": "none", "aspects": [], "evidence": "", "recommendation": "not_applicable", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-09T10:36:06", "mention_kind": "none"}
|
||||
{"id": "D1-12", "engine": "openai", "question": "가슴성형 과정 잘 설명해주는 영상 있어?", "prior": "△", "extractor_mentioned": false, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": false, "sentiment": "not_mentioned", "negative_type": "none", "aspects": [], "evidence": "", "recommendation": "not_applicable", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-09T10:36:08", "mention_kind": "none"}
|
||||
{"id": "D2-01", "engine": "openai", "question": "뷰성형외과 눈성형 어때?", "prior": "△", "extractor_mentioned": true, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": true, "sentiment": "positive", "negative_type": "none", "aspects": ["doctors", "safety", "price", "reviews", "procedure", "access"], "evidence": "장점:\n- 높은 평점(4.7)과 JCI 인증, 다국어 지원으로 의료 신뢰도 높음 ([getcarekorea.com](https://getcarekorea.com/ko/hospitals/view-plastic-surgery?utm_source=openai))\n- 눈 성형 종류 다양하고 비교적 합리적인 가격대 ([beauty6.tistory.com](https://beauty6.tistory.com/184?utm_source=openai))\n- 실제 환자들이 자연스러운 결과, 빠른 회복, 친절한 간호/진료 서비스에 만족 ([cashdoc.me](https://cashdoc.me/community/plasticsurgery/89688246?utm_source=openai))", "recommendation": "recommended", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-09T10:36:10", "mention_kind": "strong"}
|
||||
{"id": "D2-02", "engine": "openai", "question": "쌍수 절개랑 매몰 차이가 뭐야?", "prior": "✕", "extractor_mentioned": false, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": false, "sentiment": "not_mentioned", "negative_type": "none", "aspects": [], "evidence": "", "recommendation": "not_applicable", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-09T10:36:14", "mention_kind": "none"}
|
||||
{"id": "D2-03", "engine": "openai", "question": "자연유착 쌍수가 뭐야?", "prior": "✕", "extractor_mentioned": false, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": false, "sentiment": "not_mentioned", "negative_type": "none", "aspects": [], "evidence": "", "recommendation": "not_applicable", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-09T10:36:15", "mention_kind": "none"}
|
||||
{"id": "D2-04", "engine": "openai", "question": "눈매교정은 쌍수랑 같이 해야 해?", "prior": "✕", "extractor_mentioned": false, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": false, "sentiment": "not_mentioned", "negative_type": "none", "aspects": [], "evidence": "", "recommendation": "not_applicable", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-09T10:36:16", "mention_kind": "none"}
|
||||
{"id": "D2-05", "engine": "openai", "question": "앞트임 뒤트임 밑트임 뭐가 달라?", "prior": "✕", "extractor_mentioned": false, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": false, "sentiment": "not_mentioned", "negative_type": "none", "aspects": [], "evidence": "", "recommendation": "not_applicable", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-09T10:36:17", "mention_kind": "none"}
|
||||
{"id": "D2-06", "engine": "openai", "question": "쌍수 회복기간 얼마나 걸려?", "prior": "✕", "extractor_mentioned": false, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": false, "sentiment": "not_mentioned", "negative_type": "none", "aspects": [], "evidence": "", "recommendation": "not_applicable", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-09T10:36:19", "mention_kind": "none"}
|
||||
{"id": "D2-07", "engine": "openai", "question": "눈 재수술은 첫 수술 후 언제부터 가능해?", "prior": "✕", "extractor_mentioned": false, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": false, "sentiment": "not_mentioned", "negative_type": "none", "aspects": [], "evidence": "", "recommendation": "not_applicable", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-09T10:36:20", "mention_kind": "none"}
|
||||
{"id": "D2-08", "engine": "openai", "question": "앞트임하면 잘 어울리는 얼굴이 따로 있어?", "prior": "△", "extractor_mentioned": false, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": false, "sentiment": "not_mentioned", "negative_type": "none", "aspects": [], "evidence": "", "recommendation": "not_applicable", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-09T10:36:23", "mention_kind": "none"}
|
||||
{"id": "D2-09", "engine": "openai", "question": "쌍수 붓기 빨리 빼는 법 있어?", "prior": "✕", "extractor_mentioned": false, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": false, "sentiment": "not_mentioned", "negative_type": "none", "aspects": [], "evidence": "", "recommendation": "not_applicable", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-09T10:36:24", "mention_kind": "none"}
|
||||
{"id": "D3-01", "engine": "openai", "question": "뷰성형외과 코성형 어때?", "prior": "△", "extractor_mentioned": true, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": true, "sentiment": "neutral", "negative_type": "none", "aspects": ["reviews", "doctors", "procedure"], "evidence": "뷰성형외과의 코성형에 대한 평가는 일반적으로 병원의 평판, 의사들의 경험, 수술 후 환자들의 후기 등을 바탕으로 이루어집니다.", "recommendation": "listed", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-09T10:36:25", "mention_kind": "strong"}
|
||||
{"id": "D3-02", "engine": "openai", "question": "코성형 재료 뭐뭐 있어?", "prior": "✕", "extractor_mentioned": false, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": false, "sentiment": "not_mentioned", "negative_type": "none", "aspects": [], "evidence": "", "recommendation": "not_applicable", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-09T10:36:26", "mention_kind": "none"}
|
||||
{"id": "D3-03", "engine": "openai", "question": "실리콘이랑 자가연골 중에 뭐가 나아?", "prior": "✕", "extractor_mentioned": false, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": false, "sentiment": "not_mentioned", "negative_type": "none", "aspects": [], "evidence": "", "recommendation": "not_applicable", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-09T10:36:28", "mention_kind": "none"}
|
||||
{"id": "D3-04", "engine": "openai", "question": "콧대만 할까 코끝까지 같이 할까?", "prior": "✕", "extractor_mentioned": false, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": false, "sentiment": "not_mentioned", "negative_type": "none", "aspects": [], "evidence": "", "recommendation": "not_applicable", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-09T10:36:29", "mention_kind": "none"}
|
||||
{"id": "D3-05", "engine": "openai", "question": "코성형 회복기간 얼마나 돼?", "prior": "✕", "extractor_mentioned": false, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": false, "sentiment": "not_mentioned", "negative_type": "none", "aspects": [], "evidence": "", "recommendation": "not_applicable", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-09T10:36:30", "mention_kind": "none"}
|
||||
{"id": "D3-06", "engine": "openai", "question": "코 재수술은 언제부터 할 수 있어?", "prior": "✕", "extractor_mentioned": false, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": false, "sentiment": "not_mentioned", "negative_type": "none", "aspects": [], "evidence": "", "recommendation": "not_applicable", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-09T10:36:32", "mention_kind": "none"}
|
||||
{"id": "D3-07", "engine": "openai", "question": "휜코 교정도 성형외과에서 해?", "prior": "✕", "extractor_mentioned": false, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": false, "sentiment": "not_mentioned", "negative_type": "none", "aspects": [], "evidence": "", "recommendation": "not_applicable", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-09T10:36:33", "mention_kind": "none"}
|
||||
{"id": "D3-08", "engine": "openai", "question": "복코 수술 어떻게 하는 거야?", "prior": "✕", "extractor_mentioned": false, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": false, "sentiment": "not_mentioned", "negative_type": "none", "aspects": [], "evidence": "", "recommendation": "not_applicable", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-09T10:36:34", "mention_kind": "none"}
|
||||
{"id": "D3-09", "engine": "openai", "question": "내 얼굴형에 맞는 코 모양 어떻게 정해?", "prior": "△", "extractor_mentioned": false, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": false, "sentiment": "not_mentioned", "negative_type": "none", "aspects": [], "evidence": "", "recommendation": "not_applicable", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-09T10:36:35", "mention_kind": "none"}
|
||||
{"id": "D4-01", "engine": "openai", "question": "뷰성형외과 안면윤곽 잘하는 곳 맞아?", "prior": "△", "extractor_mentioned": true, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": true, "sentiment": "positive", "negative_type": "none", "aspects": ["doctors", "safety", "reviews", "procedure", "media"], "evidence": "전반적으로, 뷰성형외과는 안면윤곽 수술에 있어 안전성과 전문성을 갖춘 병원으로 평가받고 있습니다. 특히 수술 기법과 시스템, 다수의 수상 경력과 방송 노출 경력 등이 신뢰감을 줍니다.", "recommendation": "recommended", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-09T10:36:37", "mention_kind": "strong"}
|
||||
{"id": "D4-02", "engine": "openai", "question": "광대축소 수술 어떻게 하는 거야?", "prior": "✕", "extractor_mentioned": false, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": false, "sentiment": "not_mentioned", "negative_type": "none", "aspects": [], "evidence": "", "recommendation": "not_applicable", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-09T10:36:39", "mention_kind": "none"}
|
||||
{"id": "D4-03", "engine": "openai", "question": "V라인 사각턱 수술 부작용 없어?", "prior": "✕", "extractor_mentioned": false, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": false, "sentiment": "not_mentioned", "negative_type": "none", "aspects": [], "evidence": "", "recommendation": "not_applicable", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-09T10:36:40", "mention_kind": "none"}
|
||||
{"id": "D4-04", "engine": "openai", "question": "양악수술은 어떤 경우에 해야 해?", "prior": "✕", "extractor_mentioned": false, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": false, "sentiment": "not_mentioned", "negative_type": "none", "aspects": [], "evidence": "", "recommendation": "not_applicable", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-09T10:36:41", "mention_kind": "none"}
|
||||
{"id": "D4-05", "engine": "openai", "question": "윤곽수술 신경 손상 위험 얼마나 돼?", "prior": "✕", "extractor_mentioned": false, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": false, "sentiment": "not_mentioned", "negative_type": "none", "aspects": [], "evidence": "", "recommendation": "not_applicable", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-09T10:36:43", "mention_kind": "none"}
|
||||
{"id": "D4-06", "engine": "openai", "question": "윤곽수술 회복기간 얼마나 걸려?", "prior": "✕", "extractor_mentioned": false, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": false, "sentiment": "not_mentioned", "negative_type": "none", "aspects": [], "evidence": "", "recommendation": "not_applicable", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-09T10:36:44", "mention_kind": "none"}
|
||||
{"id": "D4-07", "engine": "openai", "question": "윤곽수술 붓기 언제 빠져?", "prior": "✕", "extractor_mentioned": false, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": false, "sentiment": "not_mentioned", "negative_type": "none", "aspects": [], "evidence": "", "recommendation": "not_applicable", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-09T10:36:45", "mention_kind": "none"}
|
||||
{"id": "D4-08", "engine": "openai", "question": "윤곽이랑 코성형 같이 해도 돼?", "prior": "✕", "extractor_mentioned": false, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": false, "sentiment": "not_mentioned", "negative_type": "none", "aspects": [], "evidence": "", "recommendation": "not_applicable", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-09T10:36:46", "mention_kind": "none"}
|
||||
{"id": "D4-09", "engine": "openai", "question": "안면윤곽으로 상 받은 병원 있어?", "prior": "✕", "extractor_mentioned": false, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": false, "sentiment": "not_mentioned", "negative_type": "none", "aspects": [], "evidence": "", "recommendation": "not_applicable", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-09T10:36:48", "mention_kind": "none"}
|
||||
{"id": "D5-01", "engine": "openai", "question": "지방흡입 부위별로 뭐가 달라?", "prior": "✕", "extractor_mentioned": false, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": false, "sentiment": "not_mentioned", "negative_type": "none", "aspects": [], "evidence": "", "recommendation": "not_applicable", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-09T10:36:49", "mention_kind": "none"}
|
||||
{"id": "D5-02", "engine": "openai", "question": "얼굴 지방이식하면 자연스러워?", "prior": "✕", "extractor_mentioned": false, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": false, "sentiment": "not_mentioned", "negative_type": "none", "aspects": [], "evidence": "", "recommendation": "not_applicable", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-09T10:36:51", "mention_kind": "none"}
|
||||
{"id": "D5-03", "engine": "openai", "question": "지방이식 생착률 얼마나 돼?", "prior": "✕", "extractor_mentioned": false, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": false, "sentiment": "not_mentioned", "negative_type": "none", "aspects": [], "evidence": "", "recommendation": "not_applicable", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-09T10:36:52", "mention_kind": "none"}
|
||||
{"id": "D5-04", "engine": "openai", "question": "복부 지방흡입 회복 얼마나 걸려?", "prior": "✕", "extractor_mentioned": false, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": false, "sentiment": "not_mentioned", "negative_type": "none", "aspects": [], "evidence": "", "recommendation": "not_applicable", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-09T10:36:53", "mention_kind": "none"}
|
||||
{"id": "D5-05", "engine": "openai", "question": "뷰성형외과에서 지방성형도 해?", "prior": "△", "extractor_mentioned": true, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": true, "sentiment": "neutral", "negative_type": "none", "aspects": [], "evidence": "뷰성형외과에서 지방 성형을 하는지 확인하려면, 해당 병원의 웹사이트를 방문하거나 직접 문의하는 것이 가장 정확합니다.", "recommendation": "not_applicable", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-09T10:36:54", "mention_kind": "strong"}
|
||||
{"id": "D5-06", "engine": "openai", "question": "지방흡입 흉터 남아?", "prior": "✕", "extractor_mentioned": false, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": false, "sentiment": "not_mentioned", "negative_type": "none", "aspects": [], "evidence": "", "recommendation": "not_applicable", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-09T10:36:56", "mention_kind": "none"}
|
||||
{"id": "D6-01", "engine": "openai", "question": "리프팅 종류별로 뭐가 달라?", "prior": "✕", "extractor_mentioned": false, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": false, "sentiment": "not_mentioned", "negative_type": "none", "aspects": [], "evidence": "", "recommendation": "not_applicable", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-09T10:36:58", "mention_kind": "none"}
|
||||
{"id": "D6-02", "engine": "openai", "question": "실리프팅이랑 안면거상 중에 뭐 해야 해?", "prior": "✕", "extractor_mentioned": false, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": false, "sentiment": "not_mentioned", "negative_type": "none", "aspects": [], "evidence": "", "recommendation": "not_applicable", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-09T10:37:00", "mention_kind": "none"}
|
||||
{"id": "D6-03", "engine": "openai", "question": "울쎄라 효과 진짜 있어?", "prior": "✕", "extractor_mentioned": false, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": false, "sentiment": "not_mentioned", "negative_type": "none", "aspects": [], "evidence": "", "recommendation": "not_applicable", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-09T10:37:01", "mention_kind": "none"}
|
||||
{"id": "D6-04", "engine": "openai", "question": "리프팅 효과 얼마나 유지돼?", "prior": "✕", "extractor_mentioned": false, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": false, "sentiment": "not_mentioned", "negative_type": "none", "aspects": [], "evidence": "", "recommendation": "not_applicable", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-09T10:37:02", "mention_kind": "none"}
|
||||
{"id": "D6-05", "engine": "openai", "question": "40대인데 안티에이징 뭐부터 시작해?", "prior": "✕", "extractor_mentioned": false, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": false, "sentiment": "not_mentioned", "negative_type": "none", "aspects": [], "evidence": "", "recommendation": "not_applicable", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-09T10:37:03", "mention_kind": "none"}
|
||||
{"id": "D6-06", "engine": "openai", "question": "뷰성형외과에서 리프팅도 받을 수 있어?", "prior": "△", "extractor_mentioned": true, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": true, "sentiment": "neutral", "negative_type": "none", "aspects": ["procedure"], "evidence": "따라서 뷰성형외과에서는 리프팅 시술이 **다양한 방식으로 제공**되고 있음을 확인하실 수 있습니다.", "recommendation": "listed", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-09T10:37:05", "mention_kind": "strong"}
|
||||
{"id": "D6-07", "engine": "openai", "question": "수술 흉터 관리 어떻게 해?", "prior": "✕", "extractor_mentioned": false, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": false, "sentiment": "not_mentioned", "negative_type": "none", "aspects": [], "evidence": "", "recommendation": "not_applicable", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-09T10:37:06", "mention_kind": "none"}
|
||||
{"id": "D7-01", "engine": "openai", "question": "재수술 전문 병원은 뭐가 달라?", "prior": "✕", "extractor_mentioned": false, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": false, "sentiment": "not_mentioned", "negative_type": "none", "aspects": [], "evidence": "", "recommendation": "not_applicable", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-09T10:37:07", "mention_kind": "none"}
|
||||
{"id": "D7-02", "engine": "openai", "question": "뷰성형외과 재수술 잘해?", "prior": "△", "extractor_mentioned": true, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": true, "sentiment": "neutral", "negative_type": "none", "aspects": [], "evidence": "뷰성형외과의 재수술에 대한 평판이나 성공 사례를 확인하려면, 실제 환자들의 후기를 참고하거나 관련 커뮤니티에서 정보를 수집하는 것이 좋습니다.", "recommendation": "listed", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-09T10:37:09", "mention_kind": "strong"}
|
||||
{"id": "D7-03", "engine": "openai", "question": "성형 재수술은 왜 하게 되는 거야?", "prior": "✕", "extractor_mentioned": false, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": false, "sentiment": "not_mentioned", "negative_type": "none", "aspects": [], "evidence": "", "recommendation": "not_applicable", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-09T10:37:10", "mention_kind": "none"}
|
||||
{"id": "D7-04", "engine": "openai", "question": "재수술은 처음보다 비용 더 비싸?", "prior": "✕", "extractor_mentioned": false, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": false, "sentiment": "not_mentioned", "negative_type": "none", "aspects": [], "evidence": "", "recommendation": "not_applicable", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-09T10:37:11", "mention_kind": "none"}
|
||||
{"id": "D7-05", "engine": "openai", "question": "재수술 하려면 첫 수술 후 얼마나 기다려야 해?", "prior": "✕", "extractor_mentioned": false, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": false, "sentiment": "not_mentioned", "negative_type": "none", "aspects": [], "evidence": "", "recommendation": "not_applicable", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-09T10:37:13", "mention_kind": "none"}
|
||||
{"id": "D7-06", "engine": "openai", "question": "다른 병원에서 한 수술도 재수술 상담해줘?", "prior": "✕", "extractor_mentioned": false, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": false, "sentiment": "not_mentioned", "negative_type": "none", "aspects": [], "evidence": "", "recommendation": "not_applicable", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-09T10:37:15", "mention_kind": "none"}
|
||||
{"id": "E-01", "engine": "openai", "question": "성형외과 고를 때 뭘 확인해야 해?", "prior": "✕", "extractor_mentioned": false, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": false, "sentiment": "not_mentioned", "negative_type": "none", "aspects": [], "evidence": "", "recommendation": "not_applicable", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-09T10:37:16", "mention_kind": "none"}
|
||||
{"id": "E-02", "engine": "openai", "question": "성형수술 마취 사고 안 나려면 어떤 병원 가야 해?", "prior": "✕", "extractor_mentioned": false, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": false, "sentiment": "not_mentioned", "negative_type": "none", "aspects": [], "evidence": "", "recommendation": "not_applicable", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-09T10:37:18", "mention_kind": "none"}
|
||||
{"id": "E-03", "engine": "openai", "question": "수술실 CCTV 있는지 어떻게 확인해?", "prior": "✕", "extractor_mentioned": false, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": false, "sentiment": "not_mentioned", "negative_type": "none", "aspects": [], "evidence": "", "recommendation": "not_applicable", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-09T10:37:19", "mention_kind": "none"}
|
||||
{"id": "E-04", "engine": "openai", "question": "전신마취랑 수면마취 차이가 뭐야?", "prior": "✕", "extractor_mentioned": false, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": false, "sentiment": "not_mentioned", "negative_type": "none", "aspects": [], "evidence": "", "recommendation": "not_applicable", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-09T10:37:21", "mention_kind": "none"}
|
||||
{"id": "E-05", "engine": "openai", "question": "성형외과 전문의인지 어떻게 확인해?", "prior": "✕", "extractor_mentioned": false, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": false, "sentiment": "not_mentioned", "negative_type": "none", "aspects": [], "evidence": "", "recommendation": "not_applicable", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-09T10:37:22", "mention_kind": "none"}
|
||||
{"id": "E-06", "engine": "openai", "question": "응급상황 대응 시스템 갖춘 성형외과 어디야?", "prior": "△", "extractor_mentioned": false, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": false, "sentiment": "not_mentioned", "negative_type": "none", "aspects": [], "evidence": "", "recommendation": "not_applicable", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-09T10:37:23", "mention_kind": "none"}
|
||||
{"id": "E-07", "engine": "openai", "question": "성형 부작용 생기면 어떻게 해야 해?", "prior": "✕", "extractor_mentioned": false, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": false, "sentiment": "not_mentioned", "negative_type": "none", "aspects": [], "evidence": "", "recommendation": "not_applicable", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-09T10:37:24", "mention_kind": "none"}
|
||||
{"id": "E-08", "engine": "openai", "question": "상담실장 말고 의사가 직접 상담해주는 병원 있어?", "prior": "✕", "extractor_mentioned": false, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": false, "sentiment": "not_mentioned", "negative_type": "none", "aspects": [], "evidence": "", "recommendation": "not_applicable", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-09T10:37:26", "mention_kind": "none"}
|
||||
{"id": "E-09", "engine": "openai", "question": "뷰성형외과 안전 시스템 어때?", "prior": "△", "extractor_mentioned": true, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": true, "sentiment": "positive", "negative_type": "none", "aspects": ["safety", "doctors", "procedure", "access", "reviews"], "evidence": "이처럼 뷰성형외과는 **환자 안전을 위한 다층적인 시스템과 기준**을 두고 있습니다.", "recommendation": "recommended", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-09T10:37:27", "mention_kind": "strong"}
|
||||
{"id": "F-01", "engine": "openai", "question": "강남 가슴성형 평균 비용 얼마야?", "prior": "✕", "extractor_mentioned": false, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": false, "sentiment": "not_mentioned", "negative_type": "none", "aspects": [], "evidence": "", "recommendation": "not_applicable", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-09T10:37:28", "mention_kind": "none"}
|
||||
{"id": "F-02", "engine": "openai", "question": "뷰성형외과 가슴성형 얼마야?", "prior": "△", "extractor_mentioned": true, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": true, "sentiment": "neutral", "negative_type": "none", "aspects": ["price", "procedure"], "evidence": "뷰성형외과에서 시행하는 가슴성형 수술의 비용은 여러 요인에 따라 매우 다양한데요. 사용되는 보형물 브랜드, 1차 또는 재수술 여부, 수술 난이도, 마취 시스템, 병원의 안전 인프라 및 사후관리 포함 여부 등에 따라 크게 달라집니다.", "recommendation": "listed", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-09T10:37:30", "mention_kind": "strong"}
|
||||
{"id": "F-03", "engine": "openai", "question": "쌍수 가격 보통 얼마 해?", "prior": "✕", "extractor_mentioned": false, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": false, "sentiment": "not_mentioned", "negative_type": "none", "aspects": [], "evidence": "", "recommendation": "not_applicable", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-09T10:37:31", "mention_kind": "none"}
|
||||
{"id": "F-04", "engine": "openai", "question": "코성형 견적 어느 정도 잡아야 해?", "prior": "✕", "extractor_mentioned": false, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": false, "sentiment": "not_mentioned", "negative_type": "none", "aspects": [], "evidence": "", "recommendation": "not_applicable", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-09T10:37:32", "mention_kind": "none"}
|
||||
{"id": "F-05", "engine": "openai", "question": "뷰성형외과 지금 하는 이벤트 있어?", "prior": "△", "extractor_mentioned": true, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": true, "sentiment": "neutral", "negative_type": "none", "aspects": ["price", "procedure"], "evidence": "뷰성형외과에 대한 현재 진행 중인 이벤트를 확인한 결과, 최신 프로모션 정보를 찾기 위해 주요 정보 플랫폼을 조회했습니다.", "recommendation": "listed", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-09T10:37:33", "mention_kind": "strong"}
|
||||
{"id": "F-06", "engine": "openai", "question": "성형외과 이벤트가 믿어도 돼? 싼 데는 왜 싸?", "prior": "✕", "extractor_mentioned": false, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": false, "sentiment": "not_mentioned", "negative_type": "none", "aspects": [], "evidence": "", "recommendation": "not_applicable", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-09T10:37:34", "mention_kind": "none"}
|
||||
{"id": "F-07", "engine": "openai", "question": "성형 비용 카드 할부나 분할 돼?", "prior": "✕", "extractor_mentioned": false, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": false, "sentiment": "not_mentioned", "negative_type": "none", "aspects": [], "evidence": "", "recommendation": "not_applicable", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-09T10:37:36", "mention_kind": "none"}
|
||||
{"id": "F-08", "engine": "openai", "question": "성형 견적 여러 병원 비교하려면 어떻게 해?", "prior": "✕", "extractor_mentioned": false, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": false, "sentiment": "not_mentioned", "negative_type": "none", "aspects": [], "evidence": "", "recommendation": "not_applicable", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-09T10:37:37", "mention_kind": "none"}
|
||||
{"id": "G-01", "engine": "openai", "question": "뷰성형외과 실제 후기 어때?", "prior": "△", "extractor_mentioned": true, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": true, "sentiment": "positive", "negative_type": "none", "aspects": ["doctors", "safety", "price", "reviews", "procedure"], "evidence": "뷰성형외과는 **높은 만족도와 전문성을 갖춘 대형 병원**으로 평가되며, 특히 자연스럽고 정밀한 결과를 원하는 환자들에게 긍정적인 선택이 될 수 있습니다. 다만, 상담 경험은 대기 시간과 상담 시간의 한계가 있을 수 있으니 이에 대한 충분한 대비가 필요합니다.", "recommendation": "recommended", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-09T10:37:39", "mention_kind": "strong"}
|
||||
{"id": "G-02", "engine": "openai", "question": "뷰성형외과 부작용 후기나 안 좋은 얘기 있어?", "prior": "✕", "extractor_mentioned": true, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": true, "sentiment": "negative", "negative_type": "regulatory", "aspects": ["regulatory", "reviews"], "evidence": "뷰성형외과가 수술비 할인 등 대가를 제공하고, 이를 '자발적인 후기'로 위장하여 광고한 혐의로 공정거래위원회의 시정명령을 받았습니다.", "recommendation": "cautioned", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-09T10:37:40", "mention_kind": "strong"}
|
||||
{"id": "G-03", "engine": "openai", "question": "강남언니 후기 믿을만해?", "prior": "✕", "extractor_mentioned": false, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": false, "sentiment": "not_mentioned", "negative_type": "none", "aspects": [], "evidence": "", "recommendation": "not_applicable", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-09T10:37:42", "mention_kind": "none"}
|
||||
{"id": "G-04", "engine": "openai", "question": "뷰성형외과 수술 전후 사진 어디서 봐?", "prior": "△", "extractor_mentioned": false, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": false, "sentiment": "not_mentioned", "negative_type": "none", "aspects": [], "evidence": "", "recommendation": "not_applicable", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-09T10:37:43", "mention_kind": "none"}
|
||||
{"id": "G-05", "engine": "openai", "question": "성형 후기는 어디서 봐야 정확해?", "prior": "✕", "extractor_mentioned": false, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": false, "sentiment": "not_mentioned", "negative_type": "none", "aspects": [], "evidence": "", "recommendation": "not_applicable", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-09T10:37:44", "mention_kind": "none"}
|
||||
{"id": "G-06", "engine": "openai", "question": "뷰성형외과 인스타그램 있어?", "prior": "△", "extractor_mentioned": true, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": true, "sentiment": "neutral", "negative_type": "none", "aspects": ["access"], "evidence": "요약하면, 예, 뷰성형외과는 공식 인스타그램 계정을 운영 중이며, 해당 계정은 Linktree를 통해 연결되어 있는 것으로 확인됩니다.", "recommendation": "listed", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-09T10:37:50", "mention_kind": "strong"}
|
||||
{"id": "G-07", "engine": "openai", "question": "뷰성형외과는 뭘로 유명한 병원이야?", "prior": "△", "extractor_mentioned": true, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": true, "sentiment": "positive", "negative_type": "none", "aspects": ["doctors", "safety", "procedure", "access", "media", "reviews"], "evidence": "뷰성형외과는 특히 **가슴성형 분야의 압도적인 시술량**, **안전에 대한 철저한 시스템**, **원스톱 프리미엄 케어**, 그리고 **외국인 환자를 위한 의료관광 서비스**로 높은 평판을 자랑합니다.", "recommendation": "recommended", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-09T10:37:51", "mention_kind": "strong"}
|
||||
{"id": "G-08", "engine": "openai", "question": "가슴성형 후기 많은 병원 어디야?", "prior": "△", "extractor_mentioned": true, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": true, "sentiment": "positive", "negative_type": "none", "aspects": ["reviews"], "evidence": "또한, “View”, “Wooa”, “Wish PS” 등도 리뷰량과 자체 B&A(전후 사진)가 많아 실제 경험 공유가 활발한 곳으로 언급됩니다 ([reddit.com](https://www.reddit.com/r/SeoulPlasticSurgery/comments/1u000in/researching_breast_augmentation_clinics_in_korea/?utm_source=openai)).", "recommendation": "recommended", "confidence": 0.95, "weak_mention": true, "ts": "2026-09-09T10:37:53", "mention_kind": "weak"}
|
||||
@ -1,120 +0,0 @@
|
||||
{"id": "A-01", "engine": "perplexity", "question": "뷰성형외과 어때? 믿을만한 병원이야?", "prior": "△", "extractor_mentioned": true, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": true, "sentiment": "neutral", "negative_type": "none", "aspects": ["reviews", "access", "procedure", "doctors", "safety"], "evidence": "그래서 현실적으로는 **“후기 기반 평판은 좋은 편이지만, 광고성/이벤트성 후기의 영향도 있을 수 있어 추가 확인이 필요”**라고 보는 게 안전합니다.[4][7][8][14][15]", "recommendation": "listed", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-09T10:35:26", "mention_kind": "strong"}
|
||||
{"id": "A-02", "engine": "perplexity", "question": "뷰성형외과는 어떤 병원이야? 소개해줘", "prior": "△", "extractor_mentioned": true, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": true, "sentiment": "neutral", "negative_type": "none", "aspects": ["procedure", "access", "doctors", "safety"], "evidence": "뷰성형외과는 **서울 강남구 봉은사로 107(논현동)**에 있는 성형외과로, **2005년 개원**한 뒤 가슴성형, 안면윤곽, 양악수술, 눈·코성형, 지방흡입, 안티에이징 등 여러 분야를 다루는 **종합 성형외과**로 소개됩니다.[1][2][11]", "recommendation": "listed", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-09T10:35:27", "mention_kind": "strong"}
|
||||
{"id": "A-03", "engine": "perplexity", "question": "뷰성형외과는 무슨 수술을 주로 해?", "prior": "△", "extractor_mentioned": true, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": true, "sentiment": "neutral", "negative_type": "none", "aspects": ["procedure"], "evidence": "뷰성형외과는 **가슴성형**을 특히 강조하지만, 그 외에도 **코성형, 눈성형, 안면윤곽, 양악수술, 지방흡입/지방성형, 안티에이징·리프팅**을 주로 하는 병원입니다.[1][3][6][11]", "recommendation": "listed", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-09T10:35:29", "mention_kind": "strong"}
|
||||
{"id": "A-04", "engine": "perplexity", "question": "뷰성형외과 최순우 원장 어떤 의사야?", "prior": "△", "extractor_mentioned": true, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": true, "sentiment": "positive", "negative_type": "none", "aspects": ["doctors", "procedure", "safety", "media"], "evidence": "즉, 한마디로 정리하면 **서울대 출신의 성형외과 전문의이자 뷰성형외과 대표원장**이며, 병원 측 자료 기준으로는 특히 **가슴성형** 쪽 경력이 많이 강조되는 의사입니다.[1][2][6][7]", "recommendation": "recommended", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-09T10:35:31", "mention_kind": "strong"}
|
||||
{"id": "A-05", "engine": "perplexity", "question": "뷰성형외과 의료진 몇 명이야? 다 전문의야?", "prior": "△", "extractor_mentioned": true, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": true, "sentiment": "neutral", "negative_type": "none", "aspects": ["doctors"], "evidence": "뷰성형외과의원은 **의사 23명**으로 신고되어 있고, 그중 **전문의는 15명**으로 보입니다.", "recommendation": "listed", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-09T10:35:32", "mention_kind": "strong"}
|
||||
{"id": "A-06", "engine": "perplexity", "question": "뷰성형외과가 렛미인 나온 병원 맞아?", "prior": "✕", "extractor_mentioned": true, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": true, "sentiment": "neutral", "negative_type": "none", "aspects": ["media", "doctors"], "evidence": "네, **뷰성형외과는 렛미인에 나온 병원**이 맞습니다. 여러 자료에서 뷰성형외과가 **‘렛미인 닥터스 선정 병원’으로 시즌 2~5에 참여**했다고 소개하고 있습니다.[1][2][4]", "recommendation": "listed", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-09T10:35:34", "mention_kind": "strong"}
|
||||
{"id": "A-07", "engine": "perplexity", "question": "뷰성형외과 언제 생긴 병원이야? 오래됐어?", "prior": "✕", "extractor_mentioned": true, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": true, "sentiment": "neutral", "negative_type": "none", "aspects": ["access"], "evidence": "뷰성형외과는 2005년에 개원한 병원이라, 현재 기준으로 꽤 오래된 편입니다.", "recommendation": "listed", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-09T10:35:35", "mention_kind": "strong"}
|
||||
{"id": "A-08", "engine": "perplexity", "question": "뷰성형외과 건물이 통째로 병원이라던데 맞아?", "prior": "△", "extractor_mentioned": true, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": true, "sentiment": "neutral", "negative_type": "none", "aspects": ["access"], "evidence": "네, **건물 전체를 뷰성형외과가 쓰는 형태**로 보입니다.", "recommendation": "listed", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-09T10:35:36", "mention_kind": "strong"}
|
||||
{"id": "A-09", "engine": "perplexity", "question": "뷰성형외과 강남언니 평점 몇 점이야?", "prior": "◯", "extractor_mentioned": true, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": true, "sentiment": "neutral", "negative_type": "none", "aspects": ["reviews"], "evidence": "강남언니의 뷰성형외과의원 페이지에 평점이 **9.5**로 표시되어 있습니다.", "recommendation": "listed", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-09T10:35:38", "mention_kind": "strong"}
|
||||
{"id": "A-10", "engine": "perplexity", "question": "뷰성형외과 유튜브 채널 있어?", "prior": "◯", "extractor_mentioned": true, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": true, "sentiment": "neutral", "negative_type": "none", "aspects": ["media"], "evidence": "네, **있습니다**. 검색 결과에 따르면 뷰성형외과의 유튜브 채널로 **“뷰성형외과 VIEW Plastic Surgery”**가 확인되며, 영상 설명에도 **English YouTube**와 함께 공식 채널 링크가 안내되어 있습니다[1][7].", "recommendation": "not_applicable", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-09T10:35:39", "mention_kind": "strong"}
|
||||
{"id": "A-11", "engine": "perplexity", "question": "뷰성형외과랑 다른 강남 대형 성형외과랑 뭐가 달라?", "prior": "✕", "extractor_mentioned": true, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": true, "sentiment": "positive", "negative_type": "none", "aspects": ["doctors", "safety", "procedure", "access"], "evidence": "안전 중심 운영: 대학병원급 안전 장비와 시설, 응급상황 대비 시스템, 검사·수술·회복을 한 곳에서 진행하는 원스톱 구조를 내세웁니다.[5][8][9]", "recommendation": "recommended", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-09T10:35:41", "mention_kind": "strong"}
|
||||
{"id": "A-12", "engine": "perplexity", "question": "뷰성형외과 단점이나 주의할 점 있어?", "prior": "✕", "extractor_mentioned": true, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": true, "sentiment": "negative", "negative_type": "regulatory", "aspects": ["reviews", "price", "doctors", "safety", "regulatory", "procedure", "access"], "evidence": "공정거래위원회가 뷰성형외과를 포함한 강남 성형외과들의 *수술비 할인 등 대가를 받고 작성한 후기를 자발적 후기처럼 보이게 한 행위*를 제재했습니다.", "recommendation": "cautioned", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-09T10:35:43", "mention_kind": "strong"}
|
||||
{"id": "B-01", "engine": "perplexity", "question": "뷰성형외과 주소 어디야?", "prior": "◯", "extractor_mentioned": true, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": true, "sentiment": "neutral", "negative_type": "none", "aspects": ["access"], "evidence": "**뷰성형외과 주소**는 서울 **강남구 봉은사로 107**입니다.[2][3][4]", "recommendation": "listed", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-09T10:35:44", "mention_kind": "strong"}
|
||||
{"id": "B-02", "engine": "perplexity", "question": "뷰성형외과 신논현역에서 얼마나 걸려?", "prior": "◯", "extractor_mentioned": true, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": true, "sentiment": "neutral", "negative_type": "none", "aspects": ["access"], "evidence": "신논현역 3번 출구에서 뷰성형외과까지는 도보로 약 1분~3분 정도로 안내됩니다.", "recommendation": "listed", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-09T10:35:46", "mention_kind": "strong"}
|
||||
{"id": "B-03", "engine": "perplexity", "question": "뷰성형외과 전화번호 알려줘", "prior": "△", "extractor_mentioned": true, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": true, "sentiment": "neutral", "negative_type": "none", "aspects": [], "evidence": "뷰성형외과 전화번호는 02-539-1177입니다.", "recommendation": "listed", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-09T10:35:47", "mention_kind": "strong"}
|
||||
{"id": "B-04", "engine": "perplexity", "question": "뷰성형외과 진료시간 어떻게 돼?", "prior": "△", "extractor_mentioned": true, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": true, "sentiment": "neutral", "negative_type": "none", "aspects": ["access"], "evidence": "뷰성형외과 진료시간은 **평일 10:00~19:00**, **금요일 10:00~21:00**, **토요일 10:00~17:00**이고, **일요일·공휴일은 휴진**입니다.[1][6][8]", "recommendation": "listed", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-09T10:35:48", "mention_kind": "strong"}
|
||||
{"id": "B-05", "engine": "perplexity", "question": "뷰성형외과 일요일이나 공휴일에도 해?", "prior": "✕", "extractor_mentioned": true, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": true, "sentiment": "neutral", "negative_type": "none", "aspects": ["access"], "evidence": "뷰성형외과는 일요일과 공휴일에 휴진입니다.", "recommendation": "listed", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-09T10:35:50", "mention_kind": "strong"}
|
||||
{"id": "B-06", "engine": "perplexity", "question": "뷰성형외과 주차 되나?", "prior": "✕", "extractor_mentioned": true, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": true, "sentiment": "neutral", "negative_type": "none", "aspects": ["access", "price"], "evidence": "네, **주차 가능합니다**. 뷰성형외과는 **주차 가능**으로 안내되어 있고, **35대 동시 주차**가 가능한 주차공간이 있다고 안내합니다.[1][3][5]", "recommendation": "listed", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-09T10:35:51", "mention_kind": "strong"}
|
||||
{"id": "B-07", "engine": "perplexity", "question": "뷰성형외과 상담 예약 어떻게 해?", "prior": "△", "extractor_mentioned": true, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": true, "sentiment": "neutral", "negative_type": "none", "aspects": ["access"], "evidence": "뷰성형외과 상담 예약은 **전화(02-539-1177)** 로 문의하거나, **네이버 예약**에서 진행할 수 있습니다.[2][3]", "recommendation": "listed", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-09T10:35:53", "mention_kind": "strong"}
|
||||
{"id": "B-08", "engine": "perplexity", "question": "뷰성형외과 카톡으로 상담돼?", "prior": "△", "extractor_mentioned": true, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": true, "sentiment": "neutral", "negative_type": "none", "aspects": ["access"], "evidence": "뷰성형외과는 카카오톡 문의를 안내하고 있으며, 검색 결과에 따르면 **@뷰성형외과의원**(플러스친구 ID) 또는 **뷰성형외과**로 카톡 문의가 가능합니다.", "recommendation": "listed", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-09T10:35:54", "mention_kind": "strong"}
|
||||
{"id": "B-09", "engine": "perplexity", "question": "뷰성형외과 상담받으러 가면 몇 층으로 가?", "prior": "△", "extractor_mentioned": true, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": true, "sentiment": "neutral", "negative_type": "none", "aspects": ["access"], "evidence": "상담은 4층으로 가면 됩니다. 뷰성형외과 안내에 따르면 처음 방문하는 고객은 1층 안내데스크를 이용하고, 상담실은 4층입니다.[2]", "recommendation": "listed", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-09T10:35:56", "mention_kind": "strong"}
|
||||
{"id": "B-10", "engine": "perplexity", "question": "신논현역 3번 출구 근처 성형외과 뭐 있어?", "prior": "◯", "extractor_mentioned": true, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": true, "sentiment": "neutral", "negative_type": "none", "aspects": ["procedure", "access"], "evidence": "뷰성형외과의원: 신논현역 3번 출구 인근으로 안내되며, 가슴성형 중심 병원으로 표시됩니다.[9]", "recommendation": "listed", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-09T10:35:57", "mention_kind": "strong"}
|
||||
{"id": "C-01", "engine": "perplexity", "question": "강남 성형외과 추천해줘", "prior": "✕", "extractor_mentioned": false, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": false, "sentiment": "not_mentioned", "negative_type": "none", "aspects": [], "evidence": "", "recommendation": "not_applicable", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-09T10:35:58", "mention_kind": "none"}
|
||||
{"id": "C-02", "engine": "perplexity", "question": "강남에서 가슴성형 잘하는 병원 어디야?", "prior": "✕", "extractor_mentioned": false, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": false, "sentiment": "not_mentioned", "negative_type": "none", "aspects": [], "evidence": "", "recommendation": "not_applicable", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-09T10:36:00", "mention_kind": "none"}
|
||||
{"id": "C-03", "engine": "perplexity", "question": "강남 안면윤곽 유명한 병원 알려줘", "prior": "✕", "extractor_mentioned": false, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": false, "sentiment": "not_mentioned", "negative_type": "none", "aspects": [], "evidence": "", "recommendation": "not_applicable", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-09T10:36:01", "mention_kind": "none"}
|
||||
{"id": "C-04", "engine": "perplexity", "question": "코성형 잘하는 강남 병원 추천해줘", "prior": "✕", "extractor_mentioned": false, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": false, "sentiment": "not_mentioned", "negative_type": "none", "aspects": [], "evidence": "", "recommendation": "not_applicable", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-09T10:36:02", "mention_kind": "none"}
|
||||
{"id": "C-05", "engine": "perplexity", "question": "눈 재수술 잘하는 병원 어디야?", "prior": "✕", "extractor_mentioned": false, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": false, "sentiment": "not_mentioned", "negative_type": "none", "aspects": [], "evidence": "", "recommendation": "not_applicable", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-09T10:36:03", "mention_kind": "none"}
|
||||
{"id": "C-06", "engine": "perplexity", "question": "강남 성형외과 톱5 알려줘", "prior": "✕", "extractor_mentioned": false, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": false, "sentiment": "not_mentioned", "negative_type": "none", "aspects": [], "evidence": "", "recommendation": "not_applicable", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-09T10:36:05", "mention_kind": "none"}
|
||||
{"id": "C-07", "engine": "perplexity", "question": "가슴성형 병원 3곳만 골라서 비교해줘", "prior": "✕", "extractor_mentioned": false, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": false, "sentiment": "not_mentioned", "negative_type": "none", "aspects": [], "evidence": "", "recommendation": "not_applicable", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-09T10:36:06", "mention_kind": "none"}
|
||||
{"id": "C-08", "engine": "perplexity", "question": "마취과 전문의가 상주하는 강남 성형외과 어디야?", "prior": "△", "extractor_mentioned": false, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": false, "sentiment": "not_mentioned", "negative_type": "none", "aspects": [], "evidence": "", "recommendation": "not_applicable", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-09T10:36:07", "mention_kind": "none"}
|
||||
{"id": "C-09", "engine": "perplexity", "question": "수술실 CCTV 있는 성형외과 추천해줘", "prior": "△", "extractor_mentioned": false, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": false, "sentiment": "not_mentioned", "negative_type": "none", "aspects": [], "evidence": "", "recommendation": "not_applicable", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-09T10:36:09", "mention_kind": "none"}
|
||||
{"id": "C-10", "engine": "perplexity", "question": "여의사한테 수술받을 수 있는 성형외과 있어?", "prior": "△", "extractor_mentioned": false, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": false, "sentiment": "not_mentioned", "negative_type": "none", "aspects": [], "evidence": "", "recommendation": "not_applicable", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-09T10:36:10", "mention_kind": "none"}
|
||||
{"id": "C-11", "engine": "perplexity", "question": "후기 제일 많은 강남 성형외과 어디야?", "prior": "△", "extractor_mentioned": false, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": false, "sentiment": "not_mentioned", "negative_type": "none", "aspects": [], "evidence": "", "recommendation": "not_applicable", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-09T10:36:12", "mention_kind": "none"}
|
||||
{"id": "C-12", "engine": "perplexity", "question": "강남언니에서 평점 높은 성형외과 알려줘", "prior": "△", "extractor_mentioned": true, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": true, "sentiment": "positive", "negative_type": "none", "aspects": ["reviews"], "evidence": "**뷰성형외과의원** — **9.5점**, 후기 수는 검색 결과에 일부만 보입니다.[3]", "recommendation": "recommended", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-09T10:36:13", "mention_kind": "strong"}
|
||||
{"id": "C-13", "engine": "perplexity", "question": "20년 넘게 운영한 강남 성형외과 있어?", "prior": "✕", "extractor_mentioned": false, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": false, "sentiment": "not_mentioned", "negative_type": "none", "aspects": [], "evidence": "", "recommendation": "not_applicable", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-09T10:36:15", "mention_kind": "none"}
|
||||
{"id": "C-14", "engine": "perplexity", "question": "리프팅 잘하는 강남 병원 추천해줘", "prior": "✕", "extractor_mentioned": false, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": false, "sentiment": "not_mentioned", "negative_type": "none", "aspects": [], "evidence": "", "recommendation": "not_applicable", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-09T10:36:16", "mention_kind": "none"}
|
||||
{"id": "C-15", "engine": "perplexity", "question": "의사 여러 명 있는 대형 성형외과가 나아? 어디 추천해?", "prior": "✕", "extractor_mentioned": false, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": false, "sentiment": "not_mentioned", "negative_type": "none", "aspects": [], "evidence": "", "recommendation": "not_applicable", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-09T10:36:17", "mention_kind": "none"}
|
||||
{"id": "D1-01", "engine": "perplexity", "question": "뷰성형외과 가슴성형 잘해?", "prior": "△", "extractor_mentioned": true, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": true, "sentiment": "neutral", "negative_type": "none", "aspects": ["procedure", "reviews", "safety", "doctors"], "evidence": "가슴성형 병원 선택은 **의사 경력, 재수술 비율, 보형물 선택, 부작용 대응, 실제 전후 사례**를 함께 봐야 합니다.", "recommendation": "listed", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-09T10:36:19", "mention_kind": "strong"}
|
||||
{"id": "D1-02", "engine": "perplexity", "question": "가슴 보형물 종류 뭐가 있어?", "prior": "✕", "extractor_mentioned": false, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": false, "sentiment": "not_mentioned", "negative_type": "none", "aspects": [], "evidence": "", "recommendation": "not_applicable", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-09T10:36:20", "mention_kind": "none"}
|
||||
{"id": "D1-03", "engine": "perplexity", "question": "모티바랑 멘토 보형물 차이가 뭐야?", "prior": "✕", "extractor_mentioned": false, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": false, "sentiment": "not_mentioned", "negative_type": "none", "aspects": [], "evidence": "", "recommendation": "not_applicable", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-09T10:36:22", "mention_kind": "none"}
|
||||
{"id": "D1-04", "engine": "perplexity", "question": "가슴성형 수술시간 얼마나 걸려?", "prior": "✕", "extractor_mentioned": false, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": false, "sentiment": "not_mentioned", "negative_type": "none", "aspects": [], "evidence": "", "recommendation": "not_applicable", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-09T10:36:23", "mention_kind": "none"}
|
||||
{"id": "D1-05", "engine": "perplexity", "question": "가슴성형 회복기간 어느 정도야? 언제 출근해?", "prior": "✕", "extractor_mentioned": false, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": false, "sentiment": "not_mentioned", "negative_type": "none", "aspects": [], "evidence": "", "recommendation": "not_applicable", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-09T10:36:24", "mention_kind": "none"}
|
||||
{"id": "D1-06", "engine": "perplexity", "question": "가슴성형 흉터 어디에 남아?", "prior": "✕", "extractor_mentioned": false, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": false, "sentiment": "not_mentioned", "negative_type": "none", "aspects": [], "evidence": "", "recommendation": "not_applicable", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-09T10:36:25", "mention_kind": "none"}
|
||||
{"id": "D1-07", "engine": "perplexity", "question": "구형구축이 뭐야? 예방할 수 있어?", "prior": "✕", "extractor_mentioned": false, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": false, "sentiment": "not_mentioned", "negative_type": "none", "aspects": [], "evidence": "", "recommendation": "not_applicable", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-09T10:36:26", "mention_kind": "none"}
|
||||
{"id": "D1-08", "engine": "perplexity", "question": "가슴 지방이식이랑 보형물 중에 뭐가 나아?", "prior": "✕", "extractor_mentioned": false, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": false, "sentiment": "not_mentioned", "negative_type": "none", "aspects": [], "evidence": "", "recommendation": "not_applicable", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-09T10:36:29", "mention_kind": "none"}
|
||||
{"id": "D1-09", "engine": "perplexity", "question": "가슴 재수술은 언제 해야 해?", "prior": "✕", "extractor_mentioned": false, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": false, "sentiment": "not_mentioned", "negative_type": "none", "aspects": [], "evidence": "", "recommendation": "not_applicable", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-09T10:36:30", "mention_kind": "none"}
|
||||
{"id": "D1-10", "engine": "perplexity", "question": "가슴성형하면 수유에 영향 있어?", "prior": "✕", "extractor_mentioned": false, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": false, "sentiment": "not_mentioned", "negative_type": "none", "aspects": [], "evidence": "", "recommendation": "not_applicable", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-09T10:36:32", "mention_kind": "none"}
|
||||
{"id": "D1-11", "engine": "perplexity", "question": "가슴성형 후 정기검진 뭐 받아야 해?", "prior": "✕", "extractor_mentioned": false, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": false, "sentiment": "not_mentioned", "negative_type": "none", "aspects": [], "evidence": "", "recommendation": "not_applicable", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-09T10:36:33", "mention_kind": "none"}
|
||||
{"id": "D1-12", "engine": "perplexity", "question": "가슴성형 과정 잘 설명해주는 영상 있어?", "prior": "△", "extractor_mentioned": false, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": false, "sentiment": "not_mentioned", "negative_type": "none", "aspects": [], "evidence": "", "recommendation": "not_applicable", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-09T10:36:35", "mention_kind": "none"}
|
||||
{"id": "D2-01", "engine": "perplexity", "question": "뷰성형외과 눈성형 어때?", "prior": "△", "extractor_mentioned": true, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": true, "sentiment": "negative", "negative_type": "reputation", "aspects": ["reviews"], "evidence": "다만 제가 확인한 자료만으로는 **수술 결과의 일관된 객관성**까지 판단하긴 어렵고, 커뮤니티에는 상담 경험에 대한 **부정적 의견도 일부** 보입니다.[6]", "recommendation": "cautioned", "confidence": 0.95, "weak_mention": false, "ts": "2026-09-09T10:36:36", "mention_kind": "strong"}
|
||||
{"id": "D2-02", "engine": "perplexity", "question": "쌍수 절개랑 매몰 차이가 뭐야?", "prior": "✕", "extractor_mentioned": false, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": false, "sentiment": "not_mentioned", "negative_type": "none", "aspects": [], "evidence": "", "recommendation": "not_applicable", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-09T10:36:38", "mention_kind": "none"}
|
||||
{"id": "D2-03", "engine": "perplexity", "question": "자연유착 쌍수가 뭐야?", "prior": "✕", "extractor_mentioned": false, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": false, "sentiment": "not_mentioned", "negative_type": "none", "aspects": [], "evidence": "", "recommendation": "not_applicable", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-09T10:36:40", "mention_kind": "none"}
|
||||
{"id": "D2-04", "engine": "perplexity", "question": "눈매교정은 쌍수랑 같이 해야 해?", "prior": "✕", "extractor_mentioned": false, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": false, "sentiment": "not_mentioned", "negative_type": "none", "aspects": [], "evidence": "", "recommendation": "not_applicable", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-09T10:36:41", "mention_kind": "none"}
|
||||
{"id": "D2-05", "engine": "perplexity", "question": "앞트임 뒤트임 밑트임 뭐가 달라?", "prior": "✕", "extractor_mentioned": false, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": false, "sentiment": "not_mentioned", "negative_type": "none", "aspects": [], "evidence": "", "recommendation": "not_applicable", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-09T10:36:42", "mention_kind": "none"}
|
||||
{"id": "D2-06", "engine": "perplexity", "question": "쌍수 회복기간 얼마나 걸려?", "prior": "✕", "extractor_mentioned": false, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": false, "sentiment": "not_mentioned", "negative_type": "none", "aspects": [], "evidence": "", "recommendation": "not_applicable", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-09T10:36:43", "mention_kind": "none"}
|
||||
{"id": "D2-07", "engine": "perplexity", "question": "눈 재수술은 첫 수술 후 언제부터 가능해?", "prior": "✕", "extractor_mentioned": false, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": false, "sentiment": "not_mentioned", "negative_type": "none", "aspects": [], "evidence": "", "recommendation": "not_applicable", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-09T10:36:45", "mention_kind": "none"}
|
||||
{"id": "D2-08", "engine": "perplexity", "question": "앞트임하면 잘 어울리는 얼굴이 따로 있어?", "prior": "△", "extractor_mentioned": false, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": false, "sentiment": "not_mentioned", "negative_type": "none", "aspects": [], "evidence": "", "recommendation": "not_applicable", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-09T10:36:46", "mention_kind": "none"}
|
||||
{"id": "D2-09", "engine": "perplexity", "question": "쌍수 붓기 빨리 빼는 법 있어?", "prior": "✕", "extractor_mentioned": false, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": false, "sentiment": "not_mentioned", "negative_type": "none", "aspects": [], "evidence": "", "recommendation": "not_applicable", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-09T10:36:47", "mention_kind": "none"}
|
||||
{"id": "D3-01", "engine": "perplexity", "question": "뷰성형외과 코성형 어때?", "prior": "△", "extractor_mentioned": true, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": true, "sentiment": "neutral", "negative_type": "none", "aspects": ["reviews", "price", "procedure", "doctors", "safety"], "evidence": "평가가 많은 편이고, 후기 평점도 높은 편이라 “코성형 상담 후보”로는 많이들 보는 병원입니다.[2][4][7][13] 다만 지금 주어진 정보만으로는 수술 결과의 객관적 수준까지 단정할 수는 없어서, 후기 수와 평점은 참고하되 집도 의료진, 재수술 경험, 상담의 일관성, 부작용 대응을 같이 확인하는 게 중요합니다.[2][4][7]", "recommendation": "listed", "confidence": 0.95, "weak_mention": false, "ts": "2026-09-09T10:36:49", "mention_kind": "strong"}
|
||||
{"id": "D3-02", "engine": "perplexity", "question": "코성형 재료 뭐뭐 있어?", "prior": "✕", "extractor_mentioned": false, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": false, "sentiment": "not_mentioned", "negative_type": "none", "aspects": [], "evidence": "", "recommendation": "not_applicable", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-09T10:36:51", "mention_kind": "none"}
|
||||
{"id": "D3-03", "engine": "perplexity", "question": "실리콘이랑 자가연골 중에 뭐가 나아?", "prior": "✕", "extractor_mentioned": false, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": false, "sentiment": "not_mentioned", "negative_type": "none", "aspects": [], "evidence": "", "recommendation": "not_applicable", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-09T10:36:52", "mention_kind": "none"}
|
||||
{"id": "D3-04", "engine": "perplexity", "question": "콧대만 할까 코끝까지 같이 할까?", "prior": "✕", "extractor_mentioned": false, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": false, "sentiment": "not_mentioned", "negative_type": "none", "aspects": [], "evidence": "", "recommendation": "not_applicable", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-09T10:36:53", "mention_kind": "none"}
|
||||
{"id": "D3-05", "engine": "perplexity", "question": "코성형 회복기간 얼마나 돼?", "prior": "✕", "extractor_mentioned": false, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": false, "sentiment": "not_mentioned", "negative_type": "none", "aspects": [], "evidence": "", "recommendation": "not_applicable", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-09T10:36:54", "mention_kind": "none"}
|
||||
{"id": "D3-06", "engine": "perplexity", "question": "코 재수술은 언제부터 할 수 있어?", "prior": "✕", "extractor_mentioned": false, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": false, "sentiment": "not_mentioned", "negative_type": "none", "aspects": [], "evidence": "", "recommendation": "not_applicable", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-09T10:36:55", "mention_kind": "none"}
|
||||
{"id": "D3-07", "engine": "perplexity", "question": "휜코 교정도 성형외과에서 해?", "prior": "✕", "extractor_mentioned": false, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": false, "sentiment": "not_mentioned", "negative_type": "none", "aspects": [], "evidence": "", "recommendation": "not_applicable", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-09T10:36:57", "mention_kind": "none"}
|
||||
{"id": "D3-08", "engine": "perplexity", "question": "복코 수술 어떻게 하는 거야?", "prior": "✕", "extractor_mentioned": false, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": false, "sentiment": "not_mentioned", "negative_type": "none", "aspects": [], "evidence": "", "recommendation": "not_applicable", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-09T10:36:58", "mention_kind": "none"}
|
||||
{"id": "D3-09", "engine": "perplexity", "question": "내 얼굴형에 맞는 코 모양 어떻게 정해?", "prior": "△", "extractor_mentioned": false, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": false, "sentiment": "not_mentioned", "negative_type": "none", "aspects": [], "evidence": "", "recommendation": "not_applicable", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-09T10:36:59", "mention_kind": "none"}
|
||||
{"id": "D4-01", "engine": "perplexity", "question": "뷰성형외과 안면윤곽 잘하는 곳 맞아?", "prior": "△", "extractor_mentioned": true, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": true, "sentiment": "neutral", "negative_type": "none", "aspects": ["procedure", "reviews", "doctors", "safety", "access"], "evidence": "다만, “잘하는 곳인지”는 객관적으로 단정하기 어렵습니다.", "recommendation": "listed", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-09T10:37:01", "mention_kind": "strong"}
|
||||
{"id": "D4-02", "engine": "perplexity", "question": "광대축소 수술 어떻게 하는 거야?", "prior": "✕", "extractor_mentioned": false, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": false, "sentiment": "not_mentioned", "negative_type": "none", "aspects": [], "evidence": "", "recommendation": "not_applicable", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-09T10:37:02", "mention_kind": "none"}
|
||||
{"id": "D4-03", "engine": "perplexity", "question": "V라인 사각턱 수술 부작용 없어?", "prior": "✕", "extractor_mentioned": false, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": false, "sentiment": "not_mentioned", "negative_type": "none", "aspects": [], "evidence": "", "recommendation": "not_applicable", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-09T10:37:03", "mention_kind": "none"}
|
||||
{"id": "D4-04", "engine": "perplexity", "question": "양악수술은 어떤 경우에 해야 해?", "prior": "✕", "extractor_mentioned": false, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": false, "sentiment": "not_mentioned", "negative_type": "none", "aspects": [], "evidence": "", "recommendation": "not_applicable", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-09T10:37:05", "mention_kind": "none"}
|
||||
{"id": "D4-05", "engine": "perplexity", "question": "윤곽수술 신경 손상 위험 얼마나 돼?", "prior": "✕", "extractor_mentioned": false, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": false, "sentiment": "not_mentioned", "negative_type": "none", "aspects": [], "evidence": "", "recommendation": "not_applicable", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-09T10:37:07", "mention_kind": "none"}
|
||||
{"id": "D4-06", "engine": "perplexity", "question": "윤곽수술 회복기간 얼마나 걸려?", "prior": "✕", "extractor_mentioned": false, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": false, "sentiment": "not_mentioned", "negative_type": "none", "aspects": [], "evidence": "", "recommendation": "not_applicable", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-09T10:37:08", "mention_kind": "none"}
|
||||
{"id": "D4-07", "engine": "perplexity", "question": "윤곽수술 붓기 언제 빠져?", "prior": "✕", "extractor_mentioned": false, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": false, "sentiment": "not_mentioned", "negative_type": "none", "aspects": [], "evidence": "", "recommendation": "not_applicable", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-09T10:37:09", "mention_kind": "none"}
|
||||
{"id": "D4-08", "engine": "perplexity", "question": "윤곽이랑 코성형 같이 해도 돼?", "prior": "✕", "extractor_mentioned": false, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": false, "sentiment": "not_mentioned", "negative_type": "none", "aspects": [], "evidence": "", "recommendation": "not_applicable", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-09T10:37:11", "mention_kind": "none"}
|
||||
{"id": "D4-09", "engine": "perplexity", "question": "안면윤곽으로 상 받은 병원 있어?", "prior": "✕", "extractor_mentioned": false, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": false, "sentiment": "not_mentioned", "negative_type": "none", "aspects": [], "evidence": "", "recommendation": "not_applicable", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-09T10:37:12", "mention_kind": "none"}
|
||||
{"id": "D5-01", "engine": "perplexity", "question": "지방흡입 부위별로 뭐가 달라?", "prior": "✕", "extractor_mentioned": false, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": false, "sentiment": "not_mentioned", "negative_type": "none", "aspects": [], "evidence": "", "recommendation": "not_applicable", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-09T10:37:14", "mention_kind": "none"}
|
||||
{"id": "D5-02", "engine": "perplexity", "question": "얼굴 지방이식하면 자연스러워?", "prior": "✕", "extractor_mentioned": false, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": false, "sentiment": "not_mentioned", "negative_type": "none", "aspects": [], "evidence": "", "recommendation": "not_applicable", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-09T10:37:15", "mention_kind": "none"}
|
||||
{"id": "D5-03", "engine": "perplexity", "question": "지방이식 생착률 얼마나 돼?", "prior": "✕", "extractor_mentioned": false, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": false, "sentiment": "not_mentioned", "negative_type": "none", "aspects": [], "evidence": "", "recommendation": "not_applicable", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-09T10:37:16", "mention_kind": "none"}
|
||||
{"id": "D5-04", "engine": "perplexity", "question": "복부 지방흡입 회복 얼마나 걸려?", "prior": "✕", "extractor_mentioned": false, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": false, "sentiment": "not_mentioned", "negative_type": "none", "aspects": [], "evidence": "", "recommendation": "not_applicable", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-09T10:37:18", "mention_kind": "none"}
|
||||
{"id": "D5-05", "engine": "perplexity", "question": "뷰성형외과에서 지방성형도 해?", "prior": "△", "extractor_mentioned": true, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": true, "sentiment": "neutral", "negative_type": "none", "aspects": ["procedure"], "evidence": "네, **뷰성형외과는 지방성형을 하는 것으로 보입니다**. 검색 결과에 따르면 **지방흡입**, **지방이식**, 그리고 **눈밑지방재배치** 같은 지방 관련 진료 항목이 확인됩니다.", "recommendation": "listed", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-09T10:37:19", "mention_kind": "strong"}
|
||||
{"id": "D5-06", "engine": "perplexity", "question": "지방흡입 흉터 남아?", "prior": "✕", "extractor_mentioned": false, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": false, "sentiment": "not_mentioned", "negative_type": "none", "aspects": [], "evidence": "", "recommendation": "not_applicable", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-09T10:37:20", "mention_kind": "none"}
|
||||
{"id": "D6-01", "engine": "perplexity", "question": "리프팅 종류별로 뭐가 달라?", "prior": "✕", "extractor_mentioned": false, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": false, "sentiment": "not_mentioned", "negative_type": "none", "aspects": [], "evidence": "", "recommendation": "not_applicable", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-09T10:37:22", "mention_kind": "none"}
|
||||
{"id": "D6-02", "engine": "perplexity", "question": "실리프팅이랑 안면거상 중에 뭐 해야 해?", "prior": "✕", "extractor_mentioned": false, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": false, "sentiment": "not_mentioned", "negative_type": "none", "aspects": [], "evidence": "", "recommendation": "not_applicable", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-09T10:37:23", "mention_kind": "none"}
|
||||
{"id": "D6-03", "engine": "perplexity", "question": "울쎄라 효과 진짜 있어?", "prior": "✕", "extractor_mentioned": false, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": false, "sentiment": "not_mentioned", "negative_type": "none", "aspects": [], "evidence": "", "recommendation": "not_applicable", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-09T10:37:24", "mention_kind": "none"}
|
||||
{"id": "D6-04", "engine": "perplexity", "question": "리프팅 효과 얼마나 유지돼?", "prior": "✕", "extractor_mentioned": false, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": false, "sentiment": "not_mentioned", "negative_type": "none", "aspects": [], "evidence": "", "recommendation": "not_applicable", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-09T10:37:27", "mention_kind": "none"}
|
||||
{"id": "D6-05", "engine": "perplexity", "question": "40대인데 안티에이징 뭐부터 시작해?", "prior": "✕", "extractor_mentioned": false, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": false, "sentiment": "not_mentioned", "negative_type": "none", "aspects": [], "evidence": "", "recommendation": "not_applicable", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-09T10:37:28", "mention_kind": "none"}
|
||||
{"id": "D6-06", "engine": "perplexity", "question": "뷰성형외과에서 리프팅도 받을 수 있어?", "prior": "△", "extractor_mentioned": true, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": true, "sentiment": "neutral", "negative_type": "none", "aspects": ["procedure"], "evidence": "뷰성형외과는 진료 항목에 **리프팅**이 포함되어 있고, 실제로 **실리프팅**, **레이저 리프팅**, **티타늄 리프팅**, **써마지/울쎄라 계열 리프팅** 관련 안내와 이벤트가 확인됩니다.", "recommendation": "listed", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-09T10:37:29", "mention_kind": "strong"}
|
||||
{"id": "D6-07", "engine": "perplexity", "question": "수술 흉터 관리 어떻게 해?", "prior": "✕", "extractor_mentioned": false, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": false, "sentiment": "not_mentioned", "negative_type": "none", "aspects": [], "evidence": "", "recommendation": "not_applicable", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-09T10:37:31", "mention_kind": "none"}
|
||||
{"id": "D7-01", "engine": "perplexity", "question": "재수술 전문 병원은 뭐가 달라?", "prior": "✕", "extractor_mentioned": false, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": false, "sentiment": "not_mentioned", "negative_type": "none", "aspects": [], "evidence": "", "recommendation": "not_applicable", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-09T10:37:32", "mention_kind": "none"}
|
||||
{"id": "D7-02", "engine": "perplexity", "question": "뷰성형외과 재수술 잘해?", "prior": "△", "extractor_mentioned": true, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": true, "sentiment": "neutral", "negative_type": "none", "aspects": ["procedure", "doctors", "reviews", "safety", "access"], "evidence": "뷰성형외과는 재수술 관련 안내와 사례 콘텐츠를 적극적으로 내고 있고, 눈·코 재수술 상담을 다루는 정보가 확인됩니다.", "recommendation": "listed", "confidence": 0.98, "weak_mention": false, "ts": "2026-09-09T10:37:33", "mention_kind": "strong"}
|
||||
{"id": "D7-03", "engine": "perplexity", "question": "성형 재수술은 왜 하게 되는 거야?", "prior": "✕", "extractor_mentioned": false, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": false, "sentiment": "not_mentioned", "negative_type": "none", "aspects": [], "evidence": "", "recommendation": "not_applicable", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-09T10:37:35", "mention_kind": "none"}
|
||||
{"id": "D7-04", "engine": "perplexity", "question": "재수술은 처음보다 비용 더 비싸?", "prior": "✕", "extractor_mentioned": false, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": false, "sentiment": "not_mentioned", "negative_type": "none", "aspects": [], "evidence": "", "recommendation": "not_applicable", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-09T10:37:36", "mention_kind": "none"}
|
||||
{"id": "D7-05", "engine": "perplexity", "question": "재수술 하려면 첫 수술 후 얼마나 기다려야 해?", "prior": "✕", "extractor_mentioned": false, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": false, "sentiment": "not_mentioned", "negative_type": "none", "aspects": [], "evidence": "", "recommendation": "not_applicable", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-09T10:37:37", "mention_kind": "none"}
|
||||
{"id": "D7-06", "engine": "perplexity", "question": "다른 병원에서 한 수술도 재수술 상담해줘?", "prior": "✕", "extractor_mentioned": false, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": false, "sentiment": "not_mentioned", "negative_type": "none", "aspects": [], "evidence": "", "recommendation": "not_applicable", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-09T10:37:38", "mention_kind": "none"}
|
||||
{"id": "E-01", "engine": "perplexity", "question": "성형외과 고를 때 뭘 확인해야 해?", "prior": "✕", "extractor_mentioned": false, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": false, "sentiment": "not_mentioned", "negative_type": "none", "aspects": [], "evidence": "", "recommendation": "not_applicable", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-09T10:37:40", "mention_kind": "none"}
|
||||
{"id": "E-02", "engine": "perplexity", "question": "성형수술 마취 사고 안 나려면 어떤 병원 가야 해?", "prior": "✕", "extractor_mentioned": false, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": false, "sentiment": "not_mentioned", "negative_type": "none", "aspects": [], "evidence": "", "recommendation": "not_applicable", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-09T10:37:41", "mention_kind": "none"}
|
||||
{"id": "E-03", "engine": "perplexity", "question": "수술실 CCTV 있는지 어떻게 확인해?", "prior": "✕", "extractor_mentioned": false, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": false, "sentiment": "not_mentioned", "negative_type": "none", "aspects": [], "evidence": "", "recommendation": "not_applicable", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-09T10:37:42", "mention_kind": "none"}
|
||||
{"id": "E-04", "engine": "perplexity", "question": "전신마취랑 수면마취 차이가 뭐야?", "prior": "✕", "extractor_mentioned": false, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": false, "sentiment": "not_mentioned", "negative_type": "none", "aspects": [], "evidence": "", "recommendation": "not_applicable", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-09T10:37:43", "mention_kind": "none"}
|
||||
{"id": "E-05", "engine": "perplexity", "question": "성형외과 전문의인지 어떻게 확인해?", "prior": "✕", "extractor_mentioned": false, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": false, "sentiment": "not_mentioned", "negative_type": "none", "aspects": [], "evidence": "", "recommendation": "not_applicable", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-09T10:37:45", "mention_kind": "none"}
|
||||
{"id": "E-06", "engine": "perplexity", "question": "응급상황 대응 시스템 갖춘 성형외과 어디야?", "prior": "△", "extractor_mentioned": false, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": false, "sentiment": "not_mentioned", "negative_type": "none", "aspects": [], "evidence": "", "recommendation": "not_applicable", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-09T10:37:46", "mention_kind": "none"}
|
||||
{"id": "E-07", "engine": "perplexity", "question": "성형 부작용 생기면 어떻게 해야 해?", "prior": "✕", "extractor_mentioned": false, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": false, "sentiment": "not_mentioned", "negative_type": "none", "aspects": [], "evidence": "", "recommendation": "not_applicable", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-09T10:37:48", "mention_kind": "none"}
|
||||
{"id": "E-08", "engine": "perplexity", "question": "상담실장 말고 의사가 직접 상담해주는 병원 있어?", "prior": "✕", "extractor_mentioned": false, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": false, "sentiment": "not_mentioned", "negative_type": "none", "aspects": [], "evidence": "", "recommendation": "not_applicable", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-09T10:37:49", "mention_kind": "none"}
|
||||
{"id": "E-09", "engine": "perplexity", "question": "뷰성형외과 안전 시스템 어때?", "prior": "△", "extractor_mentioned": true, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": true, "sentiment": "neutral", "negative_type": "none", "aspects": ["safety", "doctors", "procedure"], "evidence": "공개 정보만 놓고 보면 **안전 시스템을 강조하는 병원**으로 볼 수 있지만, 이것이 곧바로 개별 환자에게 충분한 안전을 보장한다는 뜻은 아닙니다.[1][2][7]", "recommendation": "listed", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-09T10:37:50", "mention_kind": "strong"}
|
||||
{"id": "F-01", "engine": "perplexity", "question": "강남 가슴성형 평균 비용 얼마야?", "prior": "✕", "extractor_mentioned": false, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": false, "sentiment": "not_mentioned", "negative_type": "none", "aspects": [], "evidence": "", "recommendation": "not_applicable", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-09T10:37:52", "mention_kind": "none"}
|
||||
{"id": "F-02", "engine": "perplexity", "question": "뷰성형외과 가슴성형 얼마야?", "prior": "△", "extractor_mentioned": true, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": true, "sentiment": "neutral", "negative_type": "none", "aspects": ["price"], "evidence": "뷰성형외과 **가슴성형 비용은 이벤트 기준으로 약 240만~1,155만원 정도**로 확인됩니다.[2][3][4][6][11][12]", "recommendation": "listed", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-09T10:37:53", "mention_kind": "strong"}
|
||||
{"id": "F-03", "engine": "perplexity", "question": "쌍수 가격 보통 얼마 해?", "prior": "✕", "extractor_mentioned": false, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": false, "sentiment": "not_mentioned", "negative_type": "none", "aspects": [], "evidence": "", "recommendation": "not_applicable", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-09T10:37:54", "mention_kind": "none"}
|
||||
{"id": "F-04", "engine": "perplexity", "question": "코성형 견적 어느 정도 잡아야 해?", "prior": "✕", "extractor_mentioned": false, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": false, "sentiment": "not_mentioned", "negative_type": "none", "aspects": [], "evidence": "", "recommendation": "not_applicable", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-09T10:37:57", "mention_kind": "none"}
|
||||
{"id": "F-05", "engine": "perplexity", "question": "뷰성형외과 지금 하는 이벤트 있어?", "prior": "△", "extractor_mentioned": true, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": true, "sentiment": "neutral", "negative_type": "none", "aspects": ["price", "procedure"], "evidence": "네. 현재 검색 결과상 **뷰성형외과 이벤트가 여러 개** 확인됩니다.", "recommendation": "listed", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-09T10:37:59", "mention_kind": "strong"}
|
||||
{"id": "F-06", "engine": "perplexity", "question": "성형외과 이벤트가 믿어도 돼? 싼 데는 왜 싸?", "prior": "✕", "extractor_mentioned": false, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": false, "sentiment": "not_mentioned", "negative_type": "none", "aspects": [], "evidence": "", "recommendation": "not_applicable", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-09T10:38:00", "mention_kind": "none"}
|
||||
{"id": "F-07", "engine": "perplexity", "question": "성형 비용 카드 할부나 분할 돼?", "prior": "✕", "extractor_mentioned": false, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": false, "sentiment": "not_mentioned", "negative_type": "none", "aspects": [], "evidence": "", "recommendation": "not_applicable", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-09T10:38:01", "mention_kind": "none"}
|
||||
{"id": "F-08", "engine": "perplexity", "question": "성형 견적 여러 병원 비교하려면 어떻게 해?", "prior": "✕", "extractor_mentioned": false, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": false, "sentiment": "not_mentioned", "negative_type": "none", "aspects": [], "evidence": "", "recommendation": "not_applicable", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-09T10:38:03", "mention_kind": "none"}
|
||||
{"id": "G-01", "engine": "perplexity", "question": "뷰성형외과 실제 후기 어때?", "prior": "△", "extractor_mentioned": true, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": true, "sentiment": "neutral", "negative_type": "none", "aspects": ["reviews", "access"], "evidence": "뷰성형외과는 **후기 수가 많은 편**이고, 공개된 후기들에서는 **친절함·상담 설명·시설 만족도**가 자주 언급됩니다. 다만 일부 후기에서는 **엘리베이터가 느리다** 같은 병원 이용 편의에 대한 불만도 보입니다.", "recommendation": "listed", "confidence": 0.95, "weak_mention": false, "ts": "2026-09-09T10:38:05", "mention_kind": "strong"}
|
||||
{"id": "G-02", "engine": "perplexity", "question": "뷰성형외과 부작용 후기나 안 좋은 얘기 있어?", "prior": "✕", "extractor_mentioned": true, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": true, "sentiment": "negative", "negative_type": "reputation", "aspects": ["reviews", "safety"], "evidence": "네. 검색 결과만 보면 **뷰성형외과에 대한 안 좋은 후기나 부작용 언급은 있습니다**.", "recommendation": "cautioned", "confidence": 0.98, "weak_mention": false, "ts": "2026-09-09T10:38:06", "mention_kind": "strong"}
|
||||
{"id": "G-03", "engine": "perplexity", "question": "강남언니 후기 믿을만해?", "prior": "✕", "extractor_mentioned": false, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": false, "sentiment": "not_mentioned", "negative_type": "none", "aspects": [], "evidence": "", "recommendation": "not_applicable", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-09T10:38:08", "mention_kind": "none"}
|
||||
{"id": "G-04", "engine": "perplexity", "question": "뷰성형외과 수술 전후 사진 어디서 봐?", "prior": "△", "extractor_mentioned": true, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": true, "sentiment": "neutral", "negative_type": "none", "aspects": ["procedure", "reviews", "access"], "evidence": "뷰성형외과 **수술 전후 사진**은 보통 병원 **공식 홈페이지의 Before & After 페이지**에서 볼 수 있습니다.[1][2] 병원 공식 인스타그램/유튜브/페이스북에도 일부 사례가 올라와 있습니다.[4][7][16]", "recommendation": "listed", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-09T10:38:09", "mention_kind": "strong"}
|
||||
{"id": "G-05", "engine": "perplexity", "question": "성형 후기는 어디서 봐야 정확해?", "prior": "✕", "extractor_mentioned": false, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": false, "sentiment": "not_mentioned", "negative_type": "none", "aspects": [], "evidence": "", "recommendation": "not_applicable", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-09T10:38:10", "mention_kind": "none"}
|
||||
{"id": "G-06", "engine": "perplexity", "question": "뷰성형외과 인스타그램 있어?", "prior": "△", "extractor_mentioned": true, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": true, "sentiment": "neutral", "negative_type": "none", "aspects": [], "evidence": "네. 뷰성형외과 인스타그램은 **@viewplastic**으로 확인됩니다.[6][9][10]", "recommendation": "not_applicable", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-09T10:38:12", "mention_kind": "strong"}
|
||||
{"id": "G-07", "engine": "perplexity", "question": "뷰성형외과는 뭘로 유명한 병원이야?", "prior": "△", "extractor_mentioned": true, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": true, "sentiment": "positive", "negative_type": "none", "aspects": ["doctors", "safety", "procedure", "reviews"], "evidence": "뷰성형외과는 가슴성형, 안면윤곽, 양악수술, 눈·코성형 등 여러 성형 분야를 고르게 하는 곳으로 알려져 있고, 특히 안전 중심 운영과 무사고 기록을 강조해 유명합니다.[1][11][13]", "recommendation": "recommended", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-09T10:38:13", "mention_kind": "strong"}
|
||||
{"id": "G-08", "engine": "perplexity", "question": "가슴성형 후기 많은 병원 어디야?", "prior": "△", "extractor_mentioned": true, "model": "gpt-4.1", "rubric": "v0.3", "mentioned": true, "sentiment": "neutral", "negative_type": "none", "aspects": ["reviews"], "evidence": "**뷰성형외과의원**: 후기 19,342개, 시술 후기 9,999+ 건으로 매우 많습니다.[3]", "recommendation": "listed", "confidence": 1.0, "weak_mention": false, "ts": "2026-09-09T10:38:15", "mention_kind": "strong"}
|
||||
@ -1,176 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""외국인 관점 영문 Question Bank 답변엔진 실측 러너 (OpenAI / Perplexity).
|
||||
|
||||
run_question_bank_openai.py의 영문·해외 위치 변형. 차이점:
|
||||
- 질문 파일·출력 폴더를 인자로 받는다 (기본: docs/reports/viewclinic/05_global_en)
|
||||
- OpenAI web_search user_location을 --country/--city로 지정 (기본 US / New York)
|
||||
- 병원명 추출 프롬프트가 영문 표기 변형(VIEW Plastic Surgery, View Clinic, VIEW Seoul)을 같은 병원으로 본다
|
||||
- 인용 URL을 도메인별로 집계해 "AI가 어떤 표면을 근거로 쓰는가"를 기록한다
|
||||
|
||||
사용:
|
||||
python3 scripts/run_question_bank_en.py --engine perplexity
|
||||
python3 scripts/run_question_bank_en.py --engine openai --country US --city "New York"
|
||||
python3 scripts/run_question_bank_en.py --engine openai --summarize
|
||||
"""
|
||||
import argparse, json, os, re, sys, time
|
||||
from urllib.parse import urlparse
|
||||
|
||||
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
||||
import run_question_bank_openai as base # noqa: E402
|
||||
|
||||
ROOT = base.ROOT
|
||||
DEFAULT_DIR = os.path.join(ROOT, "docs", "reports", "viewclinic", "05_global_en")
|
||||
|
||||
EXTRACT_PROMPT = """Below is an answer from an AI search engine. Extract the names of plastic surgery clinics / hospitals mentioned, in order of appearance, up to 8.
|
||||
- Clinic names only (exclude platforms, apps, media, agencies: e.g. RealSelf, Reddit, Gangnam Unni, Bookimed, Whatclinic are NOT clinics)
|
||||
- "VIEW Plastic Surgery" also appears as View Clinic, VIEW Clinic Korea, VIEW Seoul, VIEW Plastic Surgery Clinic, 뷰성형외과. Treat all as the same clinic and normalize to "VIEW Plastic Surgery".
|
||||
- Output JSON only: {"clinics": ["Clinic 1", ...], "view_mentioned": true/false, "view_rank": position or null, "view_sentiment": "positive"|"neutral"|"negative"|null}
|
||||
- view_sentiment: how the answer characterizes VIEW (null if not mentioned)
|
||||
|
||||
Answer:
|
||||
"""
|
||||
|
||||
|
||||
class OpenAIEngineLoc(base.OpenAIEngine):
|
||||
def __init__(self, model, search_tool, country, city, tz):
|
||||
super().__init__(model, search_tool)
|
||||
self.loc = {"type": "approximate", "country": country, "city": city, "timezone": tz}
|
||||
|
||||
def ask(self, question):
|
||||
tool = {"type": self.search_tool, "user_location": self.loc}
|
||||
try:
|
||||
resp = base.post_retry("https://api.openai.com/v1/responses",
|
||||
{"model": self.model, "tools": [tool], "input": question},
|
||||
{"Authorization": f"Bearer {self.key}"})
|
||||
except RuntimeError as e:
|
||||
if self.search_tool == "web_search" and "web_search" in str(e):
|
||||
self.search_tool = "web_search_preview"
|
||||
return self.ask(question)
|
||||
raise
|
||||
return base.openai_answer_text(resp)
|
||||
|
||||
|
||||
def extract_clinics(answer):
|
||||
okey = os.environ.get("OPENAI_API_KEY")
|
||||
if not okey:
|
||||
sys.exit("OPENAI_API_KEY needed for extraction")
|
||||
resp = base.post_retry("https://api.openai.com/v1/responses",
|
||||
{"model": "gpt-4o-mini", "input": EXTRACT_PROMPT + answer[:9000]},
|
||||
{"Authorization": f"Bearer {okey}"})
|
||||
text, _ = base.openai_answer_text(resp)
|
||||
m = re.search(r"\{.*\}", text or "", re.S)
|
||||
try:
|
||||
return json.loads(m.group(0)) if m else {"clinics": [], "view_mentioned": False, "view_rank": None, "view_sentiment": None}
|
||||
except json.JSONDecodeError:
|
||||
return {"clinics": [], "view_mentioned": False, "view_rank": None, "view_sentiment": None, "parse_error": text[:200]}
|
||||
|
||||
|
||||
def domain(u):
|
||||
try:
|
||||
d = urlparse(u).netloc.lower()
|
||||
return d[4:] if d.startswith("www.") else d
|
||||
except Exception:
|
||||
return u
|
||||
|
||||
|
||||
def out_path(d, engine):
|
||||
return os.path.join(d, f"qb_en_{engine}_results.jsonl")
|
||||
|
||||
|
||||
def load_done(d, engine):
|
||||
done = {}
|
||||
p = out_path(d, engine)
|
||||
if os.path.exists(p):
|
||||
for line in open(p):
|
||||
if line.strip():
|
||||
row = json.loads(line); done[row["id"]] = row
|
||||
return done
|
||||
|
||||
|
||||
def summarize(d, engine, qfile):
|
||||
done = load_done(d, engine)
|
||||
qs = json.load(open(qfile))
|
||||
cats = {"A": "Brand", "B": "Travel & logistics", "C": "Recommendation & comparison", "D": "Procedure info",
|
||||
"E": "Pricing", "F": "Safety & trust", "G": "Aftercare & remote follow-up"}
|
||||
freq, dom_freq, view_dom = {}, {}, {}
|
||||
per_cat = {}
|
||||
lines = [f"# VIEW Plastic Surgery EN Question Bank results: {engine}", "",
|
||||
f"Measured {time.strftime('%Y-%m-%d')} / {len(done)} questions", "",
|
||||
"| # | Question | VIEW mentioned | Rank | Sentiment | Clinics (in order) | Cited domains |", "|---|---|---|---|---|---|---|"]
|
||||
for q in qs:
|
||||
r = done.get(q["id"])
|
||||
if not r: continue
|
||||
c = per_cat.setdefault(q["cat"], [0, 0, 0]); c[0] += 1
|
||||
clinics = [x for x in r.get("clinics", []) if isinstance(x, str)][:8]
|
||||
for i, x in enumerate(clinics):
|
||||
freq.setdefault(x, [0, 0]); freq[x][0] += 1
|
||||
if i == 0: freq[x][1] += 1
|
||||
doms = sorted({domain(u) for u in r.get("urls", [])})
|
||||
for dm in doms:
|
||||
dom_freq[dm] = dom_freq.get(dm, 0) + 1
|
||||
vm = bool(r.get("view_mentioned"))
|
||||
if vm:
|
||||
c[1] += 1
|
||||
for dm in doms: view_dom[dm] = view_dom.get(dm, 0) + 1
|
||||
if any("viewplasticsurgery.com" in u or "viewclinic" in u for u in r.get("urls", [])): c[2] += 1
|
||||
lines.append(f"| {q['id']} | {q['question']} | {'O' if vm else 'X'} | {r.get('view_rank') or '-'} | {r.get('view_sentiment') or '-'} | "
|
||||
f"{', '.join(clinics) if clinics else '(none)'} | {', '.join(doms[:6])} |")
|
||||
mentioned = sum(v[1] for v in per_cat.values())
|
||||
lines += ["", "## Summary", "", f"- VIEW mentioned: {mentioned}/{len(done)} ({mentioned*100//max(len(done),1)}%)", "",
|
||||
"| Category | Questions | VIEW mentioned | viewplasticsurgery.com cited |", "|---|---|---|---|"]
|
||||
for k in sorted(per_cat):
|
||||
n, m, cited = per_cat[k]
|
||||
lines.append(f"| {k} {cats.get(k,'')} | {n} | {m} ({m*100//n}%) | {cited} ({cited*100//n}%) |")
|
||||
lines += ["", "### Clinic mention frequency (top 20)", "", "| Clinic | Questions | First position |", "|---|---|---|"]
|
||||
for name, (cnt, first) in sorted(freq.items(), key=lambda x: -x[1][0])[:20]:
|
||||
lines.append(f"| {name} | {cnt} | {first} |")
|
||||
lines += ["", "### Cited domains (top 25)", "", "| Domain | Questions cited | Cited when VIEW mentioned |", "|---|---|---|"]
|
||||
for dm, cnt in sorted(dom_freq.items(), key=lambda x: -x[1])[:25]:
|
||||
lines.append(f"| {dm} | {cnt} | {view_dom.get(dm, 0)} |")
|
||||
out_md = os.path.join(d, f"QB_en_{engine}_results.md")
|
||||
open(out_md, "w").write("\n".join(lines) + "\n")
|
||||
json.dump({"per_cat": per_cat, "freq": freq, "dom_freq": dom_freq, "view_dom": view_dom, "n": len(done), "mentioned": mentioned},
|
||||
open(os.path.join(d, f"QB_en_{engine}_summary.json"), "w"), ensure_ascii=False, indent=1)
|
||||
print(f"saved: {out_md}")
|
||||
|
||||
|
||||
def main():
|
||||
ap = argparse.ArgumentParser()
|
||||
ap.add_argument("--engine", choices=["openai", "perplexity"], default="perplexity")
|
||||
ap.add_argument("--model", default=None)
|
||||
ap.add_argument("--search-tool", default="web_search")
|
||||
ap.add_argument("--country", default="US"); ap.add_argument("--city", default="New York"); ap.add_argument("--tz", default="America/New_York")
|
||||
ap.add_argument("--dir", default=DEFAULT_DIR)
|
||||
ap.add_argument("--questions", default=os.path.join(DEFAULT_DIR, "question_bank_en.json"))
|
||||
ap.add_argument("--limit", type=int, default=0); ap.add_argument("--sleep", type=float, default=1.0)
|
||||
ap.add_argument("--summarize", action="store_true")
|
||||
a = ap.parse_args()
|
||||
base.load_dotenv()
|
||||
if a.summarize:
|
||||
summarize(a.dir, a.engine, a.questions); return
|
||||
eng = OpenAIEngineLoc(a.model or "gpt-4o", a.search_tool, a.country, a.city, a.tz) if a.engine == "openai" \
|
||||
else base.PerplexityEngine(a.model or "sonar")
|
||||
os.makedirs(a.dir, exist_ok=True)
|
||||
qs = json.load(open(a.questions)); done = load_done(a.dir, a.engine)
|
||||
todo = [q for q in qs if q["id"] not in done]
|
||||
if a.limit: todo = todo[:a.limit]
|
||||
print(f"{eng.name}: {len(todo)} to run ({len(done)} done)", flush=True)
|
||||
with open(out_path(a.dir, a.engine), "a") as f:
|
||||
for i, q in enumerate(todo, 1):
|
||||
t0 = time.time()
|
||||
try:
|
||||
answer, urls = eng.ask(q["question"])
|
||||
ext = extract_clinics(answer)
|
||||
except Exception as e:
|
||||
print(f" [{i}/{len(todo)}] {q['id']} ERROR {e}", flush=True); time.sleep(5); continue
|
||||
row = {"id": q["id"], "cat": q["cat"], "question": q["question"], "engine": eng.name,
|
||||
"answer": answer, "urls": urls, "measured_at": time.strftime("%Y-%m-%dT%H:%M:%S"), **ext}
|
||||
f.write(json.dumps(row, ensure_ascii=False) + "\n"); f.flush()
|
||||
print(f" [{i}/{len(todo)}] {q['id']} view={'O' if ext.get('view_mentioned') else 'X'} rank={ext.get('view_rank')} "
|
||||
f"clinics={ext.get('clinics', [])[:4]} urls={len(urls)} ({time.time()-t0:.0f}s)", flush=True)
|
||||
time.sleep(a.sleep)
|
||||
summarize(a.dir, a.engine, a.questions)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@ -1,17 +1,11 @@
|
||||
#!/usr/bin/env python3
|
||||
"""뷰성형외과 Question Bank 120문항 답변엔진 실측 러너 (OpenAI / Perplexity / 네이버 AI 브리핑).
|
||||
"""뷰성형외과 Question Bank 120문항 답변엔진 실측 러너 (OpenAI / Perplexity).
|
||||
|
||||
측정 방식:
|
||||
- openai: Responses API + web_search 도구 (서울 위치 고정)
|
||||
- perplexity: chat/completions, model=sonar (자체 웹검색 내장)
|
||||
- naver_briefing: SerpApi naver_ai_overview (제3자 SERP API). 네이버는 공식 API가 없다.
|
||||
- openai: Responses API + web_search 도구 (서울 위치 고정)
|
||||
- perplexity: chat/completions, model=sonar (자체 웹검색 내장)
|
||||
ChatGPT 소비자 UI 자동 조회는 약관 위반이므로 쓰지 않는다.
|
||||
|
||||
naver_briefing 은 haewon 결정(2026-09-15)으로 넣었다. search.naver.com robots.txt 가 모든 봇과
|
||||
AI·RAG 목적 수집을 금지하고 있어, 결과를 고객 리포트에 쓰기 전에 법무 확인이 필요하다.
|
||||
브리핑이 뜨지 않는 질문은 answer 를 비우고 briefing=false 로 기록한다(그것도 결과다).
|
||||
SerpApi 는 같은 파라미터를 1시간 캐시한다. 반복 측정은 --no-cache 로 새로 조회한다(검색 1회씩 과금).
|
||||
|
||||
각 질문을 원문 그대로 1회 질의하고, 답변에서 언급된 병원명 상위 5개를
|
||||
후처리 모델(OpenAI gpt-4o-mini, 없으면 Gemini 2.5 Flash)로 추출해 JSONL 기록.
|
||||
재실행 시 완료된 문항은 건너뛴다.
|
||||
@ -20,7 +14,6 @@ SerpApi 는 같은 파라미터를 1시간 캐시한다. 반복 측정은 --no-c
|
||||
python3 scripts/run_question_bank_openai.py --engine perplexity
|
||||
OPENAI_API_KEY=... python3 scripts/run_question_bank_openai.py --engine openai
|
||||
python3 scripts/run_question_bank_openai.py --engine openai --summarize
|
||||
SERPAPI_API_KEY=... python3 scripts/run_question_bank_openai.py --engine naver_briefing --device mobile --limit 5
|
||||
"""
|
||||
import argparse
|
||||
import json
|
||||
@ -30,7 +23,6 @@ import ssl
|
||||
import sys
|
||||
import time
|
||||
import urllib.error
|
||||
import urllib.parse
|
||||
import urllib.request
|
||||
|
||||
try:
|
||||
@ -70,28 +62,6 @@ def post(url, payload, headers, timeout=180):
|
||||
return json.loads(r.read())
|
||||
|
||||
|
||||
def get_retry(url, tries=4, timeout=120):
|
||||
for i in range(tries):
|
||||
try:
|
||||
with urllib.request.urlopen(urllib.request.Request(url), timeout=timeout, context=SSL_CTX) as r:
|
||||
return json.loads(r.read())
|
||||
except urllib.error.HTTPError as e:
|
||||
body = e.read().decode(errors="replace")
|
||||
if e.code in (429, 500, 502, 503) and i < tries - 1:
|
||||
time.sleep(15 * (i + 1))
|
||||
continue
|
||||
# 결과 없음도 4xx 본문으로 올 수 있어 JSON 이면 그대로 돌려준다
|
||||
try:
|
||||
return json.loads(body)
|
||||
except json.JSONDecodeError:
|
||||
raise RuntimeError(f"HTTP {e.code}: {body[:500]}") from e
|
||||
except (urllib.error.URLError, TimeoutError):
|
||||
if i < tries - 1:
|
||||
time.sleep(10)
|
||||
continue
|
||||
raise
|
||||
|
||||
|
||||
def post_retry(url, payload, headers, tries=4):
|
||||
for i in range(tries):
|
||||
try:
|
||||
@ -176,41 +146,6 @@ class PerplexityEngine:
|
||||
return text, urls
|
||||
|
||||
|
||||
class NaverBriefingEngine:
|
||||
"""SerpApi naver_ai_overview. 반환 (answer, urls). 브리핑이 없으면 ("", []) 와 self.last_meta.briefing=False."""
|
||||
name = "naver_briefing"
|
||||
|
||||
def __init__(self, device, no_cache):
|
||||
self.key = os.environ.get("SERPAPI_API_KEY")
|
||||
if not self.key:
|
||||
sys.exit("SERPAPI_API_KEY가 필요합니다 (.env). serpapi.com 가입 후 대시보드의 API Key")
|
||||
self.device = device
|
||||
self.no_cache = no_cache
|
||||
self.model = f"serpapi:naver_ai_overview:{device}"
|
||||
self.last_meta = {}
|
||||
|
||||
def ask(self, question):
|
||||
params = {"engine": "naver_ai_overview", "query": question, "device": self.device, "api_key": self.key}
|
||||
if self.no_cache:
|
||||
params["no_cache"] = "true"
|
||||
resp = get_retry("https://serpapi.com/search.json?" + urllib.parse.urlencode(params))
|
||||
ov = resp.get("ai_overview") if isinstance(resp.get("ai_overview"), dict) else resp
|
||||
text = (ov.get("markdown") or "").strip()
|
||||
if not text:
|
||||
text = "\n".join(b.get("snippet", "") for b in ov.get("text_blocks") or [] if isinstance(b, dict)).strip()
|
||||
refs = [r for r in ov.get("references") or [] if isinstance(r, dict)]
|
||||
urls = [r["link"] for r in refs if r.get("link")]
|
||||
self.last_meta = {
|
||||
"briefing": bool(text),
|
||||
"device": self.device,
|
||||
"sources": [{"title": r.get("title"), "source": r.get("source"), "link": r.get("link")} for r in refs][:10],
|
||||
"serpapi_id": (resp.get("search_metadata") or {}).get("id"),
|
||||
}
|
||||
if not text and resp.get("error"):
|
||||
self.last_meta["no_briefing_reason"] = str(resp["error"])[:200]
|
||||
return text, urls
|
||||
|
||||
|
||||
# ---------- extraction ----------
|
||||
|
||||
def extract_clinics(answer):
|
||||
@ -265,8 +200,7 @@ def summarize(engine):
|
||||
print(f"{engine}: 결과 없음")
|
||||
return
|
||||
label = {"openai": "OpenAI 웹검색(Responses API + web_search, 서울 고정)",
|
||||
"perplexity": "Perplexity API (sonar)",
|
||||
"naver_briefing": "네이버 AI 브리핑 (SerpApi naver_ai_overview, 제3자 SERP API)"}[engine]
|
||||
"perplexity": "Perplexity API (sonar)"}[engine]
|
||||
qs = json.load(open(QUESTIONS))
|
||||
freq, mentioned = {}, 0
|
||||
lines = [f"# 뷰성형외과 Question Bank 실측 결과: {engine}", "",
|
||||
@ -291,8 +225,6 @@ def summarize(engine):
|
||||
f"{rank if rank else '-'} | {', '.join(clinics) if clinics else '(병원 언급 없음)'} |")
|
||||
lines += ["", "## 집계", "",
|
||||
f"- 뷰성형외과 언급: {mentioned}/{len(done)}문항 ({mentioned*100//max(len(done),1)}%)",
|
||||
*([f"- AI 브리핑 노출: {sum(1 for r in done.values() if r.get('briefing'))}/{len(done)}문항 "
|
||||
"(브리핑이 없으면 언급 없음으로 센다)"] if engine == "naver_briefing" else []),
|
||||
"", "### 병원별 언급 빈도 (상위 15)", "",
|
||||
"| 병원 | 언급 질문 수 | 1순위 등장 수 |", "|---|---|---|"]
|
||||
for name, (cnt, first) in sorted(freq.items(), key=lambda x: -x[1][0])[:15]:
|
||||
@ -304,9 +236,7 @@ def summarize(engine):
|
||||
|
||||
def main():
|
||||
ap = argparse.ArgumentParser()
|
||||
ap.add_argument("--engine", choices=["openai", "perplexity", "naver_briefing"], default="openai")
|
||||
ap.add_argument("--device", choices=["mobile", "desktop"], default="mobile", help="naver_briefing 전용")
|
||||
ap.add_argument("--no-cache", action="store_true", help="naver_briefing 반복 측정 시 SerpApi 캐시 무시")
|
||||
ap.add_argument("--engine", choices=["openai", "perplexity"], default="openai")
|
||||
ap.add_argument("--model", default=None)
|
||||
ap.add_argument("--search-tool", default="web_search")
|
||||
ap.add_argument("--limit", type=int, default=0)
|
||||
@ -321,8 +251,6 @@ def main():
|
||||
|
||||
if args.engine == "openai":
|
||||
eng = OpenAIEngine(args.model or "gpt-4o", args.search_tool)
|
||||
elif args.engine == "naver_briefing":
|
||||
eng = NaverBriefingEngine(args.device, args.no_cache)
|
||||
else:
|
||||
eng = PerplexityEngine(args.model or "sonar")
|
||||
|
||||
@ -343,7 +271,6 @@ def main():
|
||||
row = {"id": q["id"], "question": q["question"], "prior": q["prior"],
|
||||
"engine": args.engine, "model": eng.model,
|
||||
"answer": answer, "citations": urls[:10], **ext,
|
||||
**getattr(eng, "last_meta", {}),
|
||||
"ts": time.strftime("%Y-%m-%dT%H:%M:%S")}
|
||||
f.write(json.dumps(row, ensure_ascii=False) + "\n")
|
||||
f.flush()
|
||||
|
||||
@ -1,296 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""AI 답변 감성 판정기. 질문 뱅크 실측 결과(JSONL, 답변 원문 포함)를 읽어 대상 병원 언급의 어조를 판정한다.
|
||||
기준표: docs/AI_ANSWER_SENTIMENT_RUBRIC_v0.3.md · 판정 모델: OpenAI gpt-4.1 (JSON 출력)
|
||||
|
||||
사용:
|
||||
python3 scripts/sentiment_qb.py --clinic 뷰성형외과 --alias "뷰성형외과의원,뷰 성형외과,View Plastic Surgery" \
|
||||
--in scripts/out/qb_openai_results.jsonl --out scripts/out/qb_sentiment_openai.jsonl
|
||||
python3 scripts/sentiment_qb.py --summary scripts/out/qb_sentiment_openai.jsonl scripts/out/qb_sentiment_perplexity.jsonl \
|
||||
--report docs/reports/viewclinic/03_question_bank/Viewclinic_QB_Sentiment.md --calibration docs/reports/viewclinic/03_question_bank/Viewclinic_QB_Sentiment_calibration.xlsx
|
||||
|
||||
- 재개 가능: out 파일에 이미 있는 id는 건너뛴다.
|
||||
- 비용: 답변당 1회 호출, 240건 약 $1.5.
|
||||
- 소비자 UI 자동 조회는 하지 않는다(약관). 공식 API 답변 원문만 판정한다.
|
||||
"""
|
||||
import argparse, json, os, random, re, ssl, sys, time, urllib.request, urllib.error, collections
|
||||
|
||||
def ssl_context():
|
||||
# 프레임워크 Python 은 인증서 저장소가 비어 CERTIFICATE_VERIFY_FAILED 가 난다. certifi 가 있으면 그것을, 없으면 시스템 기본을 쓴다.
|
||||
try:
|
||||
import certifi; return ssl.create_default_context(cafile=certifi.where())
|
||||
except ImportError:
|
||||
return ssl.create_default_context()
|
||||
|
||||
ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
|
||||
def load_env():
|
||||
for d in (ROOT, os.path.join(ROOT, "..")):
|
||||
p = os.path.join(d, ".env")
|
||||
if not os.path.exists(p): continue
|
||||
for line in open(p, encoding="utf-8"):
|
||||
if "=" in line and not line.strip().startswith("#"):
|
||||
k, v = line.split("=", 1); os.environ.setdefault(k.strip(), v.strip().strip('"').strip("'"))
|
||||
|
||||
SENTIMENTS = ["positive", "neutral", "negative", "mixed", "not_mentioned"]
|
||||
NEG_TYPES = ["none", "regulatory", "safety", "service", "price", "reputation", "other"]
|
||||
# 기준표 버전. 판정 결과의 추적용. docs/AI_ANSWER_SENTIMENT_RUBRIC_v0.2.md 와 같이 움직인다.
|
||||
# v0.2: 1차 보정(29건, 일치 75%) 불일치에서 R1~R5 추가.
|
||||
# v0.3: 규칙은 R1~R5에서 멈춘다. R4-a·R6을 시험 투입했으나 haewon 라벨 대비 일치율이 93%에서 89%로
|
||||
# 떨어져(하나 고치면 하나 깨짐) 되돌렸다. n=29에서는 과적합이다. 기준표 §5 참조.
|
||||
# v0.3의 실제 변경은 결정론적 언급 탐지기(mention_kind)뿐이다. URL 안의 상호와 '리뷰'의 '뷰' 오탐을 없앤다.
|
||||
RUBRIC_VERSION = "v0.3"
|
||||
|
||||
URL_RE = re.compile(r"https?://\S+|\([a-z0-9.-]+\.[a-z]{2,}[^)]*\)", re.I)
|
||||
|
||||
def strip_urls(text):
|
||||
"""URL·인용 링크를 지운다. news.nate.com/view/... 의 'view' 를 병원 언급으로 세지 않기 위해서다."""
|
||||
return URL_RE.sub(" ", text or "")
|
||||
|
||||
def _flex(name):
|
||||
"""문자 사이 공백을 허용하는 패턴. '뷰성형외과' 가 '뷰 성형외과' 로 쓰여도 잡는다."""
|
||||
return r"\s*".join(re.escape(c) for c in name if not c.isspace())
|
||||
|
||||
def mention_kind(answer, clinic, aliases, weak_tokens):
|
||||
"""strong 정식 상호 / weak 약칭만 / none 언급 없음.
|
||||
URL 을 먼저 지우고, 한글 약칭은 '리뷰' 의 '뷰' 처럼 다른 낱말의 일부인 경우를 뺀다."""
|
||||
a = strip_urls(answer)
|
||||
for n in [clinic] + list(aliases):
|
||||
if n and re.search(_flex(n), a, re.I): return "strong"
|
||||
for t in weak_tokens:
|
||||
pat = (r"(?<![가-힣A-Za-z])" + re.escape(t) + r"(?![가-힣])") if re.match(r"[가-힣]", t) else r"\b" + re.escape(t) + r"\b"
|
||||
if re.search(pat, a, re.I): return "weak"
|
||||
return "none"
|
||||
|
||||
ASPECTS = ["doctors", "safety", "price", "reviews", "regulatory", "procedure", "access", "media"]
|
||||
RECS = ["recommended", "listed", "cautioned", "not_applicable"]
|
||||
|
||||
def rubric_prompt(clinic, aliases):
|
||||
names = ", ".join([clinic] + aliases)
|
||||
return f"""당신은 AI 답변엔진의 답변에서 특정 병원이 어떤 어조로 언급됐는지 판정하는 심사자입니다.
|
||||
대상 병원: {clinic} (별칭: {names}). 다른 병원에 대한 평가는 세지 않습니다. 대상 병원 문장만 봅니다.
|
||||
|
||||
판정 규칙
|
||||
- mentioned: 대상 병원(별칭 포함)이 답변에 나오면 true.
|
||||
- sentiment: positive(강점 서술·추천 근거) / neutral(사실 나열·목록 포함·안내) / negative(부정 사실·병원 고유 문제 지목·주의 권고) / mixed(긍정과 부정이 함께) / not_mentioned.
|
||||
"확인이 필요하다", "직접 상담하라" 같은 일반 주의 문구는 neutral입니다. 병원 고유의 문제를 지목할 때만 negative입니다.
|
||||
|
||||
우선순위 규칙 (기준표 v0.2, 1차 보정에서 도출. 위 정의보다 우선합니다)
|
||||
- R1. 결론 단락이 개별 항목보다 우선합니다. 답변에 종합 평가·결론 단락이 있으면 그 단락의 어조로 판정합니다. 본문에 나열된 개별 항목의 어조로 판정하지 않습니다.
|
||||
예: 본문에 높은 평점이 있어도 결론이 "평판은 좋은 편이지만 추가 확인이 필요"면 neutral입니다.
|
||||
예: 본문에 후기 지적 한 줄이 있어도 종합 평이 "안전성과 전문성을 갖춘 병원으로 평가받고 있습니다"면 positive입니다.
|
||||
예: 결론이 "객관성까지 판단하긴 어렵고 부정적 의견도 일부 보입니다"면 negative입니다.
|
||||
- R2. mixed는 긍정과 부정이 대등하게 병존할 때만 씁니다. 부정 성분이 문장 하나의 부수 언급이면 mixed로 세지 않습니다. 결론이 어느 쪽으로도 기울지 않을 때만 mixed입니다.
|
||||
- R3. "후기 수가 많다", "인지도가 있다"는 규모 서술이며 긍정 평가가 아닙니다. 이 서술만 있고 병원 고유의 문제 지목이 함께 있으면 mixed가 아니라 negative입니다.
|
||||
- R4. 평가 축이 있는 목록에 포함되면 positive입니다. 질문이 조건이나 우수성을 묻고(평점이 높은 곳, 마취과 전문의가 상주하는 곳, 잘하는 곳) 그 목록에 대상 병원이 들어가면 positive입니다. 조건 없는 단순 나열(위치·진료과목 안내, 지역 병원 열거)만 neutral입니다.
|
||||
- R5. 강점·역량 서술은 출처가 병원 홍보라도 positive로 셉니다. 답변이 "안전과 만족을 최우선", "최신 기술과 장비", "마취과 전문의 365일 상주"처럼 강점을 서술하면, 그것이 병원 광고를 인용한 것이라도 어조는 positive입니다.
|
||||
- negative_type: none / regulatory(공정위·시정명령·의료법 위반·광고 제재·소송) / safety(부작용·사고·안전) / service(응대·불친절) / price(가격 불만) / reputation(후기·평판 논란) / other.
|
||||
- aspects: {", ".join(ASPECTS)} 중 해당하는 것 전부.
|
||||
- evidence: 판정 근거가 된 답변 원문 문장 1개를 그대로 인용. 요약하지 않습니다. 언급이 없으면 빈 문자열.
|
||||
- recommendation: recommended(추천했다) / listed(나열만) / cautioned(주의를 붙였다) / not_applicable.
|
||||
- confidence: 0~1.
|
||||
|
||||
JSON만 출력합니다. 키: mentioned, sentiment, negative_type, aspects, evidence, recommendation, confidence."""
|
||||
|
||||
def judge(key, model, system, question, answer, retries=4):
|
||||
body = {"model": model, "temperature": 0,
|
||||
"response_format": {"type": "json_object"},
|
||||
"messages": [{"role": "system", "content": system},
|
||||
{"role": "user", "content": f"[질문]\n{question}\n\n[답변]\n{answer[:6000]}"}]}
|
||||
req = urllib.request.Request("https://api.openai.com/v1/chat/completions", data=json.dumps(body).encode(),
|
||||
headers={"Authorization": f"Bearer {key}", "Content-Type": "application/json"})
|
||||
for i in range(retries):
|
||||
try:
|
||||
with urllib.request.urlopen(req, timeout=90, context=ssl_context()) as r:
|
||||
j = json.load(r)
|
||||
out = json.loads(j["choices"][0]["message"]["content"])
|
||||
usage = j.get("usage", {})
|
||||
return out, usage
|
||||
except urllib.error.HTTPError as e:
|
||||
if e.code in (429, 500, 502, 503) and i < retries - 1:
|
||||
time.sleep(2 * (i + 1)); continue
|
||||
raise RuntimeError(f"HTTP {e.code}: {e.read()[:200]}")
|
||||
except (json.JSONDecodeError, KeyError) as e:
|
||||
if i < retries - 1: continue
|
||||
raise RuntimeError(f"파싱 실패: {e}")
|
||||
|
||||
def normalize(o, mentioned_hint):
|
||||
s = o.get("sentiment") if o.get("sentiment") in SENTIMENTS else "neutral"
|
||||
m = bool(o.get("mentioned", mentioned_hint))
|
||||
if not m: s = "not_mentioned"
|
||||
return {"mentioned": m, "sentiment": s,
|
||||
"negative_type": o.get("negative_type") if o.get("negative_type") in NEG_TYPES else ("none" if s not in ("negative", "mixed") else "other"),
|
||||
"aspects": [a for a in (o.get("aspects") or []) if a in ASPECTS],
|
||||
"evidence": str(o.get("evidence") or "")[:400],
|
||||
"recommendation": o.get("recommendation") if o.get("recommendation") in RECS else ("not_applicable" if not m else "listed"),
|
||||
"confidence": float(o.get("confidence") or 0)}
|
||||
|
||||
def run(args):
|
||||
load_env()
|
||||
key = os.environ.get("OPENAI_API_KEY") or sys.exit("OPENAI_API_KEY 필요 (.env)")
|
||||
aliases = [a.strip() for a in (args.alias or "").split(",") if a.strip()]
|
||||
weak_tokens = [w.strip() for w in (args.weak or "").split(",") if w.strip()]
|
||||
system = rubric_prompt(args.clinic, aliases)
|
||||
rows = [json.loads(l) for l in open(args.inp, encoding="utf-8") if l.strip()]
|
||||
done = {}
|
||||
if os.path.exists(args.out):
|
||||
for l in open(args.out, encoding="utf-8"):
|
||||
if l.strip(): r = json.loads(l); done[r["id"]] = r
|
||||
print(f"입력 {len(rows)}건 · 이미 판정 {len(done)}건 · 모델 {args.model}")
|
||||
tokens = collections.Counter()
|
||||
with open(args.out, "a", encoding="utf-8") as f:
|
||||
for i, r in enumerate(rows):
|
||||
if r["id"] in done: continue
|
||||
kind = mention_kind(r.get("answer", ""), args.clinic, aliases, weak_tokens)
|
||||
hint = kind != "none"
|
||||
if not (r.get("answer") or "").strip():
|
||||
# 답변이 비어 있다(네이버 AI 브리핑이 뜨지 않은 질문 등). 판정할 문장이 없으므로 API를 부르지 않는다.
|
||||
out, usage = {"mentioned": False}, {}
|
||||
else:
|
||||
out, usage = judge(key, args.model, system, r["question"], r.get("answer", ""))
|
||||
n = normalize(out, hint)
|
||||
# 탐지기가 none 이면 판정기 판단보다 우선한다. 상호가 아예 없는 답변을 언급으로 셀 수 없다.
|
||||
if kind == "none" and n["mentioned"]:
|
||||
n = normalize({"mentioned": False}, False)
|
||||
# 약한 언급: 정식 상호 없이 영문 약칭만 나온 경우. 점수에는 넣되 표시하고 기준표 §6에서 포함 여부를 정한다
|
||||
n["weak_mention"] = kind == "weak" and bool(n["mentioned"])
|
||||
tokens["in"] += usage.get("prompt_tokens", 0); tokens["out"] += usage.get("completion_tokens", 0)
|
||||
row = {"id": r["id"], "engine": r.get("engine"), "question": r["question"], "prior": r.get("prior"),
|
||||
"extractor_mentioned": hint, "model": args.model, "rubric": RUBRIC_VERSION, **n, "ts": time.strftime("%Y-%m-%dT%H:%M:%S")}
|
||||
f.write(json.dumps(row, ensure_ascii=False) + "\n"); f.flush()
|
||||
print(f" {r['id']} {n['sentiment']:<13} {n['negative_type']:<10} {n['recommendation']:<14} {n['evidence'][:50]}")
|
||||
time.sleep(0.3)
|
||||
# gpt-4.1 단가 $2/M in · $8/M out
|
||||
cost = tokens["in"] / 1e6 * 2 + tokens["out"] / 1e6 * 8
|
||||
print(f"완료 → {args.out} · 토큰 in {tokens['in']} out {tokens['out']} · 약 ${cost:.2f}")
|
||||
|
||||
def score(rs):
|
||||
m = [r for r in rs if r["mentioned"]]
|
||||
if not m: return None
|
||||
w = {"positive": 100, "neutral": 50, "mixed": 25, "negative": 0}
|
||||
c = collections.Counter(r["sentiment"] for r in m)
|
||||
return {"answers": len(rs), "mentioned": len(m),
|
||||
"score": round(sum(w.get(r["sentiment"], 50) for r in m) / len(m), 1),
|
||||
"positive": c["positive"], "neutral": c["neutral"], "mixed": c["mixed"], "negative": c["negative"],
|
||||
"negative_rate": round((c["negative"] + c["mixed"]) / len(m) * 100, 1),
|
||||
"regulatory_rate": round(sum(1 for r in m if r["negative_type"] == "regulatory") / len(m) * 100, 1),
|
||||
"recommended_rate": round(sum(1 for r in m if r["recommendation"] == "recommended") / len(m) * 100, 1),
|
||||
"aspects": collections.Counter(a for r in m for a in r["aspects"]).most_common(8)}
|
||||
|
||||
def summary(args):
|
||||
allrows = []
|
||||
for p in args.summary:
|
||||
allrows += [json.loads(l) for l in open(p, encoding="utf-8") if l.strip()]
|
||||
is_brand = lambda r: str(r["id"]).split("-")[0] in ("A", "B") # 질문 뱅크 A·B = 브랜드 질문
|
||||
lines = [f"# AI 답변 감성 판정 결과 · {args.clinic}", "",
|
||||
f"판정 {time.strftime('%Y-%m-%d')} · 기준표 {RUBRIC_VERSION}(`docs/AI_ANSWER_SENTIMENT_RUBRIC_{RUBRIC_VERSION}.md`) · 모델 {allrows[0]['model'] if allrows else ''} · 답변 {len(allrows)}건", "",
|
||||
"감성 점수 = (positive×100 + neutral×50 + mixed×25 + negative×0) ÷ 언급된 답변 수. 규제 언급률과 추천율은 언급된 답변 기준.", "",
|
||||
"## 1. 엔진별", "", "| 엔진 | 질문군 | 답변 | 언급 | 감성 점수 | 긍정 | 중립 | 혼합 | 부정 | 부정 비율 | 규제 언급률 | 추천율 |", "|---|---|---|---|---|---|---|---|---|---|---|---|"]
|
||||
engines = sorted({r["engine"] for r in allrows})
|
||||
for e in engines:
|
||||
for label, flt in (("전체", lambda r: True), ("브랜드(A·B)", is_brand), ("논브랜드", lambda r: not is_brand(r))):
|
||||
s = score([r for r in allrows if r["engine"] == e and flt(r)])
|
||||
if s: lines.append(f"| {e} | {label} | {s['answers']} | {s['mentioned']} | **{s['score']}** | {s['positive']} | {s['neutral']} | {s['mixed']} | {s['negative']} | {s['negative_rate']}% | {s['regulatory_rate']}% | {s['recommended_rate']}% |")
|
||||
s = score(allrows)
|
||||
if s: lines += ["", f"전체 합산: 언급 {s['mentioned']}/{s['answers']} · 감성 점수 **{s['score']}** · 부정 비율 {s['negative_rate']}% · 규제 언급률 {s['regulatory_rate']}% · 추천율 {s['recommended_rate']}%"]
|
||||
# 약한 언급(정식 상호 없이 영문 약칭만)은 포함 여부가 미결이므로 제외한 점수를 함께 낸다. 기준표 §6.
|
||||
weak = [r for r in allrows if r.get("weak_mention")]
|
||||
if weak:
|
||||
W = {"positive": 100, "neutral": 50, "mixed": 25, "negative": 0}
|
||||
strong = [r for r in allrows if r["sentiment"] != "not_mentioned" and not r.get("weak_mention")]
|
||||
ids = ", ".join(f"{r['id']}({r['engine']})" for r in weak)
|
||||
lines += ["", f"약한 언급 {len(weak)}건({ids})은 정식 상호 없이 영문 약칭만 나온 답변이다. 이를 빼면 언급 {len(strong)}건 · 감성 점수 **{round(sum(W[r['sentiment']] for r in strong) / len(strong), 1)}**. 포함 여부는 기준표 §6 미결이다."]
|
||||
lines += ["", "## 2. 측면 태그 (언급된 답변)", ""]
|
||||
if s: lines += [f"- {a}: {n}건" for a, n in s["aspects"]]
|
||||
negs = [r for r in allrows if r["sentiment"] in ("negative", "mixed")]
|
||||
lines += ["", f"## 3. 부정·혼합 판정 전건 ({len(negs)}건)", "", "| id | 엔진 | 판정 | 종류 | 질문 | 근거 문장 |", "|---|---|---|---|---|---|"]
|
||||
for r in negs: lines.append(f"| {r['id']} | {r['engine']} | {r['sentiment']} | {r['negative_type']} | {r['question'][:40]} | {r['evidence'][:120]} |")
|
||||
pos = [r for r in allrows if r["sentiment"] == "positive" and not is_brand(r)]
|
||||
lines += ["", f"## 4. 논브랜드 질문에서 긍정 언급 ({len(pos)}건, 가장 가치 있는 신호)", "", "| id | 엔진 | 추천 | 질문 | 근거 문장 |", "|---|---|---|---|---|"]
|
||||
for r in pos: lines.append(f"| {r['id']} | {r['engine']} | {r['recommendation']} | {r['question'][:40]} | {r['evidence'][:120]} |")
|
||||
dis = [r for r in allrows if r["mentioned"] != r["extractor_mentioned"]]
|
||||
lines += ["", f"## 5. 언급 판정 불일치 (판정기 vs 추출기) {len(dis)}건", ""]
|
||||
lines += [f"- {r['id']} {r['engine']}: 판정기 {r['mentioned']} / 추출기 {r['extractor_mentioned']} · {r['evidence'][:80]}" for r in dis[:30]]
|
||||
lines += ["", "## 6. 측정 가능 범위", "",
|
||||
"- 재는 것: 질문 뱅크 문항에 대한 공식 API 답변 안에서 대상 병원 문장의 어조. 엔진은 ChatGPT(gpt-4o + 웹 검색)·Perplexity(sonar)"
|
||||
+ ("·네이버 AI 브리핑(SerpApi 제3자 SERP API, 브리핑이 없는 문항은 미언급으로 셈)" if "naver_briefing" in engines else "") + ".",
|
||||
"- 재지 않는 것: 소비자 화면(chatgpt.com 등)의 실제 답변. 약관상 자동 조회를 하지 않으므로 API 답변과 화면 답변은 다를 수 있다.",
|
||||
"- 재지 않는 것: Google AI Overview·네이버 AI 브리핑·Gemini 의 어조. 다음 회차에 엔진을 추가할 때 같은 기준표로 잰다.",
|
||||
"- 한계: 판정기는 언어 모델이다. 보정(§7) 전 점수는 참고값이고, 보정 후에도 문항 수(언급 84건)가 적어 ±10점은 우연 범위로 본다."]
|
||||
# --calibration 없이 리포트만 다시 만드는 경로가 정상 사용이다(haewon 라벨을 지우지 않기 위해).
|
||||
# 그때는 새 보정 표본을 만들지 않았으므로 1차 보정 결과를 그대로 적는다.
|
||||
if args.calibration:
|
||||
lines += ["", "## 7. 보정", "", f"무작위 30건을 `{os.path.basename(args.calibration)}`에 담았다(판정기 값은 숨김 시트). haewon이 sentiment·negative_type을 적으면 `--agreement` 로 일치율을 낸다. 80% 이상이면 기준표 확정."]
|
||||
else:
|
||||
lines += ["", "## 7. 보정", "",
|
||||
f"기준표 `{RUBRIC_VERSION}` 로 판정했다. 1차 보정(2026-09-09)에서 haewon 라벨 29건 대비 v0.1 일치율은 75%로 기준선 80%에 미달했고, 불일치 7건에서 규칙 R1~R5를 뽑아 판정기에 반영한 것이 이 회차다.",
|
||||
"",
|
||||
"같은 30건으로 다시 잰 일치율은 규칙을 그 표본에 맞춰 고친 결과이므로 확정 근거가 아니다. 기준표 확정은 새로 뽑은 30건의 2차 보정으로 한다. 자세한 이력과 한계는 `docs/AI_ANSWER_SENTIMENT_RUBRIC_v0.2.md` §5에 있다."]
|
||||
os.makedirs(os.path.dirname(args.report), exist_ok=True)
|
||||
open(args.report, "w", encoding="utf-8").write("\n".join(lines) + "\n")
|
||||
print(f"리포트 → {args.report}")
|
||||
if args.calibration: write_calibration(allrows, args.calibration)
|
||||
|
||||
def write_calibration(rows, path):
|
||||
try:
|
||||
from openpyxl import Workbook
|
||||
from openpyxl.styles import Font, Alignment, PatternFill
|
||||
except ImportError:
|
||||
sys.exit("openpyxl 필요: pip install openpyxl")
|
||||
random.seed(20260908)
|
||||
m = [r for r in rows if r["mentioned"]]
|
||||
sample = random.sample(m, min(30, len(m)))
|
||||
wb = Workbook(); ws = wb.active; ws.title = "라벨링"
|
||||
# 답변 전문을 붙인다(근거 문장만 보고 판정하면 안 된다). 원본 QB 결과에서 id+engine 으로 찾는다.
|
||||
full = {}
|
||||
for eng in ("openai", "perplexity"):
|
||||
fp = os.path.join(ROOT, "scripts", "out", f"qb_{eng}_results.jsonl")
|
||||
if os.path.exists(fp):
|
||||
for l in open(fp, encoding="utf-8"):
|
||||
if l.strip(): q = json.loads(l); full[(q["id"], eng)] = q.get("answer", "")
|
||||
head = ["id", "엔진", "질문", "답변 전문", "판정기가 인용한 근거 문장", "haewon sentiment\n(positive / neutral / negative / mixed / not_mentioned)", "haewon negative_type\n(none / regulatory / safety / service / price / reputation / other)", "메모"]
|
||||
ws.append(head)
|
||||
for c in ws[1]: c.font = Font(bold=True, color="FFFFFF"); c.fill = PatternFill("solid", fgColor="0A1128"); c.alignment = Alignment(wrap_text=True, vertical="top")
|
||||
ws.row_dimensions[1].height = 48
|
||||
for r in sample: ws.append([r["id"], r["engine"], r["question"], full.get((r["id"], r["engine"]), ""), r["evidence"], "", "", ""])
|
||||
for col, w in zip("ABCDEFGH", (8, 11, 34, 90, 46, 22, 24, 24)): ws.column_dimensions[col].width = w
|
||||
from openpyxl.worksheet.datavalidation import DataValidation
|
||||
dv1 = DataValidation(type="list", formula1='"positive,neutral,negative,mixed,not_mentioned"', allow_blank=True); dv2 = DataValidation(type="list", formula1='"none,regulatory,safety,service,price,reputation,other"', allow_blank=True)
|
||||
ws.add_data_validation(dv1); ws.add_data_validation(dv2); dv1.add(f"F2:F{len(sample)+1}"); dv2.add(f"G2:G{len(sample)+1}")
|
||||
ws.freeze_panes = "D2"
|
||||
guide = wb.create_sheet("기준 요약")
|
||||
for row in [["값", "언제"], ["positive", "병원의 강점을 서술하거나 추천 근거로 든다"], ["neutral", "사실 나열, 목록 포함, 위치·진료과목 안내. '확인이 필요하다', '직접 상담하라' 같은 일반 주의 문구도 neutral"], ["negative", "부정 사실을 서술하거나 병원 고유의 문제를 지목해 주의를 권한다(공정위, 부작용 사례, 후기 논란)"], ["mixed", "긍정과 부정이 한 답변에 함께 있다"], ["", ""], ["negative_type", "부정·혼합일 때만. regulatory 공정위·시정명령·의료법 / safety 부작용·사고 / service 응대 / price 가격 불만 / reputation 후기·평판 논란 / other. 긍정·중립이면 none"], ["", ""], ["주의", "다른 병원에 대한 평가는 세지 않는다. 뷰성형외과 문장만 본다. 답변 전문(D열)을 읽고 판단한다. 근거 문장(E열)은 참고만"]]: guide.append(row)
|
||||
guide.column_dimensions["A"].width = 16; guide.column_dimensions["B"].width = 110
|
||||
for row in guide.iter_rows():
|
||||
for c in row: c.alignment = Alignment(wrap_text=True, vertical="top")
|
||||
for row in ws.iter_rows(min_row=2):
|
||||
for c in row: c.alignment = Alignment(wrap_text=True, vertical="top")
|
||||
hid = wb.create_sheet("판정기(숨김)"); hid.sheet_state = "hidden"
|
||||
hid.append(["id", "engine", "sentiment", "negative_type", "recommendation", "confidence"])
|
||||
for r in sample: hid.append([r["id"], r["engine"], r["sentiment"], r["negative_type"], r["recommendation"], r["confidence"]])
|
||||
os.makedirs(os.path.dirname(path), exist_ok=True); wb.save(path)
|
||||
print(f"보정 샘플 {len(sample)}건 → {path}")
|
||||
|
||||
def agreement(args):
|
||||
from openpyxl import load_workbook
|
||||
wb = load_workbook(args.agreement)
|
||||
human = {(r[0], r[1]): (str(r[5] or "").strip().lower(), str(r[6] or "").strip().lower()) for r in wb["라벨링"].iter_rows(min_row=2, values_only=True) if r[0]}
|
||||
model = {(r[0], r[1]): (r[2], r[3]) for r in wb["판정기(숨김)"].iter_rows(min_row=2, values_only=True) if r[0]}
|
||||
labeled = [k for k, v in human.items() if v[0]]
|
||||
if not labeled: sys.exit("haewon 라벨이 비어 있습니다")
|
||||
s_ok = sum(1 for k in labeled if human[k][0] == model[k][0]); n_ok = sum(1 for k in labeled if human[k][1] and human[k][1] == model[k][1])
|
||||
n_lab = sum(1 for k in labeled if human[k][1])
|
||||
print(f"라벨 {len(labeled)}건 · sentiment 일치 {s_ok}/{len(labeled)} = {s_ok*100//len(labeled)}% · negative_type 일치 {n_ok}/{n_lab}")
|
||||
for k in labeled:
|
||||
if human[k][0] != model[k][0]: print(f" 불일치 {k[0]} {k[1]}: haewon {human[k][0]} / 판정기 {model[k][0]}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
ap = argparse.ArgumentParser()
|
||||
ap.add_argument("--clinic", default="뷰성형외과"); ap.add_argument("--alias", default="뷰성형외과의원,뷰 성형외과,View Plastic Surgery,viewclinic")
|
||||
ap.add_argument("--weak", default="View,뷰", help="정식 상호 없이 이것만 나오면 약한 언급으로 표시한다")
|
||||
ap.add_argument("--in", dest="inp"); ap.add_argument("--out"); ap.add_argument("--model", default="gpt-4.1")
|
||||
ap.add_argument("--summary", nargs="*"); ap.add_argument("--report"); ap.add_argument("--calibration"); ap.add_argument("--agreement")
|
||||
a = ap.parse_args()
|
||||
if a.agreement: agreement(a)
|
||||
elif a.summary: summary(a)
|
||||
elif a.inp and a.out: run(a)
|
||||
else: ap.print_help()
|
||||
10
src/App.tsx
@ -1,4 +1,3 @@
|
||||
import { useEffect } from 'react';
|
||||
import { Outlet, useLocation } from 'react-router';
|
||||
import Navbar from './components/Navbar';
|
||||
import Footer from './components/Footer';
|
||||
@ -9,15 +8,6 @@ export default function App() {
|
||||
const location = useLocation();
|
||||
const isLoadingPage = location.pathname.startsWith('/report/loading');
|
||||
|
||||
// 라우트가 바뀌어도 브라우저가 스크롤 위치를 그대로 들고 온다. 진단 리포트처럼 긴 페이지를
|
||||
// 내려 보다가 "다음 단계"를 누르면, 새로 연 페이지가 그 위치(짧으면 하단 빈 공간)에서 시작해
|
||||
// 항상 위부터 보게 되지 않는다. 라우트 전환마다 맨 위로 되돌린다.
|
||||
// 해시(#breakdown 같은 인앱 앵커)가 있으면 그 앵커로 가려는 의도이므로 건드리지 않는다.
|
||||
useEffect(() => {
|
||||
if (location.hash) return;
|
||||
window.scrollTo(0, 0);
|
||||
}, [location.pathname, location.hash]);
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-slate-50 selection:bg-purple-200 selection:text-primary-900">
|
||||
{!isLoadingPage && <Navbar />}
|
||||
|
||||
@ -1,152 +0,0 @@
|
||||
/**
|
||||
* 병원 작업 공간 상단 메뉴.
|
||||
*
|
||||
* 마케팅 메뉴(Product·Pricing·Use Cases)는 진단을 받은 병원에게 쓸모가 없다.
|
||||
* 이 화면들은 진단 → 빌드 → 사진 확인 → 확인 항목이라는 하나의 절차이므로,
|
||||
* 상단을 그 절차로 바꾼다. 지금 어디에 있고 다음이 무엇인지가 한 줄에 보여야 한다.
|
||||
*
|
||||
* 단계의 완료 여부는 각 화면이 판단한다. 여기서는 위치만 표시한다.
|
||||
*/
|
||||
import { Link, useLocation } from 'react-router';
|
||||
import { motion } from 'motion/react';
|
||||
import { PrismFilled } from './icons/FilledIcons';
|
||||
|
||||
type Step = { key: string; label: string; to: (id: string) => string; match: RegExp };
|
||||
|
||||
const STEPS: Step[] = [
|
||||
{ key: 'report', label: '진단 리포트', to: (id) => `/discovery/${id}`, match: /^\/discovery\/[^/]+/ },
|
||||
{ key: 'actions', label: '개선 제안', to: (id) => `/actions/${id}`, match: /^\/actions\/[^/]+/ },
|
||||
{ key: 'build', label: '사이트 빌드', to: (id) => `/build/${id}`, match: /^\/build\/[^/]+/ },
|
||||
{ key: 'images', label: '사진 확인', to: (id) => `/images/${id}`, match: /^\/images\/[^/]+/ },
|
||||
{ key: 'inputs', label: '확인 항목', to: (id) => `/supporters/${id}`, match: /^\/supporters\/[^/]+/ },
|
||||
];
|
||||
|
||||
/** 작업 공간 경로면 병원 id 를 돌려준다. 아니면 null. */
|
||||
export function clinicIdFromPath(pathname: string): string | null {
|
||||
for (const s of STEPS) {
|
||||
if (s.match.test(pathname)) return pathname.split('/')[2] ?? null;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
export default function ClinicNav({ clinicId }: { clinicId: string }) {
|
||||
const { pathname } = useLocation();
|
||||
const currentIndex = STEPS.findIndex((s) => s.match.test(pathname));
|
||||
const prev = currentIndex > 0 ? STEPS[currentIndex - 1] : null;
|
||||
const next = currentIndex > -1 && currentIndex < STEPS.length - 1 ? STEPS[currentIndex + 1] : null;
|
||||
|
||||
return (
|
||||
<>
|
||||
<nav className="fixed top-0 left-0 right-0 z-50 bg-white/95 border-b border-slate-100 backdrop-blur-md" data-no-print>
|
||||
<div className="max-w-7xl mx-auto px-6 h-20 flex items-center gap-6">
|
||||
{/* 로고 + 병원 */}
|
||||
<Link to="/" className="flex items-center gap-3 shrink-0" aria-label="INFINITH 홈">
|
||||
<PrismFilled size={22} className="text-[#6C5CE7]" />
|
||||
<span className="hidden sm:block font-serif text-xl font-black tracking-[0.05em] bg-gradient-to-r from-[#4F1DA1] to-[#021341] bg-clip-text text-transparent">
|
||||
INFINITH
|
||||
</span>
|
||||
</Link>
|
||||
|
||||
<span className="hidden md:block w-px h-7 bg-slate-200 shrink-0" />
|
||||
|
||||
<span className="hidden md:block text-sm font-semibold text-slate-500 shrink-0 max-w-[160px] truncate" title={clinicId}>
|
||||
{clinicId}
|
||||
</span>
|
||||
|
||||
{/* 단계 */}
|
||||
<ol className="flex items-center gap-1 overflow-x-auto ml-auto [-ms-overflow-style:none] [scrollbar-width:none] [&::-webkit-scrollbar]:hidden">
|
||||
{STEPS.map((s, i) => {
|
||||
const isCurrent = i === currentIndex;
|
||||
const isPast = currentIndex > -1 && i < currentIndex;
|
||||
return (
|
||||
<li key={s.key} className="flex items-center shrink-0">
|
||||
{i > 0 && <span className="w-4 h-px bg-slate-200 mx-0.5" aria-hidden="true" />}
|
||||
<Link
|
||||
to={s.to(clinicId)}
|
||||
aria-current={isCurrent ? 'page' : undefined}
|
||||
className={`group inline-flex items-center gap-2 rounded-full pl-2 pr-3.5 py-2 text-sm font-semibold transition-colors
|
||||
${isCurrent
|
||||
? 'bg-[#F3F0FF] text-[#4A3A7C]'
|
||||
: 'text-slate-500 hover:bg-slate-100 hover:text-[#1D0024]'}`}
|
||||
>
|
||||
<span
|
||||
className={`w-6 h-6 rounded-full flex items-center justify-center text-xs font-bold shrink-0 transition-colors
|
||||
${isCurrent
|
||||
? 'bg-gradient-to-r from-[#4F1DA1] to-[#021341] text-white'
|
||||
: isPast
|
||||
? 'bg-[#D5CDF5] text-[#4A3A7C]'
|
||||
: 'bg-slate-100 text-slate-400 group-hover:bg-slate-200'}`}
|
||||
>
|
||||
{i + 1}
|
||||
</span>
|
||||
<span className="whitespace-nowrap">{s.label}</span>
|
||||
</Link>
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
</ol>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
{/* 이전 단계로. 상단 절차 메뉴는 좁은 화면에서 가로 스크롤 안으로 밀려 눈에 안 들어온다.
|
||||
되돌아가는 길은 늘 같은 자리에 있어야 한다. 첫 단계에서는 돌아갈 곳이 없어 띄우지 않는다.
|
||||
|
||||
다음 단계로 가는 길도 같은 이유로 여기에 둔다. 화면마다 따로 심으면 빠지는 곳이 생긴다.
|
||||
실제로 사이트 빌드 화면은 빌드가 끝났을 때만 다음 단계 카드를 그려서, 빌드 중이거나
|
||||
멈췄을 때는 상단 메뉴 말고는 넘어갈 길이 없었다. 마지막 단계에서는 갈 곳이 없어 띄우지 않는다. */}
|
||||
{prev && (
|
||||
<motion.div
|
||||
initial={{ opacity: 0, x: -12 }}
|
||||
animate={{ opacity: 1, x: 0 }}
|
||||
transition={{ delay: 0.3, duration: 0.3 }}
|
||||
className="fixed left-4 bottom-4 md:left-6 md:bottom-6 z-40"
|
||||
data-no-print
|
||||
>
|
||||
<Link
|
||||
to={prev.to(clinicId)}
|
||||
className="group inline-flex items-center gap-2.5 rounded-full bg-white/95 backdrop-blur-md border border-slate-200
|
||||
shadow-[0_4px_20px_rgba(29,0,36,0.14)] pl-3 pr-5 py-3 transition-shadow hover:shadow-[0_6px_26px_rgba(29,0,36,0.2)]"
|
||||
>
|
||||
<span className="w-7 h-7 rounded-full bg-slate-100 text-[#1D0024] flex items-center justify-center
|
||||
transition-colors group-hover:bg-[#F3F0FF]">
|
||||
<svg width="14" height="14" viewBox="0 0 14 14" aria-hidden="true">
|
||||
<path d="M8.9 1.4 3.3 7l5.6 5.6a1.1 1.1 0 0 0 1.6-1.6L6.5 7l4-4A1.1 1.1 0 0 0 8.9 1.4Z" fill="currentColor" />
|
||||
</svg>
|
||||
</span>
|
||||
<span className="text-left leading-tight">
|
||||
<span className="block text-[11px] font-semibold text-slate-400">이전 단계</span>
|
||||
<span className="block text-sm font-bold text-[#1D0024] whitespace-nowrap">{prev.label}</span>
|
||||
</span>
|
||||
</Link>
|
||||
</motion.div>
|
||||
)}
|
||||
|
||||
{next && (
|
||||
<motion.div
|
||||
initial={{ opacity: 0, x: 12 }}
|
||||
animate={{ opacity: 1, x: 0 }}
|
||||
transition={{ delay: 0.3, duration: 0.3 }}
|
||||
className="fixed right-4 bottom-4 md:right-6 md:bottom-6 z-40"
|
||||
data-no-print
|
||||
>
|
||||
<Link
|
||||
to={next.to(clinicId)}
|
||||
className="group inline-flex items-center gap-2.5 rounded-full bg-gradient-to-r from-[#4F1DA1] to-[#021341] text-white
|
||||
shadow-[0_4px_20px_rgba(29,0,36,0.24)] pl-5 pr-3 py-3 transition-shadow hover:shadow-[0_6px_26px_rgba(29,0,36,0.32)]"
|
||||
>
|
||||
<span className="text-right leading-tight">
|
||||
<span className="block text-[11px] font-semibold text-white/60">다음 단계</span>
|
||||
<span className="block text-sm font-bold whitespace-nowrap">{next.label}</span>
|
||||
</span>
|
||||
<span className="w-7 h-7 rounded-full bg-white/15 flex items-center justify-center
|
||||
transition-colors group-hover:bg-white/25">
|
||||
<svg width="14" height="14" viewBox="0 0 14 14" aria-hidden="true">
|
||||
<path d="M5.1 1.4 10.7 7l-5.6 5.6a1.1 1.1 0 0 1-1.6-1.6L7.5 7l-4-4A1.1 1.1 0 0 1 5.1 1.4Z" fill="currentColor" />
|
||||
</svg>
|
||||
</span>
|
||||
</Link>
|
||||
</motion.div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
@ -18,17 +18,11 @@
|
||||
* 예컨대 `/pricing`에서 `#solution`을 누르면 /pricing 페이지 내의 #solution을 찾다가 실패합니다.
|
||||
* `/#solution`은 React Router가 홈으로 이동시킨 뒤 브라우저가 해시 스크롤을 처리합니다.
|
||||
*/
|
||||
import { Link, useLocation } from 'react-router';
|
||||
import { Link } from 'react-router';
|
||||
import { ArrowRight } from 'lucide-react';
|
||||
import { buildContactMailto } from '../lib/contact';
|
||||
import ClinicNav, { clinicIdFromPath } from './ClinicNav';
|
||||
|
||||
export default function Navbar() {
|
||||
const { pathname } = useLocation();
|
||||
// 병원 작업 공간(진단·빌드·사진 확인·확인 항목)에서는 절차 메뉴로 바꾼다.
|
||||
const clinicId = clinicIdFromPath(pathname);
|
||||
if (clinicId) return <ClinicNav clinicId={clinicId} />;
|
||||
|
||||
return (
|
||||
<nav className="fixed top-0 left-0 right-0 z-50 bg-white/95 border-b border-slate-100 backdrop-blur-md">
|
||||
<div className="max-w-7xl mx-auto px-6 h-20 flex items-center justify-between">
|
||||
|
||||
@ -41,7 +41,7 @@ export default function AbmrLoop({ axes }: AbmrLoopProps) {
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: true }}
|
||||
transition={{ duration: 0.6, delay: 0.1 }}
|
||||
className="on-dark relative overflow-hidden bg-[#0A1128] rounded-2xl px-8 py-7 flex flex-col items-center text-left"
|
||||
className="relative overflow-hidden bg-[#0A1128] rounded-2xl px-8 py-7 flex flex-col items-center text-left"
|
||||
>
|
||||
{/* Background glow */}
|
||||
<div className="absolute top-[45%] left-1/2 -translate-x-1/2 -translate-y-1/2 w-[420px] h-[420px] bg-accent/20 rounded-full blur-[80px] pointer-events-none" />
|
||||
@ -65,7 +65,7 @@ export default function AbmrLoop({ axes }: AbmrLoopProps) {
|
||||
</svg>
|
||||
|
||||
{/* Center orb */}
|
||||
<div className="on-dark absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 z-[2] w-[120px] h-[120px] md:w-[148px] md:h-[148px] rounded-full bg-[#0A1128]/90 border border-white/5 shadow-[0_0_60px_rgba(167,139,250,0.12)] flex flex-col items-center justify-center gap-0.5">
|
||||
<div className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 z-[2] w-[120px] h-[120px] md:w-[148px] md:h-[148px] rounded-full bg-[#0A1128]/90 border border-white/5 shadow-[0_0_60px_rgba(167,139,250,0.12)] flex flex-col items-center justify-center gap-0.5">
|
||||
<span className="text-2xl md:text-3xl font-bold bg-gradient-to-r from-[#fff3eb] via-[#e4cfff] to-[#f5f9ff] bg-clip-text text-transparent">
|
||||
ABMR
|
||||
</span>
|
||||
@ -77,7 +77,7 @@ export default function AbmrLoop({ axes }: AbmrLoopProps) {
|
||||
{/* Nodes */}
|
||||
{NODES.map((node) => (
|
||||
<div key={node.letter} className={`absolute ${node.pos} z-[3] flex flex-col items-center gap-[7px]`}>
|
||||
<div className="on-dark w-12 h-12 md:w-[58px] md:h-[58px] rounded-full border border-purple-500/30 bg-[#0A1128]/80 shadow-[0_0_20px_rgba(168,85,247,0.15)] flex items-center justify-center">
|
||||
<div className="w-12 h-12 md:w-[58px] md:h-[58px] rounded-full border border-purple-500/30 bg-[#0A1128]/80 shadow-[0_0_20px_rgba(168,85,247,0.15)] flex items-center justify-center">
|
||||
<span className="text-xl md:text-[25px] font-bold text-purple-300">{node.letter}</span>
|
||||
</div>
|
||||
<span className="text-sm font-medium text-purple-100">{node.label}</span>
|
||||
|
||||
@ -1,67 +0,0 @@
|
||||
/**
|
||||
* 개선 제안(/actions/:id) 1부: 우선순위별 실행 항목.
|
||||
* 원래 진단 리포트 안의 "Action Plan" 섹션이었다. 진단(증거)과 처방(할 일)은 읽는 목적이 달라
|
||||
* 별도 단계로 뗐다. 내용은 그대로다.
|
||||
*/
|
||||
import { motion } from 'motion/react';
|
||||
import { SectionWrapper } from '../report/ui/SectionWrapper';
|
||||
import { Tag } from './Tag';
|
||||
import type { ActionPriority, DiscoveryResult } from '../../types/discovery';
|
||||
|
||||
const PRIORITY_STYLE: Record<ActionPriority, string> = {
|
||||
P0: 'bg-[#FFF0F0] text-[#7C3A4B] border-[#F5D5DC]',
|
||||
P1: 'bg-[#FFF6ED] text-[#7C5C3A] border-[#F5E0C5]',
|
||||
P2: 'bg-[#F3F0FF] text-[#4A3A7C] border-[#D5CDF5]',
|
||||
};
|
||||
|
||||
const PRIORITY_HEADING: Record<ActionPriority, string> = {
|
||||
P0: '즉시: 크롤러 접근·엔티티·측정',
|
||||
P1: '4주 내: 콘텐츠 구조',
|
||||
P2: '지속: 신선도·시딩',
|
||||
};
|
||||
|
||||
export function ActionPlanSection({ result }: { result: DiscoveryResult }) {
|
||||
return (
|
||||
<SectionWrapper
|
||||
id="actions"
|
||||
title="Action Plan"
|
||||
subtitle="우선순위별 실행 항목. 각 항목이 어느 기준을 올리는지 연결"
|
||||
>
|
||||
<div className="space-y-4">
|
||||
{(['P0', 'P1', 'P2'] as ActionPriority[]).map((p) => (
|
||||
<div key={p}>
|
||||
<div className="flex items-center gap-3 mb-3 mt-6 first:mt-0">
|
||||
<span className={`inline-flex items-center rounded-full text-sm font-semibold px-3 py-1 border ${PRIORITY_STYLE[p]}`}>
|
||||
{p}
|
||||
</span>
|
||||
<span className="text-lg font-bold text-[#0A1128]">{PRIORITY_HEADING[p]}</span>
|
||||
</div>
|
||||
<div className="grid md:grid-cols-2 gap-4">
|
||||
{result.actions.filter((a) => a.priority === p).map((a, i) => (
|
||||
<motion.div
|
||||
key={a.title}
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: true }}
|
||||
transition={{ delay: i * 0.08 }}
|
||||
className="on-light bg-white rounded-2xl border border-slate-100 shadow-sm p-5"
|
||||
>
|
||||
<div className="flex items-start justify-between gap-3 mb-2">
|
||||
<h4 className="font-bold text-[#0A1128]">{a.title}</h4>
|
||||
<span className="ui-note shrink-0">{a.effort}</span>
|
||||
</div>
|
||||
<p className="ui-body mb-3">{a.detail}</p>
|
||||
<div className="flex flex-wrap gap-1.5">
|
||||
{a.criterionIds.map((cid) => (
|
||||
<Tag key={cid}>{cid}</Tag>
|
||||
))}
|
||||
</div>
|
||||
</motion.div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</SectionWrapper>
|
||||
);
|
||||
}
|
||||
@ -16,14 +16,14 @@ function ItemRow({ it }: { it: ItemScoreV2; key?: string }) {
|
||||
return (
|
||||
<div className="py-4 grid md:grid-cols-[200px_1fr_120px] gap-3 items-start">
|
||||
<div>
|
||||
<span className="ui-note font-semibold">{it.item.id}</span>
|
||||
<span className="text-sm font-semibold text-slate-400">{it.item.id}</span>
|
||||
<p className="text-base font-bold text-[#0A1128]">{it.item.name}</p>
|
||||
<p className="ui-note mt-0.5">{it.item.weight}점 · {METHOD_LABEL[it.item.method]}</p>
|
||||
<p className="text-sm text-slate-400 mt-0.5">{it.item.weight}점 · {METHOD_LABEL[it.item.method]}</p>
|
||||
</div>
|
||||
<div>
|
||||
<p className="ui-body">{it.item.question}</p>
|
||||
{it.evidence && <p className="ui-note mt-1">{it.evidence}</p>}
|
||||
<p className="ui-note mt-1">
|
||||
<p className="text-base text-slate-700">{it.item.question}</p>
|
||||
{it.evidence && <p className="text-sm text-slate-500 mt-1">{it.evidence}</p>}
|
||||
<p className="text-sm text-slate-400 mt-1">
|
||||
{BASIS_LABEL[it.basis]}
|
||||
{it.signals.length > 0 && ` · ${it.signals.map((s) => `${s.criterionId}=${s.level === 'unverified' ? '미검증' : s.level}`).join(' · ')}`}
|
||||
</p>
|
||||
@ -51,7 +51,7 @@ function AxisCard({ ax }: { ax: AxisScoreV2 }) {
|
||||
</div>
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="text-right">
|
||||
<p className="ui-note">검증 {ax.verifiedCount}/{ax.items.length} · {ax.earned} / {ax.possible}점</p>
|
||||
<p className="text-sm text-slate-400">검증 {ax.verifiedCount}/{ax.items.length} · {ax.earned} / {ax.possible}점</p>
|
||||
{ax.cappedBy.length > 0 && (
|
||||
<p className="text-sm font-semibold text-[#C084CF]">게이트 상한 {Math.min(...ax.cappedBy.map((g) => g.gate.cap))} (원점수 {ax.rawScore})</p>
|
||||
)}
|
||||
@ -60,7 +60,7 @@ function AxisCard({ ax }: { ax: AxisScoreV2 }) {
|
||||
<ScoreRing score={ax.score} size={96} />
|
||||
</div>
|
||||
</div>
|
||||
<p className="ui-body mb-3">{ax.axis.description}</p>
|
||||
<p className="text-base text-slate-600 mb-3">{ax.axis.description}</p>
|
||||
<div className="divide-y divide-slate-100">
|
||||
{ax.items.map((it) => <ItemRow key={it.item.id} it={it} />)}
|
||||
</div>
|
||||
@ -81,8 +81,8 @@ export function AeoGeoV2Panel({ rubric, score }: { rubric: RubricV2; score: Over
|
||||
<ScoreRing score={ax.score} size={120} label={ax.axis.nameEn} />
|
||||
<div>
|
||||
<p className="font-serif text-2xl font-bold text-[#1D0024]">{ax.axis.code} · {ax.axis.name}</p>
|
||||
<p className="ui-body mt-1">{ax.axis.question}</p>
|
||||
<p className="ui-note mt-2">
|
||||
<p className="text-base text-slate-600 mt-1">{ax.axis.question}</p>
|
||||
<p className="text-sm text-slate-400 mt-2">
|
||||
등급 <span className="font-bold text-[#0A1128]">{ax.grade}</span> · 검증 {ax.verifiedCount}/{ax.items.length}
|
||||
{ax.cappedBy.length > 0 && ` · 게이트 상한 적용 (원점수 ${ax.rawScore})`}
|
||||
</p>
|
||||
@ -95,7 +95,7 @@ export function AeoGeoV2Panel({ rubric, score }: { rubric: RubricV2; score: Over
|
||||
<p className="text-base font-bold text-[#0A1128] mb-2">게이트 (점수가 아니라 상한)</p>
|
||||
<ul className="grid md:grid-cols-2 gap-2">
|
||||
{score.gates.map((g) => (
|
||||
<li key={g.gate.id} className="ui-body">
|
||||
<li key={g.gate.id} className="text-base text-slate-700">
|
||||
<span className={`font-bold ${g.passed ? 'text-[#6C5CE7]' : 'text-[#C084CF]'}`}>{g.gate.id} {g.gate.name} · {g.passed ? '통과' : `실패 (${g.failedBy.join(', ')}) → 상한 ${g.gate.cap}`}</span>
|
||||
<span className="text-slate-500"> {g.gate.reason}</span>
|
||||
</li>
|
||||
|
||||
@ -38,7 +38,7 @@ export default function AiAnswerMock({ clinicName, criteriaCount }: AiAnswerMock
|
||||
<circle cx="9" cy="9" r="5.5" stroke="#64748b" strokeWidth="1.5" />
|
||||
<path d="M13.5 13.5 17 17" stroke="#64748b" strokeWidth="1.5" strokeLinecap="round" />
|
||||
</svg>
|
||||
<span className="ui-body font-medium">
|
||||
<span className="text-base font-medium text-slate-700 break-keep">
|
||||
강남에서 안전하게 가슴성형 상담받을 수 있는 병원 추천해줘
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@ -1,179 +0,0 @@
|
||||
/**
|
||||
* 진단 요약 → 이렇게 보완하겠습니다 → 빌드.
|
||||
*
|
||||
* 버튼만 두면 무엇에 동의하는지 모른 채 누르게 된다. 진단을 요약하고, 무엇을 보완하는지 말한 뒤 누르게 한다.
|
||||
*
|
||||
* 이 화면에서 틀리면 안 되는 것: 빌드는 병원 홈페이지를 한 줄도 고치지 않는다.
|
||||
* 그래서 진단의 실행 항목은 단 하나도 이 빌드로 해결되지 않는다. 전부 병원 몫이다.
|
||||
* 빌드가 하는 일은 AI가 인용할 새 표면을 하나 더 만들어 약한 카테고리를 우회 보완하는 것이다.
|
||||
* "실행 항목 N건 해결"로 쓰면 지키지 못할 약속이 된다.
|
||||
*/
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useNavigate } from 'react-router';
|
||||
import { motion } from 'motion/react';
|
||||
import { supabase } from '../../lib/supabase';
|
||||
import { STATE_LABEL } from '../../lib/buildPhases';
|
||||
import type { BuildState } from '../../lib/buildPhases';
|
||||
import { weakSpots } from '../../lib/buildCoverage';
|
||||
import type { DiscoveryResult, OverallScore } from '../../types/discovery';
|
||||
import { CheckFilled, WarningFilled } from '../icons/FilledIcons';
|
||||
import { ArrowRight } from 'lucide-react';
|
||||
|
||||
type Props = {
|
||||
result: DiscoveryResult;
|
||||
overall: OverallScore;
|
||||
summary: { rubricVersion: string; grade: string; score: number; aeo?: number; geo?: number; verified: number; total: number };
|
||||
};
|
||||
|
||||
export function BuildPlanSection({ result, overall, summary }: Props) {
|
||||
const navigate = useNavigate();
|
||||
const [existing, setExisting] = useState<BuildState | null>(null);
|
||||
const [checking, setChecking] = useState(true);
|
||||
const [busy, setBusy] = useState(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
const weak = weakSpots(overall.categories);
|
||||
const critical = result.keyFindings.filter((f) => f.severity === 'critical');
|
||||
const p0 = result.actions.filter((a) => a.priority === 'P0');
|
||||
|
||||
useEffect(() => {
|
||||
let alive = true;
|
||||
void (async () => {
|
||||
const { data } = await supabase.from('supporter_builds').select('status')
|
||||
.eq('clinic_id', result.id).order('created_at', { ascending: false }).limit(1);
|
||||
if (!alive) return;
|
||||
setExisting((data?.[0]?.status as BuildState) ?? null);
|
||||
setChecking(false);
|
||||
})();
|
||||
return () => { alive = false; };
|
||||
}, [result.id]);
|
||||
|
||||
async function approve() {
|
||||
setBusy(true); setError(null);
|
||||
await supabase.from('supporter_inputs').insert({
|
||||
clinic_id: result.id, key: 'report_approval',
|
||||
value: { ...summary, weakSpots: weak.map((w) => w.id), approvedAt: new Date().toISOString() },
|
||||
});
|
||||
const { error: e } = await supabase.from('supporter_builds').insert({
|
||||
clinic_id: result.id, clinic_name: result.clinicName, url: result.url, status: 'queued',
|
||||
});
|
||||
setBusy(false);
|
||||
if (e) { setError(e.message); return; }
|
||||
navigate(`/build/${result.id}`);
|
||||
}
|
||||
|
||||
const inFlight = existing === 'queued' || existing === 'running';
|
||||
|
||||
return (
|
||||
<section className="on-dark bg-[#0A1128] text-white relative overflow-hidden py-16 md:py-20 px-6" data-no-print>
|
||||
<div className="absolute inset-0 bg-[radial-gradient(ellipse_at_bottom_left,rgba(108,92,231,0.16),transparent_60%)]" />
|
||||
<div className="relative max-w-5xl mx-auto">
|
||||
<h2 className="font-serif text-3xl md:text-5xl font-bold tracking-[-0.02em] mb-8">
|
||||
<span className="bg-gradient-to-r from-purple-300 to-blue-300 bg-clip-text text-transparent">What We Will Fix</span>
|
||||
</h2>
|
||||
|
||||
{/* 1. 진단 요약 */}
|
||||
<div className="on-light bg-white rounded-2xl p-6 md:p-7 shadow-[3px_4px_12px_rgba(0,0,0,0.2)] mb-6">
|
||||
<p className="ui-note mb-3">진단 요약 · {result.auditedAt} 실측</p>
|
||||
<div className="flex flex-wrap items-baseline gap-x-8 gap-y-3 mb-4">
|
||||
<span className="text-[#1D0024]">
|
||||
<strong className="font-serif text-4xl font-black">{summary.grade}</strong>
|
||||
<span className="ui-note ml-2">종합 {summary.score}점</span>
|
||||
</span>
|
||||
{typeof summary.aeo === 'number' && (
|
||||
<span className="text-[#1D0024]"><strong className="text-2xl font-bold">{summary.aeo}</strong><span className="ui-note ml-1.5">AEO 답변 준비도</span></span>
|
||||
)}
|
||||
{typeof summary.geo === 'number' && (
|
||||
<span className="text-[#1D0024]"><strong className="text-2xl font-bold">{summary.geo}</strong><span className="ui-note ml-1.5">GEO 출처 준비도</span></span>
|
||||
)}
|
||||
<span className="ui-note">실측 {summary.verified} / {summary.total}항목</span>
|
||||
</div>
|
||||
{critical.length > 0 && (
|
||||
<ul className="flex flex-col gap-1.5 pt-4 border-t border-slate-100">
|
||||
{critical.slice(0, 3).map((f) => (
|
||||
<li key={f.title} className="ui-body !text-[#1D0024] flex gap-2">
|
||||
<span className="mt-[9px] w-1.5 h-1.5 rounded-full bg-[#D4889A] shrink-0" />
|
||||
<span>{f.title}</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* 2. 이렇게 보완하겠습니다 */}
|
||||
<p className="ui-ask ui-ask-block mb-5">
|
||||
홈페이지를 고치지 않고, AI가 인용할 수 있는 사이트를 하나 더 만들어 아래를 보완하겠습니다.
|
||||
</p>
|
||||
<div className="grid md:grid-cols-3 gap-4 mb-8">
|
||||
{weak.map((w, i) => (
|
||||
<motion.div
|
||||
key={w.id}
|
||||
initial={{ opacity: 0, y: 18 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: true }}
|
||||
transition={{ delay: i * 0.08 }}
|
||||
className="on-light bg-white rounded-2xl p-5 shadow-[3px_4px_12px_rgba(0,0,0,0.2)]"
|
||||
>
|
||||
<div className="flex items-baseline justify-between gap-2 mb-3">
|
||||
<span className="ui-note">{w.name}</span>
|
||||
<span className="font-serif text-2xl font-black text-[#1D0024]">{w.pct}<span className="ui-note">%</span></span>
|
||||
</div>
|
||||
<p className="ui-note mb-1">지금</p>
|
||||
<h3 className="font-bold text-[#1D0024] mb-3">{w.now}</h3>
|
||||
<p className="ui-note mb-1">보완</p>
|
||||
<p className="ui-body">{w.fix}</p>
|
||||
</motion.div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* 3. 빌드가 대신할 수 없는 것 */}
|
||||
<div className="bg-white/5 border border-white/10 rounded-2xl p-5 mb-8">
|
||||
<div className="flex items-center gap-2 mb-3 text-[#F5E0C5]">
|
||||
<WarningFilled size={15} />
|
||||
<h3 className="text-base font-bold">이 빌드가 대신할 수 없는 것</h3>
|
||||
</div>
|
||||
<p className="ui-body mb-3">
|
||||
이 빌드는 별도 사이트를 만들 뿐, 병원 홈페이지를 고치지 않습니다.{' '}
|
||||
진단의 실행 항목 {result.actions.length}건은 그대로 병원 홈페이지에서 하셔야 합니다.
|
||||
</p>
|
||||
<ul className="flex flex-col gap-1.5">
|
||||
{p0.map((a) => (
|
||||
<li key={a.title} className="ui-note"><span className="font-semibold text-[#F5E0C5] mr-1.5">P0</span>{a.title}</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
{/* 4. 실행
|
||||
버튼 배경은 밝은 보라로 둔다. 기본 CTA 그라디언트(#4F1DA1→#021341)는 흰 배경용이라
|
||||
다크 섹션(#0A1128) 위에 올리면 배경에 묻혀 버튼으로 읽히지 않는다.
|
||||
문구에 "결과"를 쓰지 않는다. 빌드가 끝나도 사진 확인과 확인 항목이 남아 최종이 아니다. */}
|
||||
<div className="flex flex-wrap items-center gap-3">
|
||||
{checking ? (
|
||||
<span className="ui-note">빌드 상태를 확인하는 중입니다.</span>
|
||||
) : existing ? (
|
||||
<>
|
||||
<button type="button" onClick={() => navigate(`/build/${result.id}`)}
|
||||
className="btn-on-dark">
|
||||
{inFlight ? '만드는 과정 보기' : '만들어진 사이트 보기'} <ArrowRight className="w-5 h-5" />
|
||||
</button>
|
||||
{!inFlight && (
|
||||
<button type="button" onClick={() => void approve()} disabled={busy}
|
||||
className="rounded-full bg-white/10 border border-white/20 text-white font-semibold px-6 py-4 text-base hover:bg-white/15 transition-colors disabled:opacity-50">
|
||||
{busy ? '시작하는 중' : '다시 빌드하기'}
|
||||
</button>
|
||||
)}
|
||||
<span className="ui-note">{STATE_LABEL[existing]}</span>
|
||||
</>
|
||||
) : (
|
||||
<button type="button" onClick={() => void approve()} disabled={busy}
|
||||
className="btn-on-dark">
|
||||
<CheckFilled size={18} />{busy ? '시작하는 중' : '이대로 빌드하기'}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
{error && <p className="text-[#F5D5DC] text-base mt-4">시작하지 못했습니다. {error}</p>}
|
||||
<p className="ui-note mt-5">만들어진 사이트는 검색에 노출되지 않는 상태로 올라갑니다. 공개 여부는 병원이 확인한 뒤에 정합니다.</p>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
@ -97,12 +97,8 @@ export function ClinicInputsPanel({ clinicId }: { clinicId: string }) {
|
||||
<div className="max-w-7xl mx-auto">
|
||||
<div className="text-sm font-bold tracking-[0.24em] text-accent mb-4">SUPPORTERS BUILD · 병원 확인 항목</div>
|
||||
<h2 className="font-serif text-3xl md:text-[40px] font-bold leading-tight text-primary-900 mb-3 break-keep">서포터즈 사이트 미리보기와 병원이 확인해 주실 것</h2>
|
||||
<p className="ui-body mb-4">
|
||||
병원 홈페이지 공개 자료만으로 만든 초안입니다.{' '}
|
||||
입력 전까지는 사이트에 "확인 대기"로 표시되고, 의학 검토와 게시 승인은 병원이 확정합니다.
|
||||
</p>
|
||||
<p className="ui-ask ui-ask-block mb-10">
|
||||
아래 항목은 공개 자료에 없어 병원만 답할 수 있는 값입니다. 채워 주세요.
|
||||
<p className="text-[17px] leading-[1.7] text-slate-500 mb-10 break-keep">
|
||||
병원 홈페이지 공개 자료만으로 만든 초안입니다. 아래 항목은 자료에 없어 병원만 답할 수 있는 값이며, 입력 전까지는 사이트에 "확인 대기"로 표시됩니다. 의학 검토와 게시 승인은 병원이 확정합니다.
|
||||
</p>
|
||||
|
||||
{/* 빌드 상태 */}
|
||||
@ -112,12 +108,12 @@ export function ClinicInputsPanel({ clinicId }: { clinicId: string }) {
|
||||
{build && (
|
||||
<div className="grid md:grid-cols-[1fr_auto] gap-6 items-start">
|
||||
<div>
|
||||
<p className="ui-note mb-1">상태</p>
|
||||
<p className="text-xl font-bold text-primary-900">{STATUS_LABEL[build.status] ?? build.status}{build.phase ? <span className="ui-note font-medium ml-2">단계 {build.phase}</span> : null}</p>
|
||||
<p className="ui-note mt-1">갱신 {build.updated_at.slice(0, 16).replace('T', ' ')} · 글 {posts.length}편{typeof build.report?.totalCostUsd === 'number' ? ` · 생성 비용 $${build.report.totalCostUsd}` : ''}</p>
|
||||
<p className="text-sm text-slate-500 mb-1">상태</p>
|
||||
<p className="text-xl font-bold text-primary-900">{STATUS_LABEL[build.status] ?? build.status}{build.phase ? <span className="text-sm font-medium text-slate-500 ml-2">단계 {build.phase}</span> : null}</p>
|
||||
<p className="text-sm text-slate-400 mt-1">갱신 {build.updated_at.slice(0, 16).replace('T', ' ')} · 글 {posts.length}편{typeof build.report?.totalCostUsd === 'number' ? ` · 생성 비용 $${build.report.totalCostUsd}` : ''}</p>
|
||||
</div>
|
||||
{build.preview_url && (
|
||||
<a href={build.preview_url} target="_blank" rel="noreferrer" className="inline-flex items-center justify-center rounded-full bg-primary-900 text-white font-semibold px-6 py-3 text-base">{build.status === 'published' || build.status === 'approved' ? '공개본 사이트 열기' : '초안 사이트 열기'}</a>
|
||||
<a href={build.preview_url} target="_blank" rel="noreferrer" className="inline-flex items-center justify-center rounded-full bg-primary-900 text-white font-semibold px-6 py-3 text-base">미리보기 열기 (noindex)</a>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
@ -139,7 +135,7 @@ export function ClinicInputsPanel({ clinicId }: { clinicId: string }) {
|
||||
<h3 className="font-bold text-lg text-primary-900 break-keep">{f.title}</h3>
|
||||
<span className={`shrink-0 text-xs font-bold px-2.5 py-1 rounded-full ${cur ? 'bg-[#E9F7EF] text-[#2E7D4F]' : 'bg-[#FFF4E5] text-[#B26A00]'}`}>{cur ? `입력됨 ${String(current(f.key)?.created_at ?? '').slice(0, 10)}` : '확인 대기'}</span>
|
||||
</div>
|
||||
<p className="ui-body mb-4">{f.hint}</p>
|
||||
<p className="text-sm text-slate-500 mb-4 break-keep">{f.hint}</p>
|
||||
<div className="grid sm:grid-cols-2 gap-3">
|
||||
{f.inputs.map((inp) => {
|
||||
const value = draft[inp.name] ?? (cur ? String(Array.isArray(cur[inp.name]) ? (cur[inp.name] as string[]).join(', ') : cur[inp.name] ?? '') : '');
|
||||
@ -179,7 +175,7 @@ export function ClinicInputsPanel({ clinicId }: { clinicId: string }) {
|
||||
{build?.report?.channels && (
|
||||
<div className="bg-white border border-slate-200 rounded-2xl p-6 shadow-[0_8px_32px_rgba(31,38,135,0.05)]">
|
||||
<h3 className="font-bold text-lg text-primary-900 mb-1">발견한 채널</h3>
|
||||
<p className="ui-note mb-3">홈페이지·웹검색·레지스트리에서 찾아 병원 것인지 확인한 채널입니다. "확인 필요"는 병원이 맞는지 알려 주셔야 사이트에 연결합니다.</p>
|
||||
<p className="text-sm text-slate-500 mb-3 break-keep">홈페이지·웹검색·레지스트리에서 찾아 병원 것인지 확인한 채널입니다. "확인 필요"는 병원이 맞는지 알려 주셔야 사이트에 연결합니다.</p>
|
||||
<ul className="text-sm flex flex-col gap-2">
|
||||
{(['youtube', 'instagram', 'facebook', 'naverBlog', 'tiktok', 'kakao', 'gangnamunni', 'naverPlace', 'babitalk'] as const).map((p) => {
|
||||
const ok = build.report?.channels?.confirmed[p] ?? [];
|
||||
@ -199,15 +195,15 @@ export function ClinicInputsPanel({ clinicId }: { clinicId: string }) {
|
||||
)}
|
||||
<div className="bg-white border border-slate-200 rounded-2xl p-6 shadow-[0_8px_32px_rgba(31,38,135,0.05)]">
|
||||
<h3 className="font-bold text-lg text-primary-900 mb-1">의료진 ID</h3>
|
||||
<p className="ui-note mb-3">담당 원장·검토자 입력에 쓰는 ID 입니다. 홈페이지 의료진 페이지에서 읽었습니다.</p>
|
||||
<p className="text-sm text-slate-500 mb-3 break-keep">담당 원장·검토자 입력에 쓰는 ID 입니다. 홈페이지 의료진 페이지에서 읽었습니다.</p>
|
||||
{physicians.length ? (
|
||||
<ul className="text-sm text-slate-700 flex flex-col gap-1">{physicians.map((p) => <li key={p.id}><span className="font-mono text-accent">{p.id}</span> · {p.name} {p.title}</li>)}</ul>
|
||||
) : <p className="ui-note">빌드 후 표시됩니다.</p>}
|
||||
) : <p className="text-sm text-slate-400">빌드 후 표시됩니다.</p>}
|
||||
</div>
|
||||
<div className="bg-white border border-slate-200 rounded-2xl p-6 shadow-[0_8px_32px_rgba(31,38,135,0.05)]">
|
||||
<h3 className="font-bold text-lg text-primary-900 mb-1">2. 글별 의학 검토 · 게시 승인</h3>
|
||||
<p className="ui-note mb-4">검토가 끝난 글만 승인할 수 있고, 승인된 글만 색인 허용 빌드에 실립니다. 승인자는 의료기관 개설자(대표원장) 이름과 직함으로 적습니다.</p>
|
||||
{posts.length === 0 && <p className="ui-note">빌드 후 글 목록이 표시됩니다.</p>}
|
||||
<p className="text-sm text-slate-500 mb-4 break-keep">검토가 끝난 글만 승인할 수 있고, 승인된 글만 색인 허용 빌드에 실립니다. 승인자는 의료기관 개설자(대표원장) 이름과 직함으로 적습니다.</p>
|
||||
{posts.length === 0 && <p className="text-sm text-slate-400">빌드 후 글 목록이 표시됩니다.</p>}
|
||||
<div className="flex flex-col gap-4">
|
||||
{posts.map((p) => {
|
||||
const rv = current('post_review', p.id)?.value as { reviewer?: string; reviewedAt?: string } | undefined;
|
||||
|
||||
@ -12,7 +12,7 @@ interface DiscoveryCtaProps {
|
||||
|
||||
export default function DiscoveryCta({ criteriaCount, onStart }: DiscoveryCtaProps) {
|
||||
return (
|
||||
<section className="on-dark bg-[#0A1128] px-6 py-24">
|
||||
<section className="bg-[#0A1128] px-6 py-24">
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
@ -30,7 +30,7 @@ export default function DiscoveryCta({ criteriaCount, onStart }: DiscoveryCtaPro
|
||||
<button
|
||||
type="button"
|
||||
onClick={onStart}
|
||||
className="on-light inline-flex items-center h-[52px] px-[30px] bg-white rounded-full text-base font-bold text-primary-900 hover:bg-slate-100 transition-colors"
|
||||
className="inline-flex items-center h-[52px] px-[30px] bg-white rounded-full text-base font-bold text-primary-900 hover:bg-slate-100 transition-colors"
|
||||
>
|
||||
URL 입력으로 시작하기
|
||||
</button>
|
||||
|
||||
@ -69,7 +69,7 @@ export default function DiscoveryHero({
|
||||
transition={{ duration: 0.6, delay: 0.2 }}
|
||||
className="text-lg md:text-xl text-slate-600 leading-relaxed max-w-3xl mb-10 break-keep"
|
||||
>
|
||||
고객은 이제 AI에게 병원을 묻습니다.
|
||||
고객은 이제 ChatGPT와 Perplexity에게 병원을 묻습니다.
|
||||
<br className="hidden md:block" /> 답변에 인용될 근거를 {criteriaCount}개 항목으로 실측해
|
||||
답변 준비도와 출처 준비도로 진단하고, 8주 안에 구축합니다.
|
||||
</motion.p>
|
||||
|
||||
@ -93,7 +93,7 @@ export default function DiscoveryLeadModal({ open, onClose }: DiscoveryLeadModal
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
exit={{ opacity: 0 }}
|
||||
className="on-dark fixed inset-0 z-50 flex items-center justify-center px-6 bg-[#0A1128]/60 backdrop-blur-sm"
|
||||
className="fixed inset-0 z-50 flex items-center justify-center px-6 bg-[#0A1128]/60 backdrop-blur-sm"
|
||||
onClick={onClose}
|
||||
data-no-print
|
||||
>
|
||||
@ -102,7 +102,7 @@ export default function DiscoveryLeadModal({ open, onClose }: DiscoveryLeadModal
|
||||
animate={{ opacity: 1, y: 0, scale: 1 }}
|
||||
exit={{ opacity: 0, y: 24, scale: 0.98 }}
|
||||
transition={{ duration: 0.25 }}
|
||||
className="on-light relative w-full max-w-md bg-white rounded-2xl shadow-2xl p-8"
|
||||
className="relative w-full max-w-md bg-white rounded-2xl shadow-2xl p-8"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
@ -134,7 +134,7 @@ export default function DiscoveryLeadModal({ open, onClose }: DiscoveryLeadModal
|
||||
</svg>
|
||||
</div>
|
||||
<h3 className="text-lg font-bold text-primary-900 mb-2">신청이 접수되었습니다</h3>
|
||||
<p className="ui-body mb-6">
|
||||
<p className="text-sm leading-relaxed text-slate-600 mb-6">
|
||||
입력하신 URL을 실측 진단한 뒤, 남겨주신 연락처로 리포트를 안내드립니다.
|
||||
<br />
|
||||
문의: {DISCOVERY_CONTACT_EMAIL}
|
||||
@ -150,7 +150,7 @@ export default function DiscoveryLeadModal({ open, onClose }: DiscoveryLeadModal
|
||||
) : (
|
||||
<form onSubmit={handleSubmit}>
|
||||
<h3 className="text-xl font-bold text-primary-900 mb-1.5">URL 입력으로 시작하기</h3>
|
||||
<p className="ui-body mb-6">
|
||||
<p className="text-sm leading-relaxed text-slate-600 mb-6">
|
||||
병원 홈페이지 URL만 입력하면 AI 검색 인용 가능성을 실측 진단합니다. 진단은
|
||||
무료입니다.
|
||||
</p>
|
||||
@ -175,7 +175,7 @@ export default function DiscoveryLeadModal({ open, onClose }: DiscoveryLeadModal
|
||||
value={clinicName}
|
||||
onChange={(e) => setClinicName(e.target.value)}
|
||||
placeholder="뷰성형외과"
|
||||
className="on-light w-full h-12 px-4 mb-4 rounded-xl border border-slate-200 bg-white text-[15px] text-primary-900 placeholder:text-slate-400 focus:outline-none focus:ring-2 focus:ring-[#6C5CE7]/40"
|
||||
className="w-full h-12 px-4 mb-4 rounded-xl border border-slate-200 bg-white text-[15px] text-primary-900 placeholder:text-slate-400 focus:outline-none focus:ring-2 focus:ring-[#6C5CE7]/40"
|
||||
/>
|
||||
|
||||
<label className="block text-xs font-semibold text-slate-500 mb-1.5">
|
||||
@ -186,7 +186,7 @@ export default function DiscoveryLeadModal({ open, onClose }: DiscoveryLeadModal
|
||||
value={contact}
|
||||
onChange={(e) => setContact(e.target.value)}
|
||||
placeholder="marketing@clinic.com"
|
||||
className="on-light w-full h-12 px-4 mb-4 rounded-xl border border-slate-200 bg-white text-[15px] text-primary-900 placeholder:text-slate-400 focus:outline-none focus:ring-2 focus:ring-[#6C5CE7]/40"
|
||||
className="w-full h-12 px-4 mb-4 rounded-xl border border-slate-200 bg-white text-[15px] text-primary-900 placeholder:text-slate-400 focus:outline-none focus:ring-2 focus:ring-[#6C5CE7]/40"
|
||||
/>
|
||||
|
||||
{fieldError && (
|
||||
|
||||
@ -1,410 +0,0 @@
|
||||
/**
|
||||
* 이미지 확인 패널 — 서포터즈 사이트에 쓸 사진을 병원이 한 장씩 고른다.
|
||||
* - 읽기: supporter_builds(최신 1건).report.images · supporter_inputs(key='image_review')
|
||||
* - 쓰기: supporter_inputs insert (anon RLS). 저장하면 워커가 다음 빌드에서 제외 목록을 반영한다.
|
||||
* - 사진 파일은 미리보기 사이트(preview_url)에서 불러온다. 배포 전 빌드는 볼 수 없다.
|
||||
* - 대상은 병원 담당자다. 제작 과정이나 내부 규칙은 쓰지 않고, 무엇을 판단하면 되는지만 둔다.
|
||||
*/
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import { motion, AnimatePresence } from 'motion/react';
|
||||
import { supabase } from '../../lib/supabase';
|
||||
import { CheckFilled, CrossFilled, EyeFilled, WarningFilled, LinkExternalFilled } from '../icons/FilledIcons';
|
||||
|
||||
type Role = 'hero' | 'used' | 'stored';
|
||||
type ImageItem = {
|
||||
id: string;
|
||||
src: string;
|
||||
alt: string;
|
||||
category: 'clinic' | 'procedure' | 'equipment' | 'doctors';
|
||||
width: number;
|
||||
height: number;
|
||||
sourceUrl: string;
|
||||
pages: string[];
|
||||
role: Role;
|
||||
};
|
||||
type Build = {
|
||||
id: string;
|
||||
status: string;
|
||||
preview_url: string | null;
|
||||
updated_at: string;
|
||||
report: { images?: { capturedAt: string; total: number; used: number; items: ImageItem[] } } | null;
|
||||
};
|
||||
type Decision = 'use' | 'exclude';
|
||||
|
||||
const ROLE_LABEL: Record<Role, string> = {
|
||||
hero: '첫 화면 대표 사진',
|
||||
used: '사이트에 실립니다',
|
||||
stored: '아직 쓰이지 않습니다',
|
||||
};
|
||||
const CATEGORY_LABEL: Record<ImageItem['category'], string> = {
|
||||
clinic: '병원 공간',
|
||||
procedure: '시술 설명',
|
||||
equipment: '장비',
|
||||
doctors: '의료진',
|
||||
};
|
||||
|
||||
/** 병원이 판단할 때 떠올릴 것. 법 조항이 아니라 눈으로 확인할 수 있는 말로 둔다. */
|
||||
const CHECKPOINTS = [
|
||||
'환자분의 얼굴이나 몸이 알아볼 수 있게 나오나요',
|
||||
'수술 전과 후를 나란히 놓고 비교한 사진인가요',
|
||||
'주사·절개처럼 시술하는 장면이 담겼나요',
|
||||
'효과나 성능을 숫자·화살표로 내세운 그림인가요',
|
||||
'상장·수상·인증 마크가 들어 있나요',
|
||||
];
|
||||
|
||||
export function ImageReviewPanel({ clinicId }: { clinicId: string }) {
|
||||
const [build, setBuild] = useState<Build | null>(null);
|
||||
const [loadError, setLoadError] = useState<string | null>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [decisions, setDecisions] = useState<Record<string, Decision>>({});
|
||||
const [cursor, setCursor] = useState(0);
|
||||
const [inputBy, setInputBy] = useState('');
|
||||
const [saving, setSaving] = useState(false);
|
||||
const [saved, setSaved] = useState<string | null>(null);
|
||||
const [saveError, setSaveError] = useState<string | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
let alive = true;
|
||||
void (async () => {
|
||||
const [b, inputs] = await Promise.all([
|
||||
supabase.from('supporter_builds').select('id,status,preview_url,report,updated_at').eq('clinic_id', clinicId).order('created_at', { ascending: false }).limit(1),
|
||||
supabase.from('supporter_inputs').select('value,input_by,created_at').eq('clinic_id', clinicId).eq('key', 'image_review').order('created_at', { ascending: true }),
|
||||
]);
|
||||
if (!alive) return;
|
||||
if (b.error) setLoadError(b.error.message);
|
||||
setBuild((b.data?.[0] as Build) ?? null);
|
||||
// 같은 key 는 최신 행이 유효값이다.
|
||||
const last = inputs.data?.[inputs.data.length - 1]?.value as { decisions?: Record<string, Decision> } | undefined;
|
||||
if (last?.decisions) setDecisions(last.decisions);
|
||||
setLoading(false);
|
||||
})();
|
||||
return () => { alive = false; };
|
||||
}, [clinicId]);
|
||||
|
||||
const items = useMemo(() => {
|
||||
const list = build?.report?.images?.items ?? [];
|
||||
// 첫 화면 대표 사진과 실제로 실리는 사진을 먼저 본다.
|
||||
const rank: Record<Role, number> = { hero: 0, used: 1, stored: 2 };
|
||||
return [...list].sort((a, b) => rank[a.role] - rank[b.role]);
|
||||
}, [build]);
|
||||
|
||||
const decidedCount = items.filter((i) => decisions[i.id]).length;
|
||||
const excluded = items.filter((i) => decisions[i.id] === 'exclude');
|
||||
const done = items.length > 0 && decidedCount === items.length;
|
||||
const current = items[cursor];
|
||||
const previewUrl = build?.preview_url ?? '';
|
||||
|
||||
function decide(id: string, d: Decision) {
|
||||
setDecisions((p) => ({ ...p, [id]: d }));
|
||||
setSaved(null);
|
||||
setCursor((c) => Math.min(c + 1, items.length - 1));
|
||||
}
|
||||
|
||||
async function save() {
|
||||
setSaving(true);
|
||||
setSaveError(null);
|
||||
const { error } = await supabase.from('supporter_inputs').insert({
|
||||
clinic_id: clinicId,
|
||||
key: 'image_review',
|
||||
value: { decisions, excludeImages: excluded.map((i) => i.id), total: items.length, decidedAt: new Date().toISOString() },
|
||||
input_by: inputBy.trim() || null,
|
||||
});
|
||||
setSaving(false);
|
||||
if (error) { setSaveError(error.message); return; }
|
||||
setSaved(new Date().toISOString().slice(0, 16).replace('T', ' '));
|
||||
}
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<section className="on-light bg-white py-20 px-6">
|
||||
<div className="max-w-7xl mx-auto"><p className="text-slate-500">사진을 불러오는 중입니다.</p></div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
if (!items.length || !previewUrl) {
|
||||
return (
|
||||
<section className="on-light bg-white py-20 px-6 [word-break:keep-all]">
|
||||
<div className="on-light max-w-3xl mx-auto bg-white border border-slate-200 rounded-2xl p-8 shadow-[3px_4px_12px_rgba(0,0,0,0.06)]">
|
||||
<h2 className="font-bold text-xl text-primary-900 mb-2">아직 확인하실 사진이 없습니다</h2>
|
||||
<p className="ui-body">
|
||||
{loadError
|
||||
? `자료를 불러오지 못했습니다. ${loadError}`
|
||||
: !previewUrl
|
||||
? '사이트 초안이 준비되면 이 화면에서 사진을 한 장씩 확인하실 수 있습니다.'
|
||||
: '홈페이지에서 모은 사진이 아직 없습니다.'}
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<section className="on-light bg-white py-16 md:py-20 px-6 [word-break:keep-all]">
|
||||
<div className="max-w-7xl mx-auto">
|
||||
{/* 진행 */}
|
||||
<div className="flex flex-wrap items-end justify-between gap-4 mb-3">
|
||||
<div>
|
||||
<h2 className="font-serif text-3xl md:text-4xl font-bold text-[#0A1128]">Photo Check</h2>
|
||||
<p className="ui-ask ui-ask-block mt-3">
|
||||
홈페이지에서 모은 사진 {items.length}장입니다.{' '}
|
||||
서포터즈 사이트에 실어도 괜찮은 사진인지 한 장씩 봐 주세요.
|
||||
</p>
|
||||
{build?.report?.images?.used ? (
|
||||
<p className="ui-body mt-2">이 중 {build.report.images.used}장이 지금 사이트에 실립니다.</p>
|
||||
) : null}
|
||||
</div>
|
||||
<div className="text-right shrink-0">
|
||||
<p className="ui-note">확인</p>
|
||||
<p className="text-2xl font-bold text-[#1D0024]">{decidedCount}<span className="text-slate-400 text-lg"> / {items.length}</span></p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="h-1.5 rounded-full bg-slate-100 overflow-hidden mb-10">
|
||||
<motion.div
|
||||
className="h-full bg-gradient-to-r from-[#4F1DA1] to-[#021341]"
|
||||
animate={{ width: `${(decidedCount / items.length) * 100}%` }}
|
||||
transition={{ duration: 0.35 }}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="grid lg:grid-cols-[minmax(0,1fr)_340px] gap-8 items-start">
|
||||
{/* 한 장씩 보기 */}
|
||||
<div>
|
||||
<AnimatePresence mode="wait">
|
||||
<motion.div
|
||||
key={current.id}
|
||||
initial={{ opacity: 0, y: 14 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
exit={{ opacity: 0, y: -10 }}
|
||||
transition={{ duration: 0.22 }}
|
||||
className="on-light bg-white rounded-2xl border border-slate-100 shadow-[3px_4px_12px_rgba(0,0,0,0.06)] overflow-hidden"
|
||||
>
|
||||
<div className="bg-slate-50 flex items-center justify-center p-4">
|
||||
<img
|
||||
src={`${previewUrl}${current.src}`}
|
||||
alt={current.alt}
|
||||
className="max-h-[420px] w-auto object-contain rounded-lg"
|
||||
loading="eager"
|
||||
/>
|
||||
</div>
|
||||
<div className="p-6 md:p-7">
|
||||
<div className="flex flex-wrap items-center gap-2 mb-3">
|
||||
<RoleBadge role={current.role} />
|
||||
<span className="text-xs font-semibold px-2.5 py-1 rounded-full bg-slate-100 text-slate-600">{CATEGORY_LABEL[current.category]}</span>
|
||||
</div>
|
||||
<h3 className="ui-ask text-lg mb-1.5">이 사진을 사이트에 써도 될까요?</h3>
|
||||
<p className="ui-body mb-1">
|
||||
{current.alt || '사진에 붙은 설명이 없습니다.'}
|
||||
</p>
|
||||
<p className="ui-note">
|
||||
출처 {current.pages[0] ?? '홈페이지'}{current.pages.length > 1 ? ` 외 ${current.pages.length - 1}곳` : ''}
|
||||
{' · '}
|
||||
<a href={current.sourceUrl} target="_blank" rel="noreferrer" className="inline-flex items-center gap-1 underline underline-offset-4 text-slate-500">
|
||||
원본 열기 <LinkExternalFilled size={12} />
|
||||
</a>
|
||||
</p>
|
||||
|
||||
<div className="flex flex-wrap gap-3 mt-6">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => decide(current.id, 'use')}
|
||||
className={`inline-flex items-center gap-2 rounded-full px-6 py-3 text-base font-semibold transition-shadow ${
|
||||
decisions[current.id] === 'use'
|
||||
? 'bg-gradient-to-r from-[#4F1DA1] to-[#021341] text-white shadow-[4px_6px_16px_rgba(0,0,0,0.09)]'
|
||||
: 'bg-white border border-slate-200 text-[#021341] hover:shadow-[4px_6px_16px_rgba(0,0,0,0.09)]'
|
||||
}`}
|
||||
>
|
||||
<CheckFilled size={16} /> 사용합니다
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => decide(current.id, 'exclude')}
|
||||
className={`inline-flex items-center gap-2 rounded-full px-6 py-3 text-base font-semibold transition-shadow ${
|
||||
decisions[current.id] === 'exclude'
|
||||
? 'bg-[#FFF0F0] border border-[#F5D5DC] text-[#7C3A4B] shadow-[4px_6px_16px_rgba(0,0,0,0.09)]'
|
||||
: 'bg-white border border-slate-200 text-[#021341] hover:shadow-[4px_6px_16px_rgba(0,0,0,0.09)]'
|
||||
}`}
|
||||
>
|
||||
<CrossFilled size={16} /> 빼 주세요
|
||||
</button>
|
||||
<div className="flex items-center gap-1 ml-auto">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setCursor((c) => Math.max(c - 1, 0))}
|
||||
disabled={cursor === 0}
|
||||
className="rounded-full px-4 py-3 text-base font-semibold text-slate-600 hover:bg-slate-100 disabled:opacity-40 disabled:hover:bg-transparent"
|
||||
>
|
||||
이전
|
||||
</button>
|
||||
<span className="text-sm text-slate-400 tabular-nums px-1">{cursor + 1} / {items.length}</span>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setCursor((c) => Math.min(c + 1, items.length - 1))}
|
||||
disabled={cursor === items.length - 1}
|
||||
className="rounded-full px-4 py-3 text-base font-semibold text-slate-600 hover:bg-slate-100 disabled:opacity-40 disabled:hover:bg-transparent"
|
||||
>
|
||||
다음
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
</AnimatePresence>
|
||||
|
||||
{/* 전체 훑기 */}
|
||||
<div className="mt-6">
|
||||
<div className="flex items-baseline justify-between gap-3 mb-3">
|
||||
<h3 className="text-base font-bold text-[#1D0024]">다른 사진으로 이동</h3>
|
||||
<p className="ui-note">
|
||||
{items.length - decidedCount > 0
|
||||
? <>아직 <strong className="text-[#1D0024]">{items.length - decidedCount}장</strong>이 남았습니다</>
|
||||
: '모두 확인하셨습니다'}
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex flex-wrap gap-2.5">
|
||||
{items.map((i, idx) => {
|
||||
const d = decisions[i.id];
|
||||
const isCur = idx === cursor;
|
||||
return (
|
||||
<button
|
||||
key={i.id}
|
||||
type="button"
|
||||
onClick={() => setCursor(idx)}
|
||||
aria-label={`${idx + 1}번째 사진으로 이동${d ? (d === 'use' ? ', 사용으로 정함' : ', 빼기로 정함') : ', 아직 안 정함'}`}
|
||||
aria-current={isCur ? 'true' : undefined}
|
||||
title={i.alt || `${idx + 1}번째 사진`}
|
||||
className={`group relative w-[72px] h-[72px] rounded-xl overflow-hidden bg-slate-100 cursor-pointer
|
||||
transition-all duration-150 outline-none
|
||||
hover:-translate-y-0.5 hover:shadow-[3px_5px_14px_rgba(0,0,0,0.14)]
|
||||
focus-visible:ring-2 focus-visible:ring-[#6C5CE7] focus-visible:ring-offset-2
|
||||
${isCur
|
||||
? 'ring-2 ring-[#6C5CE7] ring-offset-2 shadow-[3px_4px_12px_rgba(108,92,231,0.28)]'
|
||||
: 'ring-1 ring-slate-200 hover:ring-[#9B8AD4]'}`}
|
||||
>
|
||||
<img
|
||||
src={`${previewUrl}${i.src}`}
|
||||
alt=""
|
||||
loading="lazy"
|
||||
className={`w-full h-full object-cover transition-transform duration-200 group-hover:scale-[1.06]
|
||||
${d === 'exclude' ? 'grayscale opacity-45' : ''}`}
|
||||
/>
|
||||
|
||||
{/* 순번. 어디까지 봤는지 세기 쉽게 한다 */}
|
||||
<span className="on-dark absolute top-1 left-1 min-w-[18px] h-[18px] px-1 rounded-md bg-[#0A1128]/65 text-white text-[11px] font-semibold leading-[18px] text-center">
|
||||
{idx + 1}
|
||||
</span>
|
||||
|
||||
{/* 정한 것 */}
|
||||
{d === 'exclude' && (
|
||||
<span className="absolute inset-0 flex items-center justify-center">
|
||||
<span className="w-6 h-6 rounded-full bg-[#FFF0F0] border border-[#F5D5DC] text-[#7C3A4B] flex items-center justify-center">
|
||||
<CrossFilled size={12} />
|
||||
</span>
|
||||
</span>
|
||||
)}
|
||||
{d === 'use' && (
|
||||
<span className="absolute bottom-1 right-1 w-5 h-5 rounded-full bg-[#F3F0FF] border border-[#D5CDF5] text-[#4A3A7C] flex items-center justify-center">
|
||||
<CheckFilled size={11} />
|
||||
</span>
|
||||
)}
|
||||
|
||||
{/* 아직 안 정한 것은 아래에 띠를 둬서 남은 일이 보이게 한다 */}
|
||||
{!d && <span className="absolute inset-x-0 bottom-0 h-1 bg-[#D4A872]" />}
|
||||
|
||||
{/* 현재 보고 있는 것 */}
|
||||
{isCur && (
|
||||
<span className="absolute inset-x-0 bottom-0 bg-[#6C5CE7] text-white text-[10px] font-bold text-center py-0.5">
|
||||
보는 중
|
||||
</span>
|
||||
)}
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
<div className="ui-note flex flex-wrap items-center gap-x-4 gap-y-1 mt-3">
|
||||
<span className="inline-flex items-center gap-1.5"><span className="w-3 h-1 rounded-full bg-[#D4A872]" /> 아직 안 정함</span>
|
||||
<span className="inline-flex items-center gap-1.5"><span className="w-3.5 h-3.5 rounded-full bg-[#F3F0FF] border border-[#D5CDF5]" /> 사용</span>
|
||||
<span className="inline-flex items-center gap-1.5"><span className="w-3.5 h-3.5 rounded-full bg-[#FFF0F0] border border-[#F5D5DC]" /> 빼기</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 판단 기준 + 제출 */}
|
||||
<div className="flex flex-col gap-5 lg:sticky lg:top-24">
|
||||
<div className="on-light bg-white rounded-2xl border border-slate-100 shadow-[3px_4px_12px_rgba(0,0,0,0.06)] p-6">
|
||||
<div className="flex items-center gap-2 mb-3 text-[#7C5C3A]">
|
||||
<WarningFilled size={16} />
|
||||
<h3 className="text-base font-bold">이런 사진이면 빼 주세요</h3>
|
||||
</div>
|
||||
<ul className="flex flex-col gap-2">
|
||||
{CHECKPOINTS.map((c) => (
|
||||
<li key={c} className="ui-body !text-[#1D0024] flex gap-2">
|
||||
<span className="mt-[9px] w-1.5 h-1.5 rounded-full bg-[#D4A872] shrink-0" />
|
||||
<span>{c}</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
<p className="ui-ask text-[15px] mt-4">판단이 서지 않으면 빼는 쪽으로 봐 주세요.{' '}<span className="ui-note">나중에 다시 넣을 수 있습니다.</span></p>
|
||||
</div>
|
||||
|
||||
<div className="on-light bg-white rounded-2xl border border-slate-100 shadow-[3px_4px_12px_rgba(0,0,0,0.06)] p-6">
|
||||
<h3 className="text-base font-bold text-[#1D0024] mb-3">확인 결과</h3>
|
||||
<dl className="flex flex-col gap-1.5 text-[15px]">
|
||||
<div className="flex justify-between"><dt className="text-slate-500">사용</dt><dd className="font-bold text-[#4A3A7C]">{decidedCount - excluded.length}장</dd></div>
|
||||
<div className="flex justify-between"><dt className="text-slate-500">빼기</dt><dd className="font-bold text-[#7C3A4B]">{excluded.length}장</dd></div>
|
||||
<div className="flex justify-between"><dt className="text-slate-500">남음</dt><dd className="font-bold text-slate-400">{items.length - decidedCount}장</dd></div>
|
||||
</dl>
|
||||
<label className="flex flex-col gap-1.5 mt-5">
|
||||
<span className="text-sm text-slate-600">확인하신 분 (이름·직함)</span>
|
||||
<input
|
||||
value={inputBy}
|
||||
onChange={(e) => setInputBy(e.target.value)}
|
||||
placeholder="예: 김담당 실장"
|
||||
className="border border-slate-200 rounded-lg px-3 py-2 text-base"
|
||||
/>
|
||||
</label>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => void save()}
|
||||
disabled={saving || decidedCount === 0}
|
||||
className="w-full mt-4 rounded-full bg-gradient-to-r from-[#4F1DA1] to-[#021341] text-white font-semibold px-6 py-3 text-base disabled:opacity-40"
|
||||
>
|
||||
{saving ? '보내는 중' : done ? '확인 결과 보내기' : `여기까지 보내기 (${decidedCount}장)`}
|
||||
</button>
|
||||
{saved && (
|
||||
<p className="text-sm text-[#4A3A7C] mt-3 flex items-start gap-1.5">
|
||||
<span className="mt-0.5"><CheckFilled size={13} /></span>
|
||||
<span>{saved} 에 보냈습니다. 빼기로 하신 사진은 다음 초안부터 빠집니다.</span>
|
||||
</p>
|
||||
)}
|
||||
{saveError && <p className="text-sm text-[#7C3A4B] mt-3">보내지 못했습니다. {saveError}</p>}
|
||||
{!done && decidedCount > 0 && !saved && (
|
||||
<p className="ui-note mt-3">지금 보내고 나중에 이어서 보셔도 됩니다.</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{previewUrl && (
|
||||
<a
|
||||
href={previewUrl}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
className="on-light inline-flex items-center justify-center gap-2 rounded-full bg-white border border-slate-200 text-[#021341] font-semibold px-6 py-3 text-base"
|
||||
>
|
||||
<EyeFilled size={15} /> 사이트 초안 열기
|
||||
</a>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
function RoleBadge({ role }: { role: Role }) {
|
||||
const style: Record<Role, string> = {
|
||||
hero: 'bg-[#EFF0FF] text-[#3A3F7C] border-[#C5CBF5]',
|
||||
used: 'bg-[#F3F0FF] text-[#4A3A7C] border-[#D5CDF5]',
|
||||
stored: 'bg-slate-100 text-slate-500 border-slate-200',
|
||||
};
|
||||
return <span className={`text-xs font-semibold px-2.5 py-1 rounded-full border ${style[role]}`}>{ROLE_LABEL[role]}</span>;
|
||||
}
|
||||
@ -1,53 +0,0 @@
|
||||
/**
|
||||
* 개선 제안(/actions/:id) 2부: 하지 않을 것 + 측정 조건.
|
||||
* "이 개선안이 무엇을 안 파는지"라 처방 쪽 내용이다. 진단 리포트에서 옮겨왔다.
|
||||
*/
|
||||
import { SectionWrapper } from '../report/ui/SectionWrapper';
|
||||
import { CheckFilled, CrossFilled } from '../icons/FilledIcons';
|
||||
import type { DiscoveryResult } from '../../types/discovery';
|
||||
|
||||
export function IntegritySection({ result }: { result: DiscoveryResult }) {
|
||||
return (
|
||||
<SectionWrapper
|
||||
id="integrity"
|
||||
title="What We Don't Sell"
|
||||
subtitle="근거 없는 것은 팔지 않는다. 이 리포트의 정직성 규약"
|
||||
dark
|
||||
>
|
||||
<div className="grid lg:grid-cols-2 gap-6">
|
||||
<div className="on-light bg-white rounded-2xl shadow-[3px_4px_12px_rgba(0,0,0,0.06)] p-6 text-[#0A1128]">
|
||||
<h3 className="text-xl font-bold mb-4 font-sans text-[#0A1128]!">하지 않을 것</h3>
|
||||
<ul className="space-y-4">
|
||||
{result.notDoing.map((n) => (
|
||||
<li key={n.item} className="flex gap-3">
|
||||
<span className="shrink-0 text-[#D4889A] mt-0.5"><CrossFilled size={18} /></span>
|
||||
<div>
|
||||
<p className="text-base font-semibold">{n.item}</p>
|
||||
<p className="ui-note mt-0.5">{n.reason}</p>
|
||||
</div>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
<div className="on-light bg-white rounded-2xl shadow-[3px_4px_12px_rgba(0,0,0,0.06)] p-6 text-[#0A1128]">
|
||||
<h3 className="text-xl font-bold mb-4 font-sans text-[#0A1128]!">측정 조건</h3>
|
||||
<ul className="space-y-3">
|
||||
{result.conditions.map((c) => (
|
||||
<li key={c} className="flex gap-3">
|
||||
<span className="shrink-0 text-[#9B8AD4] mt-0.5"><CheckFilled size={18} /></span>
|
||||
<p className="ui-body">{c}</p>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
<div className="mt-6 rounded-xl p-4 bg-[linear-gradient(to_right,#fff3eb,#e4cfff,#f5f9ff)]">
|
||||
<p className="text-sm text-slate-600 leading-relaxed">
|
||||
점수는 <strong>검증된 항목만</strong>으로 정규화했습니다. 미검증 항목(강남언니·GBP·GA4·로그)을
|
||||
확인하면 점수가 오를 수도, 내릴 수도 있습니다. 이 리포트는 답변엔진 노출을 보장하지 않으며,
|
||||
크롤러 접근·엔티티·콘텐츠 구조라는 <strong>전제조건</strong>의 충족 여부를 진단합니다.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</SectionWrapper>
|
||||
);
|
||||
}
|
||||
@ -89,7 +89,7 @@ export default function SampleReport({ overall, axes, result }: SampleReportProp
|
||||
<div className={`px-3.5 py-[5px] border rounded-full ${GRADE_BADGE_STYLE[ax.grade]}`}>
|
||||
<span className="text-sm font-bold">{ax.code} 등급 {ax.grade}</span>
|
||||
</div>
|
||||
<span className="ui-note">{ax.name}</span>
|
||||
<span className="text-sm text-slate-500">{ax.name}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
@ -161,7 +161,7 @@ export default function SampleReport({ overall, axes, result }: SampleReportProp
|
||||
<span className="text-[13px] font-bold">{finding.label}</span>
|
||||
</div>
|
||||
<div className="text-[15.5px] font-semibold text-primary-900 break-keep">{finding.title}</div>
|
||||
<p className="ui-note leading-[1.7]">{finding.detail}</p>
|
||||
<p className="text-sm leading-[1.7] text-slate-500 break-keep">{finding.detail}</p>
|
||||
</motion.div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
@ -1,13 +0,0 @@
|
||||
/**
|
||||
* 작은 라벨 배지. 진단 리포트(Scorecard의 근거/통제/채점 태그)와 개선 제안(항목 ID 태그)
|
||||
* 양쪽에서 쓰여 공용 파일로 뺐다.
|
||||
*/
|
||||
import type { ReactNode } from 'react';
|
||||
|
||||
export function Tag({ children }: { key?: string; children: ReactNode }) {
|
||||
return (
|
||||
<span className="inline-flex items-center rounded-full bg-slate-100 text-slate-600 text-sm font-medium px-2 py-0.5">
|
||||
{children}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
@ -28,7 +28,7 @@ const STATS = [
|
||||
|
||||
export default function WhyNow() {
|
||||
return (
|
||||
<section id="why-now" className="on-dark bg-[#0A1128] px-6 py-22">
|
||||
<section id="why-now" className="bg-[#0A1128] px-6 py-22">
|
||||
<div className="max-w-7xl mx-auto">
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
|
||||
@ -22,7 +22,7 @@ export function SectionWrapper({
|
||||
id={id}
|
||||
className={`
|
||||
${dark
|
||||
? 'on-dark bg-[#0A1128] text-white relative overflow-hidden'
|
||||
? 'bg-[#0A1128] text-white relative overflow-hidden'
|
||||
: 'bg-white'
|
||||
}
|
||||
py-16 md:py-20 px-6
|
||||
|
||||
@ -6,11 +6,9 @@ import type { DiscoveryResult } from '../types/discovery';
|
||||
import { DISCOVERY_VIEWCLINIC } from './discovery_viewclinic';
|
||||
import { DISCOVERY_TAEHA } from './discovery_taeha';
|
||||
import { DISCOVERY_WONJIN } from './discovery_wonjin';
|
||||
import { DISCOVERY_JKPLASTIC } from './discovery_jkplastic';
|
||||
|
||||
export const DISCOVERY_RESULTS: Record<string, DiscoveryResult> = {
|
||||
viewclinic: DISCOVERY_VIEWCLINIC,
|
||||
taeha: DISCOVERY_TAEHA,
|
||||
wonjin: DISCOVERY_WONJIN,
|
||||
jkplastic: DISCOVERY_JKPLASTIC,
|
||||
};
|
||||
|
||||
@ -1,164 +0,0 @@
|
||||
/**
|
||||
* JK성형외과 영문 사이트 (www.jkplastic.com/en/) AEO/GEO 진단
|
||||
* 자동 채점: audit_aeo_geo.py · 2026-09-20 · JS 미실행 HTML 기준
|
||||
* 사람 보정: 2026-09-20 · 영문 페이지 11개 + 국문 의료진 1개 실측
|
||||
*
|
||||
* 영문 기준으로 본다. 국문 사이트는 별도 진단 대상이며 여기 점수에 섞지 않는다.
|
||||
* 자동 채점기의 수치·NAP·alt 정규식은 국문 표현을 전제해 만들어져 영문 페이지에서
|
||||
* B4·C4·F1 세 항목을 과소 판정했다. 아래는 원본 HTML을 다시 읽어 보정한 값이다.
|
||||
*/
|
||||
import type { DiscoveryResult } from '../types/discovery';
|
||||
|
||||
export const DISCOVERY_JKPLASTIC: DiscoveryResult = {
|
||||
id: 'jkplastic',
|
||||
clinicName: 'JK성형외과',
|
||||
url: 'https://www.jkplastic.com/en/',
|
||||
industry: '성형외과 (강남 압구정) · 영문 사이트 기준',
|
||||
auditedAt: '2026-09-20',
|
||||
rubricVersion: '1.0',
|
||||
conditions: [
|
||||
'실측 대상: 영문 페이지 11개 (홈·의료진·오시는길·JK스토리·연혁·안전·쌍꺼풀·코·모티바·사각턱·수술후기) + 대조용 국문 의료진 1개, JS 미실행 HTML 기준',
|
||||
'크롤러 접근성: robots.txt 파싱 + 12개 AI 봇 UA 실제 GET 요청',
|
||||
'영문 기준 진단이다. 국문 사이트(jkplastic.com/)의 페이지·수치·표면은 채점에 넣지 않았다',
|
||||
'자동 채점기의 국문 전제 정규식이 놓친 B4·C4·F1은 영문 원본 HTML을 다시 읽어 보정했다',
|
||||
'semi/manual 항목 중 외부 실측이 가능한 C1·C4·C7·C8·D1·D3은 확정했고, 오너 계정이 필요한 A6·D2·D4·E1~E4는 미검증으로 두어 분모에서 뺐다',
|
||||
'답변엔진 언급률은 측정하지 않았다 (단발 조회는 무효, 반복 측정은 별도 기준선 단계에서 수행)',
|
||||
],
|
||||
results: [
|
||||
// A. 접근성
|
||||
{ criterionId: 'A1', level: 3, evidence: '12개 AI 봇 전부 허용. OAI-SearchBot·PerplexityBot·GPTBot UA로 GET → 전부 200', source: 'robots.txt: User-agent: * / Disallow: /admincenter/' },
|
||||
{ criterionId: 'A2', level: 3, evidence: 'Server 미표기 직접 응답, Cloudflare 헤더 없음, AI UA 챌린지 없음' },
|
||||
{ criterionId: 'A3', level: 3, evidence: '서버 렌더링(ASP). JS 미실행 HTML에서 영문 홈 본문 5,110자 추출. 시술 페이지도 3,692~7,667자로 본문이 HTML에 그대로 있다' },
|
||||
{ criterionId: 'A4', level: 1, evidence: '변형 간 최종 URL 불일치(중복 콘텐츠 위험): [\'https://jkplastic.com\', \'https://jkplastic.com/index.asp\', \'https://www.jkplastic.com\', \'https://www.jkplastic.com/index.asp\']' },
|
||||
{ criterionId: 'A5', level: 1, evidence: 'robots.txt에 sitemap 선언, 200 응답, URL 236개. 그러나 lastmod 최신값이 2024-05-28로 28개월 전이고, 영문 55개 URL 안에 의료진(/en/about-us/our-value/doctors.asp)과 오시는길(/en/about-us/medical-info/map.asp)이 빠져 있다. 두 페이지 모두 직접 요청하면 200이다. 영문 권위·위치 페이지가 발견 경로에서 누락된 상태', source: 'sitemap.xml 파싱 + 두 URL 직접 GET 200 (2026-09-20)' },
|
||||
{ criterionId: 'A6', level: 'unverified', evidence: '[manual] Bing Webmaster Tools 등록 여부는 오너 계정 접근 필요' },
|
||||
{ criterionId: 'A7', level: 3, evidence: 'TTFB 0.147s / 초기 HTML 93KB. 영문 시술 페이지 67~83KB — 모두 기준 이내' },
|
||||
// B. 엔티티
|
||||
{ criterionId: 'B1', level: 0, evidence: 'JSON-LD 블록 없음. 영문 11개 페이지 전부 구조화 데이터 0' },
|
||||
{ criterionId: 'B2', level: 0, evidence: 'MedicalClinic·LocalBusiness 등 업종 엔티티 스키마 없음' },
|
||||
{ criterionId: 'B3', level: 0, evidence: 'Physician 스키마 없음. 의료진 학력·경력은 HTML 텍스트로만 존재' },
|
||||
{ criterionId: 'B4', level: 2, evidence: '영문 12개 페이지 중 11개가 지번 표기 "584-2 Sinsa-dong, Gangnam-gu, Seoul", 홈 푸터만 도로명 표기 "835, Nonhyeon-ro, Gangnam-gu, Seoul". 같은 위치의 2개 변형이다. 대표전화는 "(+82) 2-777-7797" 단일 표기이며 Whatsapp 번호(+82-10-9738-4042)는 용도가 달라 변형으로 세지 않았다. 자동 채점은 홈·시술 페이지만 보고 "미표기 0점"으로 판정했으나 오시는길 페이지에 완전한 NAP이 텍스트로 있다', source: '영문 11개 페이지 주소·전화 정규식 추출 (2026-09-20)' },
|
||||
{ criterionId: 'B5', level: 0, evidence: '스키마 자체가 없어 sameAs 부재. 푸터에 Facebook·YouTube·Instagram 링크가 있으나 스키마에 미연결' },
|
||||
{ criterionId: 'B6', level: 0, evidence: 'rel=canonical 없음. A4의 4변형 중복과 겹쳐 정본 URL 신호가 없다' },
|
||||
{ criterionId: 'B7', level: 0, evidence: '언어 버전 7종(ENG·KOR·CHN·JPN·RUS·ARAB·IND)을 헤더에서 전환하지만 hreflang 태그는 0개. 외국인 환자 질의가 주력인 사이트인데 언어판이 서로 연결되어 있지 않다' },
|
||||
// C. 콘텐츠
|
||||
{ criterionId: 'C1', level: 1, evidence: '본문 첫 200자가 전부 슬로건이다. 홈 "Live a life True to yourself / Mindful of your beauty", JK스토리 "Virtue We take the right path", 쌍꺼풀 "Simple Beauty is in the eye of the beholder". "JK Plastic Surgery is a plastic surgery clinic in Apgujeong, Seoul, specializing in ..." 형태의 정의문이 어느 핵심 페이지에도 없다. 상호·지역·전문분야를 한 문장에 담은 텍스트는 메타 타이틀에만 있다', source: '영문 홈·JK스토리·쌍꺼풀 본문 첫 200자 (2026-09-20)' },
|
||||
{ criterionId: 'C2', level: 0, evidence: 'FAQ·Q&A 섹션이 영문 11개 페이지에 없다. FAQPage 스키마도 없다. 후기 게시판(/en/boad/bd_free/5/)은 있으나 질문·답변 구조가 아니다' },
|
||||
{ criterionId: 'C3', level: 2, evidence: 'H1 1개: "JK PLASTIC". 다만 상호 자체가 H1이라 페이지 주제를 나타내지 않고, H2는 라벨형 중복이 있다' },
|
||||
{ criterionId: 'C4', level: 2, evidence: '시술 4개 중 3개에 스펙이 텍스트로 있다. 쌍꺼풀 "Operation Time 30-40 minutes / Stitch Removal 4days after the surgery / Hospitalization Not necessary", 모티바 "Operation Time 1.5 Hours / Stitch Removal 10 days", 사각턱 "Operation Time 1~1.5 hours / Stitch Removal 2 weeks / Avoid hard and tough food for 4 weeks". 코 성형 페이지는 수치 0. 가격은 영문 어느 시술 페이지에도 없다. 자동 채점이 "수치 0회"로 판정한 것은 국문 단위(분·일·원)만 찾는 정규식 때문이다', source: '영문 시술 4개 페이지 수치 맥락 추출 (2026-09-20)' },
|
||||
{ criterionId: 'C5', level: 1, evidence: 'HTML <table> 0개. 정의 리스트 <dl>은 모티바 6개·홈 4개가 있으나 내용이 제품 특징 설명(Shell·Silicone Gel·BluSeal®)이라 비교표가 아니다. 시술별 스펙(수술시간·실밥제거)은 표가 아닌 이미지와 div 레이아웃으로 배치되어 있다' },
|
||||
{ criterionId: 'C6', level: 0, evidence: '최신 갱신 신호 2024-05-28 (sitemap lastmod) — 28개월 전. dateModified·article:modified_time 마크업 없음. 영문 사이트에 갱신 시점을 알리는 신호가 하나도 없다' },
|
||||
{ criterionId: 'C7', level: 2, evidence: '영문 의료진 페이지(7,225자)에 대표원장 학력·전공·수련이 텍스트로 있다. "Joo Kwon / Plastic Surgeon / Representative Doctor / Graduated Catholic University Medical School (majored in Plastic Surgery) / Finished Intern·Resident at Seoul St. Mary\'s Hospital / Outpatient Professor of Plastic Surgery School of Catholic University". 학회·학술 언급 87회, 전문의·인증 표현 2회, "20 years of know-how" 경력 표기. 다만 Physician 스키마가 없어 구조화되지 않았고, 이 페이지가 sitemap에서 빠져 있다', source: '/en/about-us/our-value/doctors.asp 본문 실측 (2026-09-20)' },
|
||||
{ criterionId: 'C8', level: 2, evidence: '영문 10개 페이지의 60자 이상 단락 144개 중 10개를 표본 추출해 채점: 7/10(70%)이 시술명 또는 상호를 포함한다. 다만 상호(JK)를 포함한 단락은 10개 중 1개뿐이고 나머지는 시술명으로만 맞았다. 답변엔진이 단락을 인용해도 "어느 병원 이야기인지"가 문장 안에 남지 않는 구조다', source: '무작위 표본 10개 (seed 고정) 수동 채점 (2026-09-20)' },
|
||||
// D. 표면
|
||||
{ criterionId: 'D1', level: 2, evidence: '영문 기준 버티컬은 해외 의료관광 플랫폼이다. Bookimed(리뷰 9건 표기, 요약 평점 4.5/5)·Medijump·MediGlobus·PlacidWay·Clinics on Call·seoul.clinic에 프로필이 있고 가격·의료진·후기 항목이 채워져 있다. 다만 병원이 직접 관리하는 프로필인지, 갱신이 3개월 이내인지는 확인하지 못해 상위 단계로 올리지 않았다', source: '웹 검색 실측 (2026-09-20)' },
|
||||
{ criterionId: 'D2', level: 'unverified', evidence: '[manual] 2차 출처(seoul.clinic 리뷰 기사)에 Google 리뷰 약 80건·평점 4.5 언급이 있으나, 프로필 소유권·카테고리·NAP 일치는 Google Maps에서 직접 확인해야 한다. 2차 출처 수치로 판정하지 않는다' },
|
||||
{ criterionId: 'D3', level: 2, evidence: '자체 YouTube 채널(JK Plastic Surgery Center) 운영 중이고, 의료 전문지 기사(Korea Biomedical Review "In a way, JK plastic surgeons are artists")와 해외 의료관광 도메인 6곳의 소개 문서가 있다. 유튜브와 언론이 모두 있어 2단계. 다만 2025~2026년 신규 언론 노출은 검색에서 확인되지 않아, 언급이 과거에 쏠려 있다', source: '웹 검색 실측, naver.com 제외 (2026-09-20)' },
|
||||
{ criterionId: 'D4', level: 'unverified', evidence: '[manual] 표면 간 NAP 대조표는 D2(GBP) 실측이 선행되어야 작성할 수 있다. 사이트 내부 변형 2종은 B4 참조' },
|
||||
{ criterionId: 'D5', level: 'unverified', evidence: '[semi] 네이버 플레이스는 영문 사이트에서 링크되지 않고, 검색으로도 플레이스 프로필을 확인하지 못했다. 글로벌 AI 기여도가 0인 항목이라 영문 기준 진단에서 미검증으로 둔다. 국문 트랙에서 별도 실측한다' },
|
||||
{ criterionId: 'D6', level: 0, evidence: 'Wikidata에 "JK성형외과"·"JK Plastic Surgery" 항목 없음' },
|
||||
// E. 측정
|
||||
{ criterionId: 'E1', level: 'unverified', evidence: '[manual] 서버 로그 접근 필요 (AI 크롤러 히트 + IP 검증)' },
|
||||
{ criterionId: 'E2', level: 'unverified', evidence: '[manual] GA4 접근 필요 (AI 유입 커스텀 채널그룹 여부)' },
|
||||
{ criterionId: 'E3', level: 'unverified', evidence: '[manual] GSC 접근 필요 (AI Overviews 노출)' },
|
||||
{ criterionId: 'E4', level: 'unverified', evidence: '[manual] 영문 질의 기준선 스팟체크 미실시 (질의 10개 × 5회 반복은 별도 단계)' },
|
||||
// F. 위생
|
||||
{ criterionId: 'F1', level: 1, evidence: '영문 11개 페이지 이미지 483장 중 142장(29%) alt 누락. 페이지 편차가 크다: 쌍꺼풀 36/67·홈 36/64가 절반 이상 누락인 반면 의료진 2/31·JK스토리 2/26은 양호. 자동 채점의 61%는 홈 한 페이지만 본 값이다', source: '영문 11개 페이지 img 태그 전수 파싱 (2026-09-20)' },
|
||||
{ criterionId: 'F2', level: 'unverified', evidence: 'meta generator 없음 — CMS 종류·버전 판정 근거 없음. ASP 기반으로 보이나 확정하지 않는다' },
|
||||
{ criterionId: 'F3', level: 3, evidence: '초기 HTML 93KB (300KB 미만)' },
|
||||
{ criterionId: 'F4', level: 2, evidence: 'viewport meta 존재, 반응형. CWV 미측정' },
|
||||
],
|
||||
keyFindings: [
|
||||
{
|
||||
title: 'AI는 이 사이트를 읽을 수 있습니다',
|
||||
detail: 'robots.txt가 AI 검색봇 12종을 모두 허용하고, 차단 계층도 없으며, 본문이 서버에서 렌더링되어 JS 없이도 페이지마다 3,700~7,700자가 그대로 읽힙니다. 응답 속도와 문서 크기도 기준 안입니다. 영문 답변에 잘 나오지 않는 원인은 접근 차단이 아니라 읽은 다음에 있습니다.',
|
||||
severity: 'good',
|
||||
},
|
||||
{
|
||||
title: '읽어도 어느 병원인지 확정할 근거가 없습니다',
|
||||
detail: '영문 11개 페이지 전부 구조화 데이터가 0입니다. MedicalClinic 스키마도, Physician 스키마도, canonical도, sameAs도 없습니다. 언어판을 7종 운영하면서 hreflang 태그가 0개라 영어·중국어·일본어 페이지가 서로 같은 병원이라는 연결도 없습니다. 외국인 환자 유치가 주력인 사이트에서 이 항목들이 비어 있습니다.',
|
||||
severity: 'critical',
|
||||
},
|
||||
{
|
||||
title: '문장이 병원 이름을 데리고 다니지 않습니다',
|
||||
detail: '단락 표본 10개 중 7개가 시술명을 담고 있어 내용 자체는 인용될 만합니다. 그런데 상호가 들어간 단락은 10개 중 1개뿐이고, 핵심 페이지의 첫 200자는 모두 슬로건입니다. 답변엔진이 단락을 가져가도 문장 안에 병원 이름이 남지 않아, 인용은 되고 이름은 안 나오는 상태가 됩니다.',
|
||||
severity: 'critical',
|
||||
},
|
||||
{
|
||||
title: '영문 권위 페이지가 발견 경로에서 빠져 있습니다',
|
||||
detail: '영문 의료진 페이지에는 대표원장의 출신 학교·전공·수련 병원·외래교수 이력과 학회 언급 87회가 텍스트로 있고, 오시는 길 페이지에는 주소·전화·진료시간이 완전한 형태로 있습니다. 두 페이지 모두 직접 열면 정상입니다. 그런데 sitemap의 영문 55개 URL 목록에 둘 다 없습니다. 권위와 위치를 증명할 자료가 준비되어 있는데 크롤러에게 알려지지 않고 있습니다.',
|
||||
severity: 'warning',
|
||||
},
|
||||
{
|
||||
title: '해외 플랫폼에는 있고, 사이트 갱신은 28개월째 멈춰 있습니다',
|
||||
detail: 'Bookimed·Medijump·MediGlobus·PlacidWay 등 해외 의료관광 플랫폼 6곳에 프로필이 있고 유튜브 채널과 전문지 기사도 있어, 오픈웹 표면 자체는 확보되어 있습니다. 반면 사이트의 최신 갱신 신호는 2024년 5월이 마지막이고 갱신 시점 마크업도 없습니다. 시술 페이지에 수술시간·실밥제거 같은 스펙은 있으나 가격은 한 건도 없어, 비용을 묻는 영문 질의에 답할 문장이 사이트 안에 없습니다.',
|
||||
severity: 'warning',
|
||||
},
|
||||
],
|
||||
actions: [
|
||||
{
|
||||
priority: 'P0',
|
||||
title: '영문 핵심 페이지에 MedicalClinic·Physician 스키마와 canonical을 넣습니다',
|
||||
detail: '오시는 길 페이지에 이미 텍스트로 있는 주소·전화·진료시간을 그대로 MedicalClinic 스키마(address·telephone·openingHoursSpecification·medicalSpecialty·geo)로 옮깁니다. 의료진 페이지의 학력·수련·외래교수 이력은 Physician 스키마로 구조화합니다. sameAs에 YouTube·Instagram·Facebook과 해외 플랫폼 프로필 URL을 연결하고, 전 페이지에 self-referencing canonical을 넣어 A4의 4변형 중복을 정리합니다.',
|
||||
criterionIds: ['B1', 'B2', 'B3', 'B5', 'B6', 'A4'],
|
||||
effort: '1주',
|
||||
},
|
||||
{
|
||||
priority: 'P0',
|
||||
title: '언어판 7종을 hreflang으로 연결합니다',
|
||||
detail: '헤더에서 전환만 되고 태그로는 연결되지 않은 ENG·KOR·CHN·JPN·RUS·ARAB·IND 7종에 상호 hreflang과 x-default를 넣습니다. 외국인 환자 질의는 언어별로 다른 표면에서 일어나므로, 언어판이 하나의 병원으로 묶여야 각 언어 답변에서 같은 엔티티로 인식됩니다.',
|
||||
criterionIds: ['B7'],
|
||||
effort: '1주',
|
||||
},
|
||||
{
|
||||
priority: 'P1',
|
||||
title: 'sitemap에 영문 의료진·오시는 길 페이지를 넣고 lastmod를 되살립니다',
|
||||
detail: '영문 55개 URL 목록에서 빠진 /en/about-us/our-value/doctors.asp 와 /en/about-us/medical-info/map.asp 를 추가합니다. 두 페이지는 이미 200으로 열립니다. 그리고 2024-05-28에 멈춘 lastmod를 실제 수정일로 갱신하고, 시술 페이지에 dateModified 마크업을 넣어 갱신 신호를 만듭니다.',
|
||||
criterionIds: ['A5', 'C6'],
|
||||
effort: '1일',
|
||||
},
|
||||
{
|
||||
priority: 'P1',
|
||||
title: '핵심 페이지 첫 단락을 슬로건에서 정의문으로 바꿉니다',
|
||||
detail: '홈·시술·소개 페이지의 첫 200자를 "JK Plastic Surgery is a plastic surgery clinic in Apgujeong, Gangnam, Seoul, specializing in ... , certified by the Korean Ministry of Health and Welfare as an outstanding medical institution for foreign patients." 형태로 바꿉니다. 시술 페이지 본문 단락에도 상호를 넣어, 인용된 문장 안에 병원 이름이 남게 합니다.',
|
||||
criterionIds: ['C1', 'C8'],
|
||||
effort: '2~4주',
|
||||
},
|
||||
{
|
||||
priority: 'P1',
|
||||
title: '시술 페이지에 가격대와 FAQ를 텍스트로 넣습니다',
|
||||
detail: '수술시간·실밥제거·입원 여부는 이미 텍스트로 있습니다. 여기에 USD 가격대와 체류 기간을 더하고, 시술별 FAQ 5~7문항을 FAQPage 스키마와 함께 넣습니다. 스펙을 이미지·div가 아닌 HTML 표로 옮겨 비교 구조를 만듭니다. 코 성형 페이지는 수치가 하나도 없어 먼저 채웁니다.',
|
||||
criterionIds: ['C4', 'C2', 'C5'],
|
||||
effort: '2~4주',
|
||||
},
|
||||
{
|
||||
priority: 'P1',
|
||||
title: 'Google 비즈니스 프로필과 해외 플랫폼 프로필을 실측하고 NAP을 맞춥니다',
|
||||
detail: 'Google Maps에서 소유권·카테고리·리뷰·NAP을 직접 확인합니다. 2차 출처에 리뷰 약 80건·평점 4.5 언급이 있으나 이것으로 판정하지 않습니다. 확인 후 홈 푸터의 도로명 표기(835, Nonhyeon-ro)와 나머지 11개 페이지의 지번 표기(584-2 Sinsa-dong)를 한쪽으로 통일하고, 해외 플랫폼 6곳의 주소·전화도 같은 표기로 맞춥니다.',
|
||||
criterionIds: ['D2', 'D4', 'B4', 'D1'],
|
||||
effort: '1주',
|
||||
},
|
||||
{
|
||||
priority: 'P2',
|
||||
title: 'alt 누락 142장을 채웁니다',
|
||||
detail: '영문 11개 페이지 이미지 483장 중 142장이 alt 없이 들어가 있습니다. 쌍꺼풀 페이지 36장, 홈 36장이 집중 구간입니다. 시술 전후 사진과 시술 설명 이미지부터 채웁니다.',
|
||||
criterionIds: ['F1'],
|
||||
effort: '1주',
|
||||
},
|
||||
{
|
||||
priority: 'P2',
|
||||
title: '영문 질의 기준선을 측정합니다',
|
||||
detail: '영문 질의 10개를 5회씩 반복 조회해 언급률 기준선과 신뢰구간을 만듭니다. 서버 로그에서 AI 크롤러 히트를 IP 검증과 함께 집계하고, GA4에 AI 유입 커스텀 채널그룹을 만듭니다. 개선 전후 비교는 이 기준선이 있어야 말할 수 있습니다.',
|
||||
criterionIds: ['E1', 'E2', 'E3', 'E4'],
|
||||
effort: '지속',
|
||||
},
|
||||
],
|
||||
notDoing: [
|
||||
{ item: 'llms.txt 제작', reason: 'Google 공식(2026-06) 효과 0, 채택률 0.13%, 서버 로그에 요청 자체가 없다. 위약이다.' },
|
||||
{ item: '"스키마 넣으면 AI가 인용합니다" 약속', reason: '인과 증거 없음. 스키마는 엔티티 식별을 돕는 위생 조치로만 설명한다.' },
|
||||
{ item: '단발 조회로 순위·노출 보고', reason: 'LLM 답변 분산 10~34%. 반복 측정 + 신뢰구간으로만 보고한다.' },
|
||||
{ item: '답변엔진 UI 자동 조회', reason: 'OpenAI·Perplexity 약관 위반. 주력 측정은 오너가 소유한 신호(로그·GA4)로 한다.' },
|
||||
{ item: '네이버 블로그 증량', reason: 'blog.naver.com robots.txt가 AI 크롤링을 전면 금지. 영문 질의 기여도 0이다.' },
|
||||
{ item: '국문 사이트 항목을 영문 점수에 합산', reason: '국문에만 있는 의료진 페이지·전화번호 6종은 영문 표면의 근거가 되지 않는다. 국문은 별도 진단으로 낸다.' },
|
||||
],
|
||||
};
|
||||
113
src/index.css
@ -20,22 +20,6 @@
|
||||
|
||||
--color-accent: #6C5CE7; /* Purple accent */
|
||||
|
||||
/* 흰·밝은 배경 위 강조 텍스트. haewon이 말하는 "인디고" */
|
||||
--color-indigo: #1D0024;
|
||||
|
||||
/* 텍스트 역할 토큰.
|
||||
ask = 요청문. 사람에게 무엇을 해달라고 하는 문장 (읽고 끝나는 설명과 구분한다)
|
||||
body = 설명 본문
|
||||
note = 보조 설명·출처 */
|
||||
--color-text-ask: #1D0024;
|
||||
--color-text-body: #475569;
|
||||
--color-text-note: #94A3B8;
|
||||
--color-ask-rule: #D5CDF5;
|
||||
|
||||
--text-ask: 1.0625rem; /* 17px. 본문(16px)보다 한 단 크게 */
|
||||
--text-body: 1rem; /* 16px. 기존 text-base 와 같다 */
|
||||
--text-note: 0.875rem; /* 14px. 기존 text-sm 과 같다 */
|
||||
|
||||
/* INFINITH Semantic Status Colors — pastel palette derived from brand gradient */
|
||||
/* Critical/Negative: warm rose (peach-toned) */
|
||||
--color-status-critical-bg: #FFF0F0;
|
||||
@ -174,99 +158,4 @@
|
||||
|
||||
.animation-delay-14000 {
|
||||
animation-delay: 14s;
|
||||
}
|
||||
|
||||
/* ──────────────────────────────────────────────────────────────
|
||||
텍스트 역할 클래스
|
||||
|
||||
요청문(.ui-ask)과 설명문(.ui-body)을 눈에 띄게 가른다. 요청문은 사람이
|
||||
무엇을 해야 하는지 말하는 문장이라, 읽고 넘어가는 설명과 같은 무게로 두면
|
||||
지나친다. 색·굵기·크기를 함께 올리고 왼쪽에 규칙선을 둔다.
|
||||
|
||||
한글 줄바꿈:
|
||||
word-break: keep-all → 단어(어절) 중간에서 자르지 않는다
|
||||
overflow-wrap: anywhere → 긴 URL·영문 토큰은 예외적으로 자른다
|
||||
text-wrap: pretty → 마지막 줄에 한 어절만 남는 것을 줄인다
|
||||
max-width 62ch → 한 줄이 길어 눈이 되돌아오는 것을 막는다
|
||||
────────────────────────────────────────────────────────────── */
|
||||
.ui-ask,
|
||||
.ui-body,
|
||||
.ui-note {
|
||||
word-break: keep-all;
|
||||
overflow-wrap: anywhere;
|
||||
text-wrap: pretty;
|
||||
}
|
||||
|
||||
.ui-ask {
|
||||
font-size: var(--text-ask);
|
||||
line-height: 1.75;
|
||||
font-weight: 600;
|
||||
color: var(--color-text-ask);
|
||||
max-width: 62ch;
|
||||
}
|
||||
|
||||
/* 문단으로 서는 요청문. 왼쪽 규칙선으로 설명 본문과 구분한다 */
|
||||
.ui-ask-block {
|
||||
border-left: 3px solid var(--color-ask-rule);
|
||||
padding-left: 0.875rem;
|
||||
}
|
||||
|
||||
.ui-body {
|
||||
font-size: var(--text-body);
|
||||
line-height: 1.75;
|
||||
color: var(--color-text-body);
|
||||
max-width: 62ch;
|
||||
}
|
||||
|
||||
.ui-note {
|
||||
font-size: var(--text-note);
|
||||
line-height: 1.7;
|
||||
color: var(--color-text-note);
|
||||
}
|
||||
|
||||
/* 다크 섹션 위에서는 같은 역할을 밝은 값으로 뒤집는다 */
|
||||
.on-dark .ui-ask { color: #FFFFFF; }
|
||||
.on-dark .ui-ask-block { border-left-color: rgba(213, 205, 245, 0.45); }
|
||||
.on-dark .ui-body { color: rgba(255, 255, 255, 0.72); }
|
||||
.on-dark .ui-note { color: rgba(255, 255, 255, 0.45); }
|
||||
|
||||
/* 다크 섹션 안의 흰 카드는 다시 라이트 값으로 되돌린다.
|
||||
이걸 두지 않으면 흰 글씨가 흰 배경에 얹혀 보이지 않는다. 흰 카드에 .on-light 를 붙인다. */
|
||||
.on-dark .on-light .ui-ask,
|
||||
.on-light .ui-ask { color: var(--color-text-ask); }
|
||||
.on-dark .on-light .ui-ask-block,
|
||||
.on-light .ui-ask-block { border-left-color: var(--color-ask-rule); }
|
||||
.on-dark .on-light .ui-body,
|
||||
.on-light .ui-body { color: var(--color-text-body); }
|
||||
.on-dark .on-light .ui-note,
|
||||
.on-light .ui-note { color: var(--color-text-note); }
|
||||
|
||||
/* 제목도 어절 단위로 끊는다. 한글 제목이 글자 중간에서 잘리면 읽기 나쁘다 */
|
||||
h1, h2, h3, h4, h5, h6 {
|
||||
word-break: keep-all;
|
||||
text-wrap: balance;
|
||||
}
|
||||
|
||||
/* 다크 섹션(#0A1128) 위의 주 버튼.
|
||||
기본 CTA 그라디언트(#4F1DA1 → #021341)는 흰 배경용이라 다크 위에 올리면 배경에 묻힌다.
|
||||
밝은 보라 + 글로우로 떼어 놓는다. */
|
||||
.btn-on-dark {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.625rem;
|
||||
border-radius: 9999px;
|
||||
padding: 1rem 2rem;
|
||||
font-size: 1.0625rem;
|
||||
font-weight: 700;
|
||||
color: #FFFFFF;
|
||||
background-image: linear-gradient(to right, #7C6DD8, #6C5CE7);
|
||||
box-shadow: 0 6px 28px rgba(108, 92, 231, 0.55);
|
||||
outline: 1px solid rgba(255, 255, 255, 0.25);
|
||||
outline-offset: -1px;
|
||||
transition: transform 0.15s ease, box-shadow 0.15s ease;
|
||||
}
|
||||
.btn-on-dark:hover:not(:disabled) {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 10px 34px rgba(108, 92, 231, 0.7);
|
||||
}
|
||||
.btn-on-dark:disabled { opacity: 0.6; }
|
||||
}
|
||||
@ -1,47 +0,0 @@
|
||||
/**
|
||||
* 서포터즈 사이트 빌드가 진단의 어느 약점을 보완하는지 고른다.
|
||||
*
|
||||
* 중요한 전제. 이 빌드는 병원 홈페이지를 한 줄도 고치지 않는다. 별도 사이트를 하나 만들 뿐이다.
|
||||
* 그래서 진단의 실행 항목(깨진 JSON-LD 수정·GBP 전화번호 등록·H1 재설계 등)은 단 하나도
|
||||
* 이 빌드로 해결되지 않는다. 전부 병원 몫이다.
|
||||
*
|
||||
* 빌드가 하는 일은 다르다. AI가 인용할 수 있는 새 표면을 하나 더 만들어, 홈페이지를 고치지 않고도
|
||||
* 답변·출처로 쓰일 텍스트를 확보한다. 그래서 "실행 항목 중 몇 건 해결"이 아니라
|
||||
* "어느 약점을 우회해서 보완하는가"로 말해야 한다.
|
||||
*/
|
||||
import type { CategoryId, CategoryScore } from '../types/discovery';
|
||||
|
||||
/** 빌드가 보완할 수 있는 카테고리. 크롤러 접근(A)·측정(E)·기술 위생(F)은 홈페이지 쪽이라 손댈 수 없다. */
|
||||
const ADDRESSABLE: CategoryId[] = ['content', 'entity', 'surface'];
|
||||
|
||||
/** 카테고리별로, 빌드가 무엇을 만들어 어떻게 보완하는지 */
|
||||
export const COVERAGE_PLAN: Record<string, { now: string; fix: string }> = {
|
||||
content: {
|
||||
now: '패시지 단위로 인용할 문장이 없습니다',
|
||||
fix: '병원을 알아보는 사람이 실제로 묻는 질문을 골라, 한 단락만 떼어내도 뜻이 통하는 글로 답합니다.',
|
||||
},
|
||||
entity: {
|
||||
now: '누가 말하는지 기계가 확정하지 못합니다',
|
||||
fix: '의료진 이력과 병원 사실을 한자리에 모으고, 글마다 작성자와 감수 원장을 구조화해 밝힙니다.',
|
||||
},
|
||||
surface: {
|
||||
now: 'AI가 인용할 표면이 좁습니다',
|
||||
fix: '원장 설명 영상과 언론 보도를 글의 근거로 연결하고, 회복 일정과 방문 안내를 별도 페이지로 만듭니다.',
|
||||
},
|
||||
};
|
||||
|
||||
export type WeakSpot = { id: CategoryId; name: string; pct: number; now: string; fix: string };
|
||||
|
||||
/** 빌드가 보완할 수 있는 카테고리 중 점수가 낮은 순 */
|
||||
export function weakSpots(categories: CategoryScore[]): WeakSpot[] {
|
||||
return categories
|
||||
.filter((c) => ADDRESSABLE.includes(c.category.id) && COVERAGE_PLAN[c.category.id])
|
||||
.sort((a, b) => a.pct - b.pct)
|
||||
.map((c) => ({
|
||||
id: c.category.id,
|
||||
name: c.category.name,
|
||||
pct: c.pct,
|
||||
now: COVERAGE_PLAN[c.category.id].now,
|
||||
fix: COVERAGE_PLAN[c.category.id].fix,
|
||||
}));
|
||||
}
|
||||
@ -1,120 +0,0 @@
|
||||
/**
|
||||
* 서포터즈 빌드 17단계를 고객이 읽을 수 있는 5묶음으로 옮긴다.
|
||||
*
|
||||
* 워커(workers/supporters-build/run.mjs)의 PHASES 와 순서가 같아야 한다.
|
||||
* 워커가 단계를 늘리면 여기 STEPS 의 phases 에도 넣는다. 매핑에 없는 단계는
|
||||
* 진행률 계산에서 빠지므로, 모르는 값이 와도 화면이 멈추지는 않는다.
|
||||
*/
|
||||
|
||||
/** 워커가 supporter_builds.phase 에 쓰는 값 */
|
||||
export type WorkerPhase =
|
||||
| 'evidence' | 'ocr' | 'data' | 'discover' | 'youtube' | 'news' | 'images'
|
||||
| 'tourism' | 'recovery' | 'planner' | 'inputs' | 'briefs' | 'generate'
|
||||
| 'build' | 'deploy' | 'publish' | 'done';
|
||||
|
||||
export type PhaseRow = { phase: string; status: 'running' | 'ok' | 'warning' | 'failed'; summary?: string };
|
||||
|
||||
export type BuildStep = {
|
||||
key: string;
|
||||
/** 진행 중일 때 문구 */
|
||||
label: string;
|
||||
/** 끝났을 때 문구 */
|
||||
labelDone: string;
|
||||
/** 무엇을 하는지 한 줄 */
|
||||
detail: string;
|
||||
phases: WorkerPhase[];
|
||||
};
|
||||
|
||||
export const BUILD_STEPS: BuildStep[] = [
|
||||
{
|
||||
key: 'collect',
|
||||
label: '홈페이지를 읽는 중입니다',
|
||||
labelDone: '홈페이지 자료 확보',
|
||||
detail: '공개된 페이지와 이미지 속 글자까지 읽어 병원 정보와 의료진을 정리합니다.',
|
||||
phases: ['evidence', 'ocr', 'data'],
|
||||
},
|
||||
{
|
||||
key: 'sources',
|
||||
label: '채널과 근거를 모으는 중입니다',
|
||||
labelDone: '채널과 근거 확보',
|
||||
detail: '유튜브·인스타그램·블로그 채널을 찾고, 기사와 사진을 근거로 모읍니다.',
|
||||
phases: ['discover', 'youtube', 'news', 'images'],
|
||||
},
|
||||
{
|
||||
key: 'guides',
|
||||
label: '방문·회복 안내를 만드는 중입니다',
|
||||
labelDone: '방문·회복 안내 완성',
|
||||
detail: '오시는 길, 회복 일정, 머무는 동안의 주변 안내를 병원 위치 기준으로 만듭니다.',
|
||||
phases: ['tourism', 'recovery', 'planner'],
|
||||
},
|
||||
{
|
||||
key: 'write',
|
||||
label: '질문에 답하는 글을 쓰는 중입니다',
|
||||
labelDone: '글 작성 완료',
|
||||
detail: '병원을 알아보는 사람이 실제로 묻는 질문을 골라, 공개 자료를 근거로 답합니다.',
|
||||
phases: ['inputs', 'briefs', 'generate'],
|
||||
},
|
||||
{
|
||||
key: 'publish',
|
||||
label: '사이트를 만들고 올리는 중입니다',
|
||||
labelDone: '사이트 준비 완료',
|
||||
detail: '발행 기준을 검사한 뒤 사이트를 만들어 올립니다. 검색 노출은 확인을 마친 뒤에 켭니다.',
|
||||
phases: ['build', 'deploy', 'publish', 'done'],
|
||||
},
|
||||
];
|
||||
|
||||
const PHASE_TO_STEP = new Map<string, number>();
|
||||
BUILD_STEPS.forEach((s, i) => s.phases.forEach((p) => PHASE_TO_STEP.set(p, i)));
|
||||
|
||||
/** 전체 워커 단계 수. 진행률을 단계 묶음이 아니라 실제 단계 기준으로 센다. */
|
||||
const TOTAL_PHASES = BUILD_STEPS.reduce((n, s) => n + s.phases.length, 0);
|
||||
|
||||
export type BuildState = 'queued' | 'running' | 'gate_failed' | 'failed' | 'preview' | 'published' | 'published_pending_tasks' | 'approved';
|
||||
|
||||
/** 지금 어느 묶음에 있는지. 모르는 단계면 -1. */
|
||||
export function stepIndexOf(phase: string | null | undefined): number {
|
||||
if (!phase) return -1;
|
||||
return PHASE_TO_STEP.get(phase) ?? -1;
|
||||
}
|
||||
|
||||
/** 끝난 워커 단계 수 기준 진행률 0~1 */
|
||||
export function progressOf(phases: PhaseRow[] | null | undefined, state: BuildState): number {
|
||||
if (state === 'preview' || state === 'published' || state === 'published_pending_tasks' || state === 'approved') return 1;
|
||||
if (!phases?.length) return 0;
|
||||
const done = phases.filter((p) => (p.status === 'ok' || p.status === 'warning') && PHASE_TO_STEP.has(p.phase)).length;
|
||||
return Math.min(done / TOTAL_PHASES, 0.98);
|
||||
}
|
||||
|
||||
/** 묶음별 상태. 화면에서 체크·스피너·대기를 고르는 데 쓴다. */
|
||||
export type StepState = 'done' | 'active' | 'waiting' | 'failed';
|
||||
|
||||
export function stepStates(phases: PhaseRow[] | null | undefined, phase: string | null, state: BuildState): StepState[] {
|
||||
const finished = state === 'preview' || state === 'published' || state === 'published_pending_tasks' || state === 'approved';
|
||||
const cur = stepIndexOf(phase);
|
||||
const rows = new Map((phases ?? []).map((p) => [p.phase, p.status]));
|
||||
return BUILD_STEPS.map((s, i) => {
|
||||
if (finished) return 'done';
|
||||
if ((state === 'failed' || state === 'gate_failed') && i === cur) return 'failed';
|
||||
const allDone = s.phases.every((p) => { const st = rows.get(p); return st === 'ok' || st === 'warning'; });
|
||||
if (allDone) return 'done';
|
||||
if (i === cur) return 'active';
|
||||
if (cur > i) return 'done';
|
||||
return 'waiting';
|
||||
});
|
||||
}
|
||||
|
||||
/** 경고가 남은 단계. 완료 화면에서 "확인 대기"로 보여 준다. */
|
||||
export function warningsOf(phases: PhaseRow[] | null | undefined): PhaseRow[] {
|
||||
return (phases ?? []).filter((p) => p.status === 'warning');
|
||||
}
|
||||
|
||||
export const STATE_LABEL: Record<BuildState, string> = {
|
||||
queued: '차례를 기다리는 중입니다',
|
||||
running: '만드는 중입니다',
|
||||
gate_failed: '발행 기준에서 멈췄습니다',
|
||||
failed: '만들다가 멈췄습니다',
|
||||
preview: '준비됐습니다',
|
||||
approved: '승인 완료',
|
||||
published: '검색 노출까지 켜졌습니다',
|
||||
published_pending_tasks: '발행됐고 남은 확인이 있습니다',
|
||||
};
|
||||
14
src/main.tsx
@ -17,24 +17,15 @@ import ApiDashboardPage from './pages/ApiDashboardPage.tsx';
|
||||
import LoginPage from './pages/LoginPage.tsx';
|
||||
import DiscoveryLandingPage from './pages/DiscoveryLandingPage.tsx';
|
||||
import DiscoveryReportPage from './pages/DiscoveryReportPage.tsx';
|
||||
import ActionPlanPage from './pages/ActionPlanPage.tsx';
|
||||
import SupportersBuildPage from './pages/SupportersBuildPage.tsx';
|
||||
import ImageReviewPage from './pages/ImageReviewPage.tsx';
|
||||
import SiteBuildPage from './pages/SiteBuildPage.tsx';
|
||||
import './index.css';
|
||||
|
||||
// 배포별 루트 페이지 분기. Vercel 프로젝트의 환경변수로 정한다.
|
||||
const DISCOVERY_SITE = import.meta.env.VITE_SITE === 'discovery';
|
||||
|
||||
createRoot(document.getElementById('root')!).render(
|
||||
<StrictMode>
|
||||
<BrowserRouter>
|
||||
<Routes>
|
||||
<Route element={<App />}>
|
||||
{/* 루트에서 무엇을 그릴지는 배포마다 다르다. VITE_SITE=discovery 로 빌드한 배포(webforai.kr)는
|
||||
AI Discovery 랜딩을 루트에 놓고, 값이 없으면 지금까지처럼 INFINITH 랜딩을 놓는다.
|
||||
한 코드베이스로 두 사이트를 내되 기존 배포의 동작은 바꾸지 않는다. */}
|
||||
<Route index element={DISCOVERY_SITE ? <DiscoveryLandingPage /> : <LandingPage />} />
|
||||
<Route index element={<LandingPage />} />
|
||||
<Route path="pricing" element={<PricingPage />} />
|
||||
<Route path="login" element={<LoginPage />} />
|
||||
<Route path="report/loading" element={<AnalysisLoadingPage />} />
|
||||
@ -50,10 +41,7 @@ createRoot(document.getElementById('root')!).render(
|
||||
<Route path="api-dashboard" element={<ApiDashboardPage />} />
|
||||
<Route path="discovery" element={<DiscoveryLandingPage />} />
|
||||
<Route path="discovery/:id" element={<DiscoveryReportPage />} />
|
||||
<Route path="actions/:id" element={<ActionPlanPage />} />
|
||||
<Route path="supporters/:id" element={<SupportersBuildPage />} />
|
||||
<Route path="images/:id" element={<ImageReviewPage />} />
|
||||
<Route path="build/:id" element={<SiteBuildPage />} />
|
||||
</Route>
|
||||
</Routes>
|
||||
</BrowserRouter>
|
||||
|
||||
@ -1,80 +0,0 @@
|
||||
/**
|
||||
* /actions/:id — 개선 제안 (Action Plan). 진단 리포트(/discovery/:id)에서 분리된 처방 단계.
|
||||
*
|
||||
* 진단은 "지금 무엇이 문제인가"(증거), 여기는 "그래서 무엇을 하는가"(처방)다.
|
||||
* 읽는 목적이 다르고, 둘을 한 페이지에 두면 스크롤이 길어져 둘 다 읽기 힘들어진다.
|
||||
* 진단 데이터가 없으면 처방도 쓸 수 없으므로 진단 리포트로 돌려보낸다.
|
||||
*/
|
||||
import { useMemo } from 'react';
|
||||
import { useParams, Navigate } from 'react-router';
|
||||
import { AEO_GEO_RUBRIC } from '../data/aeoGeoRubric';
|
||||
import { AEO_GEO_RUBRIC_V2 } from '../data/aeoGeoRubricV2';
|
||||
import { DISCOVERY_RESULTS } from '../data/discoveryResults';
|
||||
import { scoreDiscovery } from '../lib/discoveryScore';
|
||||
import { scoreDiscoveryV2 } from '../lib/discoveryScoreV2';
|
||||
import { ActionPlanSection } from '../components/discovery/ActionPlanSection';
|
||||
import { IntegritySection } from '../components/discovery/IntegritySection';
|
||||
import { BuildPlanSection } from '../components/discovery/BuildPlanSection';
|
||||
import { PrismFilled } from '../components/icons/FilledIcons';
|
||||
|
||||
export default function ActionPlanPage() {
|
||||
const { id } = useParams<{ id: string }>();
|
||||
const result = id ? DISCOVERY_RESULTS[id] : undefined;
|
||||
const overall = useMemo(
|
||||
() => (result ? scoreDiscovery(AEO_GEO_RUBRIC, result) : null),
|
||||
[result],
|
||||
);
|
||||
const overallV2 = useMemo(
|
||||
() => (result ? scoreDiscoveryV2(AEO_GEO_RUBRIC_V2, result) : null),
|
||||
[result],
|
||||
);
|
||||
|
||||
if (!id) return null;
|
||||
if (!result || !overall) {
|
||||
// 개선 제안은 진단 항목 위에 세운다. 진단이 없으면 여기서 보여줄 근거가 없으니
|
||||
// 진단 리포트로 보낸다. 그 페이지가 "아직 진단 전" 안내와 다음 링크를 갖고 있다.
|
||||
return <Navigate to={`/discovery/${id}`} replace />;
|
||||
}
|
||||
|
||||
return (
|
||||
<div data-report-content className="[word-break:keep-all]">
|
||||
<section className="on-dark bg-[#0A1128] text-white relative overflow-hidden pt-28 pb-16 md:pt-36 md:pb-20 px-6">
|
||||
<div className="absolute inset-0 bg-[radial-gradient(ellipse_at_top_right,rgba(108,92,231,0.18),transparent_60%)]" />
|
||||
<div className="relative max-w-7xl mx-auto">
|
||||
<div className="inline-flex items-center gap-2 rounded-full bg-white/10 border border-white/10 px-4 py-1.5 mb-6">
|
||||
<PrismFilled size={16} className="text-purple-300" />
|
||||
<span className="text-sm font-semibold tracking-wide text-purple-200">
|
||||
INFINITH AI Discovery · Action Plan
|
||||
</span>
|
||||
</div>
|
||||
<h1 className="font-serif text-4xl md:text-6xl font-bold tracking-[-0.02em] mb-4">
|
||||
<span className="bg-gradient-to-r from-purple-300 to-blue-300 bg-clip-text text-transparent">
|
||||
개선 제안
|
||||
</span>
|
||||
</h1>
|
||||
<p className="text-3xl font-bold mb-2">{result.clinicName}</p>
|
||||
<p className="ui-body text-white/70 max-w-2xl">
|
||||
진단(v{result.rubricVersion} · {overall.score}/{overall.grade})에서 확인된 항목을 근거로,
|
||||
무엇을 언제까지 할지와 저희가 하지 않을 것을 정리했습니다.
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<ActionPlanSection result={result} />
|
||||
<IntegritySection result={result} />
|
||||
<BuildPlanSection
|
||||
result={result}
|
||||
overall={overall}
|
||||
summary={{
|
||||
rubricVersion: result.rubricVersion,
|
||||
grade: overall.grade,
|
||||
score: overall.score,
|
||||
aeo: overallV2?.aeo.score,
|
||||
geo: overallV2?.geo.score,
|
||||
verified: overall.categories.reduce((n, c) => n + c.verifiedCount, 0),
|
||||
total: AEO_GEO_RUBRIC.criteria.length,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -192,7 +192,7 @@ export default function ClinicProfilePage() {
|
||||
<div className="pt-20 min-h-screen bg-slate-50">
|
||||
|
||||
{/* ── Hero / Clinic Header ── */}
|
||||
<div className="on-dark bg-[#0A1128] pt-10 pb-16 px-6 relative overflow-hidden">
|
||||
<div className="bg-[#0A1128] pt-10 pb-16 px-6 relative overflow-hidden">
|
||||
<div className="absolute top-0 right-0 w-[500px] h-[500px] rounded-full bg-[#7B2D8E]/10 blur-[120px]" />
|
||||
<div className="absolute bottom-0 left-0 w-[300px] h-[300px] rounded-full bg-purple-500/5 blur-[100px]" />
|
||||
|
||||
@ -253,7 +253,7 @@ export default function ClinicProfilePage() {
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
className="on-light bg-white rounded-2xl border border-slate-100 shadow-[3px_4px_12px_rgba(0,0,0,0.06)] p-6"
|
||||
className="bg-white rounded-2xl border border-slate-100 shadow-[3px_4px_12px_rgba(0,0,0,0.06)] p-6"
|
||||
>
|
||||
<h2 className="font-serif text-lg font-bold text-primary-900 mb-4 flex items-center gap-2">
|
||||
<Building2 size={18} className="text-[#6C5CE7]" />
|
||||
@ -304,7 +304,7 @@ export default function ClinicProfilePage() {
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: true }}
|
||||
className="on-light bg-white rounded-2xl border border-slate-100 shadow-[3px_4px_12px_rgba(0,0,0,0.06)] p-6"
|
||||
className="bg-white rounded-2xl border border-slate-100 shadow-[3px_4px_12px_rgba(0,0,0,0.06)] p-6"
|
||||
>
|
||||
<h2 className="font-serif text-lg font-bold text-primary-900 mb-4 flex items-center gap-2">
|
||||
<Star size={18} className="text-[#6C5CE7]" />
|
||||
@ -348,7 +348,7 @@ export default function ClinicProfilePage() {
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: true }}
|
||||
className="on-light bg-white rounded-2xl border border-slate-100 shadow-[3px_4px_12px_rgba(0,0,0,0.06)] p-6"
|
||||
className="bg-white rounded-2xl border border-slate-100 shadow-[3px_4px_12px_rgba(0,0,0,0.06)] p-6"
|
||||
>
|
||||
<h2 className="font-serif text-lg font-bold text-primary-900 mb-4 flex items-center gap-2">
|
||||
<Stethoscope size={18} className="text-[#6C5CE7]" />
|
||||
@ -396,7 +396,7 @@ export default function ClinicProfilePage() {
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: true }}
|
||||
className="on-light bg-white rounded-2xl border border-slate-100 shadow-[3px_4px_12px_rgba(0,0,0,0.06)] p-6"
|
||||
className="bg-white rounded-2xl border border-slate-100 shadow-[3px_4px_12px_rgba(0,0,0,0.06)] p-6"
|
||||
>
|
||||
<h2 className="font-serif text-lg font-bold text-primary-900 mb-4 flex items-center gap-2">
|
||||
<Heart size={18} className="text-[#6C5CE7]" />
|
||||
@ -428,7 +428,7 @@ export default function ClinicProfilePage() {
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: true }}
|
||||
className="on-light bg-white rounded-2xl border border-slate-100 shadow-[3px_4px_12px_rgba(0,0,0,0.06)] p-6"
|
||||
className="bg-white rounded-2xl border border-slate-100 shadow-[3px_4px_12px_rgba(0,0,0,0.06)] p-6"
|
||||
>
|
||||
<h2 className="font-serif text-lg font-bold text-primary-900 mb-4 flex items-center gap-2">
|
||||
<ShieldCheck size={18} className="text-[#6C5CE7]" />
|
||||
@ -464,7 +464,7 @@ export default function ClinicProfilePage() {
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: true }}
|
||||
className="on-light bg-white rounded-2xl border border-slate-100 shadow-[3px_4px_12px_rgba(0,0,0,0.06)] p-6"
|
||||
className="bg-white rounded-2xl border border-slate-100 shadow-[3px_4px_12px_rgba(0,0,0,0.06)] p-6"
|
||||
>
|
||||
<h2 className="font-serif text-lg font-bold text-primary-900 mb-4 flex items-center gap-2">
|
||||
<TrendingUp size={18} className="text-[#6C5CE7]" />
|
||||
|
||||
@ -237,7 +237,7 @@ export default function DataValidationPage() {
|
||||
return (
|
||||
<div className="pt-20 min-h-screen">
|
||||
{/* ── Header ── */}
|
||||
<div className="on-dark bg-[#0A1128] py-14 px-6 relative overflow-hidden">
|
||||
<div className="bg-[#0A1128] py-14 px-6 relative overflow-hidden">
|
||||
<div className="absolute top-0 right-0 w-[500px] h-[500px] rounded-full bg-[#6C5CE7]/10 blur-[120px]" />
|
||||
<div className="absolute bottom-0 left-0 w-[300px] h-[300px] rounded-full bg-purple-500/5 blur-[100px]" />
|
||||
<div className="max-w-6xl mx-auto relative">
|
||||
@ -259,7 +259,7 @@ export default function DataValidationPage() {
|
||||
<h2 className="font-serif text-2xl font-bold text-primary-900 mb-2">수집 프로세스</h2>
|
||||
<p className="text-slate-500 text-sm mb-6">URL 하나로 시작하는 멀티소스 데이터 수집 파이프라인</p>
|
||||
|
||||
<div className="on-light bg-white rounded-2xl border border-slate-100 shadow-[3px_4px_12px_rgba(0,0,0,0.06)] p-6 md:p-8">
|
||||
<div className="bg-white rounded-2xl border border-slate-100 shadow-[3px_4px_12px_rgba(0,0,0,0.06)] p-6 md:p-8">
|
||||
<div className="flex flex-col md:flex-row items-start md:items-center gap-4 md:gap-0">
|
||||
{CRAWL_STEPS.map((step, i) => {
|
||||
const Icon = step.icon;
|
||||
@ -309,7 +309,7 @@ export default function DataValidationPage() {
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: true }}
|
||||
transition={{ delay: i * 0.1 }}
|
||||
className="on-light bg-white rounded-2xl border border-slate-100 shadow-[3px_4px_12px_rgba(0,0,0,0.06)] overflow-hidden"
|
||||
className="bg-white rounded-2xl border border-slate-100 shadow-[3px_4px_12px_rgba(0,0,0,0.06)] overflow-hidden"
|
||||
>
|
||||
{/* Card Header */}
|
||||
<div className="p-5 pb-4 flex items-center justify-between">
|
||||
@ -409,7 +409,7 @@ export default function DataValidationPage() {
|
||||
</motion.div>
|
||||
|
||||
{/* Alternatives Table */}
|
||||
<div className="on-light bg-white rounded-2xl border border-slate-100 shadow-[3px_4px_12px_rgba(0,0,0,0.06)] overflow-hidden">
|
||||
<div className="bg-white rounded-2xl border border-slate-100 shadow-[3px_4px_12px_rgba(0,0,0,0.06)] overflow-hidden">
|
||||
<div className="grid grid-cols-[1fr_1fr_auto_auto] gap-x-4 px-5 py-3 bg-slate-50 border-b border-slate-100 text-xs font-semibold text-slate-400 uppercase tracking-wider">
|
||||
<span>차단된 데이터</span>
|
||||
<span>대안 솔루션</span>
|
||||
@ -443,7 +443,7 @@ export default function DataValidationPage() {
|
||||
<h2 className="font-serif text-2xl font-bold text-primary-900 mb-2">전체 데이터 커버리지</h2>
|
||||
<p className="text-slate-500 text-sm mb-6">3가지 수집 방법 조합으로 100% 분석 데이터 확보</p>
|
||||
|
||||
<div className="on-light bg-white rounded-2xl border border-slate-100 shadow-[3px_4px_12px_rgba(0,0,0,0.06)] p-6 md:p-8 space-y-5">
|
||||
<div className="bg-white rounded-2xl border border-slate-100 shadow-[3px_4px_12px_rgba(0,0,0,0.06)] p-6 md:p-8 space-y-5">
|
||||
{COVERAGE.map((item, i) => (
|
||||
<motion.div
|
||||
key={item.method}
|
||||
@ -548,7 +548,7 @@ export default function DataValidationPage() {
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: true }}
|
||||
transition={{ delay: i * 0.08 }}
|
||||
className="on-light bg-white rounded-2xl border border-slate-100 shadow-[3px_4px_12px_rgba(0,0,0,0.06)] p-5 text-center"
|
||||
className="bg-white rounded-2xl border border-slate-100 shadow-[3px_4px_12px_rgba(0,0,0,0.06)] p-5 text-center"
|
||||
>
|
||||
<p className="text-xs text-slate-400 mb-1">{item.name}</p>
|
||||
<p className="text-xl font-bold text-primary-900 mb-0.5">{item.cost}</p>
|
||||
|
||||
@ -1,15 +1,16 @@
|
||||
import { useMemo } from 'react';
|
||||
import type { ComponentType } from 'react';
|
||||
import type { ComponentType, ReactNode } from 'react';
|
||||
import { useParams, Link } from 'react-router';
|
||||
import { motion } from 'motion/react';
|
||||
import { AEO_GEO_RUBRIC } from '../data/aeoGeoRubric';
|
||||
import { AEO_GEO_RUBRIC_V2 } from '../data/aeoGeoRubricV2';
|
||||
import { scoreDiscoveryV2 } from '../lib/discoveryScoreV2';
|
||||
import { AeoGeoV2Panel } from '../components/discovery/AeoGeoV2Panel';
|
||||
import { Tag } from '../components/discovery/Tag';
|
||||
import { ClinicInputsPanel } from '../components/discovery/ClinicInputsPanel';
|
||||
import { DISCOVERY_RESULTS } from '../data/discoveryResults';
|
||||
import { scoreDiscovery, levelToSeverity } from '../lib/discoveryScore';
|
||||
import type {
|
||||
ActionPriority,
|
||||
CategoryId,
|
||||
CategoryScore,
|
||||
ControlLevel,
|
||||
@ -63,6 +64,12 @@ const METHOD_LABEL: Record<EvidenceMethod, string> = {
|
||||
manual: '수동',
|
||||
};
|
||||
|
||||
const PRIORITY_STYLE: Record<ActionPriority, string> = {
|
||||
P0: 'bg-[#FFF0F0] text-[#7C3A4B] border-[#F5D5DC]',
|
||||
P1: 'bg-[#FFF6ED] text-[#7C5C3A] border-[#F5E0C5]',
|
||||
P2: 'bg-[#F3F0FF] text-[#4A3A7C] border-[#D5CDF5]',
|
||||
};
|
||||
|
||||
const scoreColor = (pct: number) =>
|
||||
pct <= 40 ? '#C084CF' : pct <= 60 ? '#8B9CF7' : pct <= 80 ? '#7C6DD8' : '#6C5CE7';
|
||||
|
||||
@ -80,10 +87,13 @@ export default function DiscoveryReportPage() {
|
||||
[result],
|
||||
);
|
||||
|
||||
// 진단은 AEO/GEO 36항목 실측 결과가 있어야 그린다. 사이트 빌드와는 별개 산출물이라
|
||||
// 빌드만 끝난 병원은 여기에 들어올 수 있다. 막다른 길로 두지 않고 다음 단계로 잇는다.
|
||||
if (!result || !overall) {
|
||||
return <NoReport clinicId={id ?? ''} />;
|
||||
return (
|
||||
<div className="pt-36 pb-24 px-6 text-center">
|
||||
<p className="text-slate-600">진단 결과를 찾을 수 없습니다.</p>
|
||||
<Link to="/" className="text-[#6C5CE7] text-base mt-4 inline-block">홈으로</Link>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const resultById = new Map(result.results.map((r) => [r.criterionId, r]));
|
||||
@ -91,7 +101,7 @@ export default function DiscoveryReportPage() {
|
||||
return (
|
||||
<div data-report-content className="[word-break:keep-all]">
|
||||
{/* ── 1. 헤더 (dark) ── */}
|
||||
<section className="on-dark bg-[#0A1128] text-white relative overflow-hidden pt-28 pb-16 md:pt-36 md:pb-20 px-6">
|
||||
<section className="bg-[#0A1128] text-white relative overflow-hidden pt-28 pb-16 md:pt-36 md:pb-20 px-6">
|
||||
<div className="absolute inset-0 bg-[radial-gradient(ellipse_at_top_right,rgba(108,92,231,0.18),transparent_60%)]" />
|
||||
<div className="relative max-w-7xl mx-auto grid lg:grid-cols-[1fr_auto] gap-12 items-center">
|
||||
<div>
|
||||
@ -122,19 +132,19 @@ export default function DiscoveryReportPage() {
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="on-light bg-white rounded-2xl shadow-[3px_4px_12px_rgba(0,0,0,0.06)] p-8 flex flex-col items-center gap-4 min-w-[260px]">
|
||||
<div className="bg-white rounded-2xl shadow-[3px_4px_12px_rgba(0,0,0,0.06)] p-8 flex flex-col items-center gap-4 min-w-[260px]">
|
||||
<div className="flex items-start gap-8">
|
||||
{overallV2 && [overallV2.aeo, overallV2.geo].map((ax) => (
|
||||
<div key={ax.axis.id} className="flex flex-col items-center gap-2">
|
||||
<ScoreRing score={ax.score} size={130} label={ax.axis.nameEn} />
|
||||
<div className="flex items-baseline gap-2">
|
||||
<span className="font-serif text-3xl font-black text-[#0A1128]">{ax.grade}</span>
|
||||
<span className="ui-note">{ax.axis.code} {ax.axis.name}</span>
|
||||
<span className="text-sm text-slate-500">{ax.axis.code} {ax.axis.name}</span>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<p className="ui-note">
|
||||
<p className="text-sm text-slate-500">
|
||||
v1.0 종합 {overall.score}/{overall.grade} · 실측 {overall.earned} / {overall.possible}점
|
||||
</p>
|
||||
</div>
|
||||
@ -165,13 +175,13 @@ export default function DiscoveryReportPage() {
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: true }}
|
||||
transition={{ delay: i * 0.08 }}
|
||||
className="on-light bg-white rounded-2xl border border-slate-100 shadow-[3px_4px_12px_rgba(0,0,0,0.06)] p-6"
|
||||
className="bg-white rounded-2xl border border-slate-100 shadow-[3px_4px_12px_rgba(0,0,0,0.06)] p-6"
|
||||
>
|
||||
<div className="flex items-start justify-between gap-4 mb-3">
|
||||
<h3 className="text-xl font-bold text-[#0A1128]">{f.title}</h3>
|
||||
<FindingIcon severity={f.severity} />
|
||||
</div>
|
||||
<p className="ui-body">{f.detail}</p>
|
||||
<p className="text-base text-slate-600 leading-relaxed">{f.detail}</p>
|
||||
</motion.div>
|
||||
))}
|
||||
</div>
|
||||
@ -191,7 +201,7 @@ export default function DiscoveryReportPage() {
|
||||
return (
|
||||
<div
|
||||
key={cs.category.id}
|
||||
className="on-light bg-white rounded-2xl shadow-[3px_4px_12px_rgba(0,0,0,0.06)] p-6 text-[#0A1128]"
|
||||
className="bg-white rounded-2xl shadow-[3px_4px_12px_rgba(0,0,0,0.06)] p-6 text-[#0A1128]"
|
||||
>
|
||||
<div className="flex flex-wrap items-center justify-between gap-4 mb-5">
|
||||
<div>
|
||||
@ -207,7 +217,7 @@ export default function DiscoveryReportPage() {
|
||||
</div>
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="text-right">
|
||||
<p className="ui-note">배점 {cs.weight} · 검증 {cs.verifiedCount}/{cs.totalCount}</p>
|
||||
<p className="text-sm text-slate-400">배점 {cs.weight} · 검증 {cs.verifiedCount}/{cs.totalCount}</p>
|
||||
<p className="text-base font-semibold">{cs.earned} / {cs.possible}점</p>
|
||||
</div>
|
||||
<ScoreRing score={cs.pct} size={64} />
|
||||
@ -224,14 +234,14 @@ export default function DiscoveryReportPage() {
|
||||
return (
|
||||
<div key={c.id} className="py-4 grid md:grid-cols-[170px_1fr_auto] gap-3 items-start">
|
||||
<div>
|
||||
<span className="ui-note font-semibold">{c.id}</span>
|
||||
<span className="text-sm font-semibold text-slate-400">{c.id}</span>
|
||||
<p className="text-base font-bold">{c.name}</p>
|
||||
<p className="ui-note mt-0.5">{c.weight}점</p>
|
||||
<p className="text-sm text-slate-400 mt-0.5">{c.weight}점</p>
|
||||
</div>
|
||||
<div>
|
||||
<p className="ui-body">{r?.evidence}</p>
|
||||
<p className="text-base text-slate-700">{r?.evidence}</p>
|
||||
{r?.source && (
|
||||
<p className="ui-note mt-1 font-mono break-all">{r.source}</p>
|
||||
<p className="text-sm text-slate-400 mt-1 font-mono break-all">{r.source}</p>
|
||||
)}
|
||||
<div className="flex flex-wrap gap-1.5 mt-2">
|
||||
<Tag>{EVIDENCE_LABEL[c.evidenceGrade]}</Tag>
|
||||
@ -242,7 +252,7 @@ export default function DiscoveryReportPage() {
|
||||
<div className="flex flex-col items-end gap-1">
|
||||
<SeverityBadge severity={levelToSeverity(level)} label={levelLabel} className="text-base font-bold px-4 py-1.5" />
|
||||
{level !== 'unverified' && (
|
||||
<span className="ui-note">
|
||||
<span className="text-sm text-slate-400">
|
||||
{Math.round((c.weight * level) / 3 * 10) / 10} / {c.weight}
|
||||
</span>
|
||||
)}
|
||||
@ -257,19 +267,106 @@ export default function DiscoveryReportPage() {
|
||||
</div>
|
||||
</SectionWrapper>
|
||||
|
||||
{/* 개선 제안(Action Plan)·하지 않을 것·빌드 제안·확인 항목 패널은 /actions/:id 로 옮겼다.
|
||||
증거(진단)와 처방(할 일)은 읽는 목적이 달라 여기 한 페이지에 두면 스크롤만 길어진다.
|
||||
확인 항목 패널은 /supporters/:id 라는 전용 단계가 이미 있어 여기서 다시 보여줄 필요가 없다. */}
|
||||
{/* ── 4. 실행 계획 (light) ── */}
|
||||
<SectionWrapper
|
||||
id="actions"
|
||||
title="Action Plan"
|
||||
subtitle="우선순위별 실행 항목. 각 항목이 어느 기준을 올리는지 연결"
|
||||
>
|
||||
<div className="space-y-4">
|
||||
{(['P0', 'P1', 'P2'] as ActionPriority[]).map((p) => (
|
||||
<div key={p}>
|
||||
<div className="flex items-center gap-3 mb-3 mt-6 first:mt-0">
|
||||
<span className={`inline-flex items-center rounded-full text-sm font-semibold px-3 py-1 border ${PRIORITY_STYLE[p]}`}>
|
||||
{p}
|
||||
</span>
|
||||
<span className="text-lg font-bold text-[#0A1128]">
|
||||
{p === 'P0' ? '즉시: 크롤러 접근·엔티티·측정' : p === 'P1' ? '4주 내: 콘텐츠 구조' : '지속: 신선도·시딩'}
|
||||
</span>
|
||||
</div>
|
||||
<div className="grid md:grid-cols-2 gap-4">
|
||||
{result.actions.filter((a) => a.priority === p).map((a, i) => (
|
||||
<motion.div
|
||||
key={a.title}
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: true }}
|
||||
transition={{ delay: i * 0.08 }}
|
||||
className="bg-white rounded-2xl border border-slate-100 shadow-sm p-5"
|
||||
>
|
||||
<div className="flex items-start justify-between gap-3 mb-2">
|
||||
<h4 className="font-bold text-[#0A1128]">{a.title}</h4>
|
||||
<span className="text-sm text-slate-400 shrink-0">{a.effort}</span>
|
||||
</div>
|
||||
<p className="text-base text-slate-600 leading-relaxed mb-3">{a.detail}</p>
|
||||
<div className="flex flex-wrap gap-1.5">
|
||||
{a.criterionIds.map((cid) => (
|
||||
<Tag key={cid}>{cid}</Tag>
|
||||
))}
|
||||
</div>
|
||||
</motion.div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</SectionWrapper>
|
||||
|
||||
{/* ── 4. 채점 기준표 (light) ── */}
|
||||
{/* ── 5. 하지 않을 것 + 측정 조건 (dark) ── */}
|
||||
<SectionWrapper
|
||||
id="integrity"
|
||||
title="What We Don't Sell"
|
||||
subtitle="근거 없는 것은 팔지 않는다. 이 리포트의 정직성 규약"
|
||||
dark
|
||||
>
|
||||
<div className="grid lg:grid-cols-2 gap-6">
|
||||
<div className="bg-white rounded-2xl shadow-[3px_4px_12px_rgba(0,0,0,0.06)] p-6 text-[#0A1128]">
|
||||
<h3 className="text-xl font-bold mb-4 font-sans text-[#0A1128]!">하지 않을 것</h3>
|
||||
<ul className="space-y-4">
|
||||
{result.notDoing.map((n) => (
|
||||
<li key={n.item} className="flex gap-3">
|
||||
<span className="shrink-0 text-[#D4889A] mt-0.5"><CrossFilled size={18} /></span>
|
||||
<div>
|
||||
<p className="text-base font-semibold">{n.item}</p>
|
||||
<p className="text-sm text-slate-500 mt-0.5">{n.reason}</p>
|
||||
</div>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
<div className="bg-white rounded-2xl shadow-[3px_4px_12px_rgba(0,0,0,0.06)] p-6 text-[#0A1128]">
|
||||
<h3 className="text-xl font-bold mb-4 font-sans text-[#0A1128]!">측정 조건</h3>
|
||||
<ul className="space-y-3">
|
||||
{result.conditions.map((c) => (
|
||||
<li key={c} className="flex gap-3">
|
||||
<span className="shrink-0 text-[#9B8AD4] mt-0.5"><CheckFilled size={18} /></span>
|
||||
<p className="text-base text-slate-600">{c}</p>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
<div className="mt-6 rounded-xl p-4 bg-[linear-gradient(to_right,#fff3eb,#e4cfff,#f5f9ff)]">
|
||||
<p className="text-sm text-slate-600 leading-relaxed">
|
||||
점수는 <strong>검증된 항목만</strong>으로 정규화했습니다. 미검증 항목(강남언니·GBP·GA4·로그)을
|
||||
확인하면 점수가 오를 수도, 내릴 수도 있습니다. 이 리포트는 답변엔진 노출을 보장하지 않으며,
|
||||
크롤러 접근·엔티티·콘텐츠 구조라는 <strong>전제조건</strong>의 충족 여부를 진단합니다.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</SectionWrapper>
|
||||
|
||||
{/* ── 5b. 서포터즈 자동 빌드 · 병원 확인 항목 (light, v2 §7-5) ── */}
|
||||
<ClinicInputsPanel clinicId={result.id} />
|
||||
|
||||
{/* ── 6. 채점 기준표 (light) ── */}
|
||||
<SectionWrapper
|
||||
id="rubric"
|
||||
title="Scoring Rubric"
|
||||
subtitle={`실측 기준표 v${AEO_GEO_RUBRIC.version} · ${AEO_GEO_RUBRIC.criteria.length}개 항목. v2.0 AEO/GEO 점수는 이 항목들을 파생 신호로 쓴다`}
|
||||
>
|
||||
<div className="on-light overflow-x-auto rounded-2xl border border-slate-100 shadow-sm bg-white">
|
||||
<div className="overflow-x-auto rounded-2xl border border-slate-100 shadow-sm bg-white">
|
||||
<table className="w-full text-base min-w-[860px]">
|
||||
<thead className="on-dark bg-[#0A1128] text-white">
|
||||
<thead className="bg-[#0A1128] text-white">
|
||||
<tr>
|
||||
<th className="text-left px-4 py-3 font-semibold w-16">ID</th>
|
||||
<th className="text-left px-4 py-3 font-semibold">항목</th>
|
||||
@ -298,50 +395,6 @@ export default function DiscoveryReportPage() {
|
||||
|
||||
/* ────────────────────────── 서브 컴포넌트 ────────────────────────── */
|
||||
|
||||
/** 진단 데이터가 없는 병원. 빌드·사진 확인으로 잇고, 진단이 무엇인지 한 줄로 알린다. */
|
||||
function NoReport({ clinicId }: { clinicId: string }) {
|
||||
return (
|
||||
<div className="[word-break:keep-all]">
|
||||
<section className="on-dark bg-[#0A1128] text-white relative overflow-hidden pt-28 pb-16 md:pt-36 md:pb-20 px-6">
|
||||
<div className="absolute inset-0 bg-[radial-gradient(ellipse_at_top_right,rgba(108,92,231,0.18),transparent_60%)]" />
|
||||
<div className="relative max-w-3xl mx-auto">
|
||||
<div className="inline-flex items-center gap-2 rounded-full bg-white/10 border border-white/10 px-4 py-1.5 mb-6">
|
||||
<PrismFilled size={16} className="text-purple-300" />
|
||||
<span className="text-sm font-semibold tracking-wide text-purple-200">INFINITH AI Discovery</span>
|
||||
</div>
|
||||
<h1 className="font-serif text-4xl md:text-5xl font-bold tracking-[-0.02em] mb-4">
|
||||
<span className="bg-gradient-to-r from-purple-300 to-blue-300 bg-clip-text text-transparent">No Audit Yet</span>
|
||||
</h1>
|
||||
<p className="text-2xl font-bold mb-3">{clinicId}</p>
|
||||
<p className="ui-body">
|
||||
이 병원은 아직 AEO/GEO 진단을 받지 않았습니다.{' '}
|
||||
진단은 36개 항목을 실제로 물어보고 답변을 확인해 매기는 별도 작업이라, 사이트 빌드만으로는 만들어지지 않습니다.
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="bg-white py-16 md:py-20 px-6">
|
||||
<div className="max-w-3xl mx-auto">
|
||||
<p className="ui-ask ui-ask-block mb-8">진단 없이도 아래는 바로 보실 수 있습니다.</p>
|
||||
<div className="grid sm:grid-cols-2 gap-4">
|
||||
<Link to={`/build/${clinicId}`} className="rounded-2xl border border-slate-200 p-5 hover:shadow-[4px_6px_16px_rgba(0,0,0,0.09)] transition-shadow">
|
||||
<p className="font-bold text-[#1D0024] mb-1">사이트 빌드 결과</p>
|
||||
<p className="ui-note">만들어진 글과 안내 페이지를 봅니다.</p>
|
||||
</Link>
|
||||
<Link to={`/images/${clinicId}`} className="rounded-2xl border border-slate-200 p-5 hover:shadow-[4px_6px_16px_rgba(0,0,0,0.09)] transition-shadow">
|
||||
<p className="font-bold text-[#1D0024] mb-1">사진 확인</p>
|
||||
<p className="ui-note">사이트에 실을 사진을 고릅니다.</p>
|
||||
</Link>
|
||||
</div>
|
||||
<p className="ui-note mt-8">
|
||||
진단 리포트 예시는 <Link to="/discovery/viewclinic" className="underline underline-offset-4 text-[#4A3A7C]">뷰성형외과 리포트</Link>에서 보실 수 있습니다.
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function CategoryBar({ cs, index }: { key?: string; cs: CategoryScore; index: number }) {
|
||||
const Icon = CATEGORY_ICON[cs.category.id];
|
||||
return (
|
||||
@ -350,14 +403,14 @@ function CategoryBar({ cs, index }: { key?: string; cs: CategoryScore; index: nu
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ delay: 0.2 + index * 0.06 }}
|
||||
className="on-light bg-white rounded-2xl shadow-[3px_4px_12px_rgba(0,0,0,0.06)] hover:shadow-[4px_6px_16px_rgba(0,0,0,0.09)] transition-shadow p-5 text-[#0A1128]"
|
||||
className="bg-white rounded-2xl shadow-[3px_4px_12px_rgba(0,0,0,0.06)] hover:shadow-[4px_6px_16px_rgba(0,0,0,0.09)] transition-shadow p-5 text-[#0A1128]"
|
||||
>
|
||||
<div className="flex items-center justify-between mb-3">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-[#6C5CE7]"><Icon size={18} /></span>
|
||||
<span className="text-base font-bold">{cs.category.code}. {cs.category.name}</span>
|
||||
</div>
|
||||
<span className="ui-note">{cs.weight}점</span>
|
||||
<span className="text-sm text-slate-400">{cs.weight}점</span>
|
||||
</div>
|
||||
<div className="h-2 rounded-full bg-slate-100 overflow-hidden">
|
||||
<motion.div
|
||||
@ -384,6 +437,14 @@ function FindingIcon({ severity }: { severity: 'critical' | 'warning' | 'good' }
|
||||
return <span className="text-[#D4889A] shrink-0"><CrossFilled size={22} /></span>;
|
||||
}
|
||||
|
||||
function Tag({ children }: { key?: string; children: ReactNode }) {
|
||||
return (
|
||||
<span className="inline-flex items-center rounded-full bg-slate-100 text-slate-600 text-sm font-medium px-2 py-0.5">
|
||||
{children}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
function RubricRows({ categoryName, criteria }: { key?: string; categoryName: string; criteria: RubricCriterion[] }) {
|
||||
return (
|
||||
<>
|
||||
@ -397,8 +458,8 @@ function RubricRows({ categoryName, criteria }: { key?: string; categoryName: st
|
||||
<td className="px-4 py-3 font-semibold text-slate-500">{c.id}</td>
|
||||
<td className="px-4 py-3">
|
||||
<p className="font-semibold text-[#0A1128]">{c.name}</p>
|
||||
<p className="ui-note mt-1">{c.rationale}</p>
|
||||
<p className="ui-note mt-1 font-mono">{c.howToCheck}</p>
|
||||
<p className="text-sm text-slate-500 mt-1 leading-relaxed">{c.rationale}</p>
|
||||
<p className="text-sm text-slate-400 mt-1 font-mono">{c.howToCheck}</p>
|
||||
</td>
|
||||
<td className="px-4 py-3 font-semibold">{c.weight}</td>
|
||||
<td className="px-4 py-3 text-slate-600">{EVIDENCE_LABEL[c.evidenceGrade]}</td>
|
||||
|
||||
@ -97,7 +97,7 @@ export default function DistributionPage() {
|
||||
return (
|
||||
<div className="pt-20 min-h-screen">
|
||||
{/* Header */}
|
||||
<div className="on-dark bg-[#0A1128] py-14 px-6 relative overflow-hidden">
|
||||
<div className="bg-[#0A1128] py-14 px-6 relative overflow-hidden">
|
||||
<div className="absolute top-0 right-0 w-[500px] h-[500px] rounded-full bg-[#6C5CE7]/10 blur-[120px]" />
|
||||
<div className="max-w-5xl mx-auto relative">
|
||||
<p className="text-xs font-semibold text-purple-300 tracking-widest uppercase mb-3">Content Distribution</p>
|
||||
|
||||
@ -1,37 +0,0 @@
|
||||
/**
|
||||
* /images/:id — 서포터즈 사이트에 실을 사진을 병원이 한 장씩 확인하는 화면.
|
||||
* 병원 담당자에게 링크로 보낸다. 확인 항목 입력(/supporters/:id)과 같은 supporter_inputs 트랙을 쓴다.
|
||||
*/
|
||||
import { useParams, Link } from 'react-router';
|
||||
import { ImageReviewPanel } from '../components/discovery/ImageReviewPanel';
|
||||
|
||||
export default function ImageReviewPage() {
|
||||
const { id } = useParams<{ id: string }>();
|
||||
if (!id) return null;
|
||||
return (
|
||||
<div className="[word-break:keep-all]">
|
||||
<section className="on-dark bg-[#0A1128] text-white relative overflow-hidden pt-28 pb-14 md:pt-36 md:pb-16 px-6">
|
||||
<div className="absolute inset-0 bg-[radial-gradient(ellipse_at_top_right,rgba(108,92,231,0.18),transparent_60%)]" />
|
||||
<div className="relative max-w-7xl mx-auto">
|
||||
<div className="inline-flex items-center gap-2 rounded-full bg-white/10 border border-white/10 px-4 py-1.5 mb-6">
|
||||
<span className="text-sm font-semibold tracking-wide text-purple-200">INFINITH AI Discovery · Photo Check</span>
|
||||
</div>
|
||||
<h1 className="font-serif text-4xl md:text-6xl font-bold tracking-[-0.02em] mb-3">
|
||||
<span className="bg-gradient-to-r from-purple-300 to-blue-300 bg-clip-text text-transparent">사진을 확인해 주세요</span>
|
||||
</h1>
|
||||
<p className="ui-body">
|
||||
병원 홈페이지에 올라와 있는 사진을 그대로 가져왔습니다.{' '}
|
||||
그중에는 사이트에 싣기 어려운 사진이 섞여 있을 수 있습니다.
|
||||
</p>
|
||||
<p className="ui-ask ui-ask-block mt-4">병원에서 한 장씩 봐 주셔야 합니다.</p>
|
||||
<p className="text-purple-200 text-sm mt-4">
|
||||
병원 ID <span className="font-mono text-white">{id}</span>
|
||||
{' · '}
|
||||
<Link to={`/supporters/${id}`} className="underline underline-offset-4 text-purple-300">확인 항목 입력으로 가기</Link>
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
<ImageReviewPanel clinicId={id} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -143,7 +143,7 @@ export default function PerformancePage() {
|
||||
return (
|
||||
<div className="pt-20 min-h-screen">
|
||||
{/* Header */}
|
||||
<div className="on-dark bg-[#0A1128] py-14 px-6 relative overflow-hidden">
|
||||
<div className="bg-[#0A1128] py-14 px-6 relative overflow-hidden">
|
||||
<div className="absolute top-0 right-0 w-[500px] h-[500px] rounded-full bg-[#6C5CE7]/10 blur-[120px]" />
|
||||
<div className="absolute bottom-0 left-0 w-[300px] h-[300px] rounded-full bg-purple-500/5 blur-[100px]" />
|
||||
<div className="max-w-6xl mx-auto relative">
|
||||
@ -177,7 +177,7 @@ export default function PerformancePage() {
|
||||
{OVERVIEW_STATS.map((stat, i) => (
|
||||
<motion.div
|
||||
key={stat.label}
|
||||
className="on-light bg-white rounded-2xl border border-slate-100 shadow-[3px_4px_12px_rgba(0,0,0,0.06)] p-4"
|
||||
className="bg-white rounded-2xl border border-slate-100 shadow-[3px_4px_12px_rgba(0,0,0,0.06)] p-4"
|
||||
initial={{ opacity: 0, y: 15 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.3, delay: i * 0.05 }}
|
||||
@ -190,7 +190,7 @@ export default function PerformancePage() {
|
||||
</div>
|
||||
|
||||
{/* ═══ Section 1: Marketing Funnel ═══ */}
|
||||
<div className="on-light bg-white rounded-2xl border border-slate-100 shadow-[3px_4px_12px_rgba(0,0,0,0.06)] p-6 mb-10">
|
||||
<div className="bg-white rounded-2xl border border-slate-100 shadow-[3px_4px_12px_rgba(0,0,0,0.06)] p-6 mb-10">
|
||||
<div className="flex items-center justify-between mb-6">
|
||||
<div>
|
||||
<h3 className="font-serif font-bold text-xl text-[#0A1128]">마케팅 퍼널</h3>
|
||||
@ -260,7 +260,7 @@ export default function PerformancePage() {
|
||||
</div>
|
||||
|
||||
{/* ═══ Section 2: Channel Trend (Stacked Bar) ═══ */}
|
||||
<div className="on-light bg-white rounded-2xl border border-slate-100 shadow-[3px_4px_12px_rgba(0,0,0,0.06)] p-6 mb-10">
|
||||
<div className="bg-white rounded-2xl border border-slate-100 shadow-[3px_4px_12px_rgba(0,0,0,0.06)] p-6 mb-10">
|
||||
<div className="flex items-center justify-between mb-6">
|
||||
<div>
|
||||
<h3 className="font-serif font-bold text-xl text-[#0A1128]">채널별 주간 트렌드</h3>
|
||||
@ -298,7 +298,7 @@ export default function PerformancePage() {
|
||||
animate={{ scaleY: 1 }}
|
||||
transition={{ duration: 0.5, delay: wi * 0.1 }}
|
||||
>
|
||||
<div className="on-dark absolute -top-8 left-1/2 -translate-x-1/2 hidden group-hover:block bg-[#0A1128] text-white text-xs px-2 py-1 rounded whitespace-nowrap z-10">
|
||||
<div className="absolute -top-8 left-1/2 -translate-x-1/2 hidden group-hover:block bg-[#0A1128] text-white text-xs px-2 py-1 rounded whitespace-nowrap z-10">
|
||||
{ch.label}: {val}K
|
||||
</div>
|
||||
</motion.div>
|
||||
@ -321,7 +321,7 @@ export default function PerformancePage() {
|
||||
</div>
|
||||
|
||||
{/* ═══ Section 3: Day × Time Heatmap ═══ */}
|
||||
<div className="on-light bg-white rounded-2xl border border-slate-100 shadow-[3px_4px_12px_rgba(0,0,0,0.06)] p-6 mb-10">
|
||||
<div className="bg-white rounded-2xl border border-slate-100 shadow-[3px_4px_12px_rgba(0,0,0,0.06)] p-6 mb-10">
|
||||
<div className="flex items-center justify-between mb-6">
|
||||
<div>
|
||||
<h3 className="font-serif font-bold text-xl text-[#0A1128]">최적 게시 시간</h3>
|
||||
@ -387,7 +387,7 @@ export default function PerformancePage() {
|
||||
return (
|
||||
<motion.div
|
||||
key={ch.id}
|
||||
className="on-light bg-white rounded-2xl border border-slate-100 shadow-[3px_4px_12px_rgba(0,0,0,0.06)] p-5"
|
||||
className="bg-white rounded-2xl border border-slate-100 shadow-[3px_4px_12px_rgba(0,0,0,0.06)] p-5"
|
||||
initial={{ opacity: 0, y: 15 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.3, delay: i * 0.08 }}
|
||||
@ -421,8 +421,8 @@ export default function PerformancePage() {
|
||||
|
||||
{/* Top Content */}
|
||||
<h3 className="font-serif font-bold text-xl text-[#0A1128] mb-4">인기 콘텐츠 TOP 5</h3>
|
||||
<div className="on-light bg-white rounded-2xl border border-slate-100 shadow-[3px_4px_12px_rgba(0,0,0,0.06)] overflow-hidden mb-10">
|
||||
<div className="on-dark grid grid-cols-[1fr_100px_80px_80px_60px_70px_70px] gap-2 px-5 py-3 bg-[#0A1128] text-white text-xs font-medium">
|
||||
<div className="bg-white rounded-2xl border border-slate-100 shadow-[3px_4px_12px_rgba(0,0,0,0.06)] overflow-hidden mb-10">
|
||||
<div className="grid grid-cols-[1fr_100px_80px_80px_60px_70px_70px] gap-2 px-5 py-3 bg-[#0A1128] text-white text-xs font-medium">
|
||||
<span>콘텐츠</span>
|
||||
<span>채널</span>
|
||||
<span className="text-right">조회수</span>
|
||||
|
||||
@ -194,7 +194,7 @@ const TierCard: React.FC<TierCardProps> = ({ tier, billing, onSelect }) => {
|
||||
{/* 이름 + 태그라인 */}
|
||||
<div className="mb-6">
|
||||
<h3 className="text-2xl font-serif font-bold text-primary-900 mb-2">{tier.name}</h3>
|
||||
<p className="ui-note">{tier.tagline}</p>
|
||||
<p className="text-sm text-slate-500 leading-relaxed break-keep">{tier.tagline}</p>
|
||||
</div>
|
||||
|
||||
{/* 가격 */}
|
||||
@ -203,7 +203,7 @@ const TierCard: React.FC<TierCardProps> = ({ tier, billing, onSelect }) => {
|
||||
<span className="text-4xl font-bold text-primary-900 tracking-tight">
|
||||
{formatKRW(price)}
|
||||
</span>
|
||||
<span className="ui-note">/월</span>
|
||||
<span className="text-slate-500 text-sm">/월</span>
|
||||
</div>
|
||||
{isAnnual && (
|
||||
<p className="text-xs text-accent font-semibold mt-2">
|
||||
|
||||
@ -1,241 +0,0 @@
|
||||
/**
|
||||
* /build/:id — 진단 리포트에서 "사이트로 빌드"를 누른 뒤 보는 진행 화면.
|
||||
*
|
||||
* 예전 전략마케팅 툴의 분석 진행 화면(/report/loading)의 단계 UX를 그대로 이어받되,
|
||||
* 진행 상황을 프런트가 직접 만들지 않고 supporter_builds 를 폴링해 워커가 쓴 값을 읽는다.
|
||||
* 프런트가 파이프라인을 돌리지 않으므로 창을 닫아도 빌드는 계속된다.
|
||||
*/
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import { useParams, Link } from 'react-router';
|
||||
import { motion } from 'motion/react';
|
||||
import { supabase } from '../lib/supabase';
|
||||
import { BUILD_STEPS, STATE_LABEL, progressOf, stepStates, warningsOf } from '../lib/buildPhases';
|
||||
import type { BuildState, PhaseRow } from '../lib/buildPhases';
|
||||
import { CheckFilled, WarningFilled, EyeFilled, PrismFilled } from '../components/icons/FilledIcons';
|
||||
|
||||
type Build = {
|
||||
id: string;
|
||||
clinic_id: string;
|
||||
clinic_name: string | null;
|
||||
status: BuildState;
|
||||
phase: string | null;
|
||||
phases: PhaseRow[] | null;
|
||||
posts: Array<{ id: string; title: string }> | null;
|
||||
preview_url: string | null;
|
||||
error: string | null;
|
||||
updated_at: string;
|
||||
};
|
||||
|
||||
const POLL_MS = 4000;
|
||||
|
||||
export default function SiteBuildPage() {
|
||||
const { id } = useParams<{ id: string }>();
|
||||
const [build, setBuild] = useState<Build | null>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [loadError, setLoadError] = useState<string | null>(null);
|
||||
const timer = useRef<number | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (!id) return;
|
||||
let alive = true;
|
||||
|
||||
async function tick() {
|
||||
const { data, error } = await supabase
|
||||
.from('supporter_builds')
|
||||
.select('id,clinic_id,clinic_name,status,phase,phases,posts,preview_url,error,updated_at')
|
||||
.eq('clinic_id', id)
|
||||
.order('created_at', { ascending: false })
|
||||
.limit(1);
|
||||
if (!alive) return;
|
||||
if (error) setLoadError(error.message);
|
||||
const row = (data?.[0] as Build) ?? null;
|
||||
setBuild(row);
|
||||
setLoading(false);
|
||||
// 끝났으면 더 부르지 않는다.
|
||||
const done = row && ['preview', 'published', 'published_pending_tasks', 'approved', 'failed', 'gate_failed'].includes(row.status);
|
||||
if (!done) timer.current = window.setTimeout(tick, POLL_MS);
|
||||
}
|
||||
|
||||
void tick();
|
||||
return () => { alive = false; if (timer.current) window.clearTimeout(timer.current); };
|
||||
}, [id]);
|
||||
|
||||
if (!id) return null;
|
||||
|
||||
const state: BuildState = build?.status ?? 'queued';
|
||||
const finished = ['preview', 'published', 'published_pending_tasks', 'approved'].includes(state);
|
||||
const stopped = state === 'failed' || state === 'gate_failed';
|
||||
const states = stepStates(build?.phases, build?.phase ?? null, state);
|
||||
const pct = Math.round(progressOf(build?.phases, state) * 100);
|
||||
const warnings = warningsOf(build?.phases);
|
||||
const name = build?.clinic_name || id;
|
||||
|
||||
return (
|
||||
<div className="on-dark min-h-screen bg-[#0A1128] text-white relative overflow-hidden">
|
||||
<div className="absolute inset-0 bg-[radial-gradient(ellipse_at_top_right,rgba(108,92,231,0.18),transparent_60%)]" />
|
||||
|
||||
<div className="relative max-w-3xl mx-auto px-6 pt-28 pb-24 md:pt-36">
|
||||
<div className="inline-flex items-center gap-2 rounded-full bg-white/10 border border-white/10 px-4 py-1.5 mb-8">
|
||||
<PrismFilled size={16} className="text-purple-300" />
|
||||
<span className="text-sm font-semibold tracking-wide text-purple-200">INFINITH AI Discovery · Site Build</span>
|
||||
</div>
|
||||
|
||||
<h1 className="font-serif text-4xl md:text-5xl font-bold tracking-[-0.02em] mb-3">
|
||||
<span className="bg-gradient-to-r from-purple-300 to-blue-300 bg-clip-text text-transparent">
|
||||
{finished ? 'Your Site Is Ready' : 'Building Your Site'}
|
||||
</span>
|
||||
</h1>
|
||||
<p className="text-2xl font-bold mb-1">{name}</p>
|
||||
<p className="text-purple-200 text-base mb-12">{STATE_LABEL[state]}</p>
|
||||
|
||||
{loading && <p className="text-white/50">상태를 불러오는 중입니다.</p>}
|
||||
|
||||
{!loading && !build && (
|
||||
<div className="bg-white/5 border border-white/10 rounded-2xl p-8">
|
||||
<h2 className="text-lg font-bold mb-2">아직 빌드가 시작되지 않았습니다</h2>
|
||||
<p className="text-white/60 text-base leading-[1.75]">
|
||||
{loadError ? `상태를 불러오지 못했습니다. ${loadError}` : '진단 리포트에서 사이트로 빌드를 눌러 주세요.'}
|
||||
</p>
|
||||
<Link to={`/discovery/${id}`} className="inline-block mt-6 rounded-full bg-white/10 border border-white/20 px-6 py-3 text-base font-semibold hover:bg-white/15 transition-colors">
|
||||
진단 리포트로 가기
|
||||
</Link>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{!loading && build && (
|
||||
<>
|
||||
{/* 단계 */}
|
||||
<div className="space-y-6 mb-12">
|
||||
{BUILD_STEPS.map((step, i) => {
|
||||
const st = states[i];
|
||||
return (
|
||||
<motion.div
|
||||
key={step.key}
|
||||
initial={{ opacity: 0, x: -16 }}
|
||||
animate={{ opacity: st === 'waiting' ? 0.35 : 1, x: 0 }}
|
||||
transition={{ duration: 0.4, delay: i * 0.08 }}
|
||||
className="flex items-start gap-4"
|
||||
>
|
||||
<div className="w-7 h-7 shrink-0 flex items-center justify-center mt-0.5">
|
||||
{st === 'done' ? (
|
||||
<motion.div
|
||||
initial={{ scale: 0 }}
|
||||
animate={{ scale: 1 }}
|
||||
transition={{ type: 'spring', stiffness: 300, damping: 20 }}
|
||||
className="w-7 h-7 rounded-full bg-gradient-to-r from-[#4F1DA1] to-[#6C5CE7] flex items-center justify-center"
|
||||
>
|
||||
<CheckFilled size={14} className="text-white" />
|
||||
</motion.div>
|
||||
) : st === 'active' ? (
|
||||
<div className="w-7 h-7 rounded-full border-2 border-purple-400 border-t-transparent animate-spin" />
|
||||
) : st === 'failed' ? (
|
||||
<div className="w-7 h-7 rounded-full bg-[#FFF0F0] flex items-center justify-center">
|
||||
<WarningFilled size={14} className="text-[#7C3A4B]" />
|
||||
</div>
|
||||
) : (
|
||||
<div className="w-7 h-7 rounded-full border-2 border-white/10" />
|
||||
)}
|
||||
</div>
|
||||
<div>
|
||||
<p className={`text-base font-semibold ${st === 'done' ? 'text-white' : st === 'active' ? 'text-purple-200' : st === 'failed' ? 'text-[#F5D5DC]' : 'text-white/40'}`}>
|
||||
{st === 'done' ? step.labelDone : step.label}
|
||||
</p>
|
||||
<p className="ui-note mt-0.5">{step.detail}</p>
|
||||
</div>
|
||||
</motion.div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
|
||||
{/* 진행바 */}
|
||||
<div className="w-full h-2 bg-white/10 rounded-full overflow-hidden">
|
||||
<motion.div
|
||||
initial={{ width: '0%' }}
|
||||
animate={{ width: `${pct}%` }}
|
||||
transition={{ duration: 0.8, ease: 'easeInOut' }}
|
||||
className="h-full bg-gradient-to-r from-[#4F1DA1] to-[#6C5CE7] rounded-full"
|
||||
/>
|
||||
</div>
|
||||
<p className="text-white/40 text-sm mt-4">
|
||||
{finished
|
||||
? `${build.posts?.length ?? 0}편의 글과 안내 페이지를 만들었습니다.`
|
||||
: '창을 닫으셔도 계속 진행됩니다. 보통 10분 안팎 걸립니다.'}
|
||||
</p>
|
||||
|
||||
{/* 멈춤 */}
|
||||
{stopped && (
|
||||
<div className="mt-10 bg-white/5 border border-[#F5D5DC]/30 rounded-2xl p-6">
|
||||
<h2 className="text-lg font-bold mb-2 text-[#F5D5DC]">
|
||||
{state === 'gate_failed' ? '발행 기준에서 멈췄습니다' : '만들다가 멈췄습니다'}
|
||||
</h2>
|
||||
<p className="ui-body">
|
||||
{state === 'gate_failed'
|
||||
? '사이트에 실으면 안 되는 내용이 걸려 배포하지 않고 멈췄습니다. 담당자가 확인한 뒤 다시 진행합니다.'
|
||||
: '자동으로 처리하지 못한 지점이 있습니다. 담당자가 확인한 뒤 다시 진행합니다.'}
|
||||
</p>
|
||||
{build.error && <p className="text-white/40 text-sm mt-3 font-mono break-all">{build.error.slice(0, 300)}</p>}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 완료 */}
|
||||
{finished && (
|
||||
<div className="mt-12 grid gap-4">
|
||||
<div className="on-light bg-white rounded-2xl p-6 md:p-7 shadow-[3px_4px_12px_rgba(0,0,0,0.2)]">
|
||||
<h2 className="ui-ask text-lg mb-2">다음은 병원에서 확인해 주셔야 합니다</h2>
|
||||
<p className="ui-body mb-5">
|
||||
지금 사이트는 검색에 노출되지 않는 상태입니다.{' '}
|
||||
아래 두 가지를 확인해 주시면 공개 준비가 끝납니다.
|
||||
</p>
|
||||
<div className="grid sm:grid-cols-2 gap-3">
|
||||
<Link to={`/images/${build.clinic_id}`} className="rounded-xl border border-slate-200 p-4 hover:shadow-[4px_6px_16px_rgba(0,0,0,0.09)] transition-shadow">
|
||||
<p className="font-bold text-[#1D0024] mb-1">사진 확인</p>
|
||||
<p className="ui-body">사이트에 실을 사진을 한 장씩 골라 주세요.</p>
|
||||
</Link>
|
||||
<Link to={`/supporters/${build.clinic_id}`} className="rounded-xl border border-slate-200 p-4 hover:shadow-[4px_6px_16px_rgba(0,0,0,0.09)] transition-shadow">
|
||||
<p className="font-bold text-[#1D0024] mb-1">확인 항목 입력</p>
|
||||
<p className="ui-body">공개 자료에 없어 병원만 답할 수 있는 값입니다.</p>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{build.preview_url && (
|
||||
<a
|
||||
href={build.preview_url}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
className="btn-on-dark justify-center"
|
||||
>
|
||||
<EyeFilled size={16} /> {state === 'published' || state === 'approved' ? '공개본 사이트 열기' : '초안 사이트 열기'}
|
||||
</a>
|
||||
)}
|
||||
|
||||
{warnings.length > 0 && (
|
||||
<div className="bg-white/5 border border-white/10 rounded-2xl p-6">
|
||||
<div className="flex items-center gap-2 mb-2 text-[#F5E0C5]">
|
||||
<WarningFilled size={15} />
|
||||
<h3 className="text-base font-bold">확인 대기로 발행된 부분</h3>
|
||||
</div>
|
||||
<ul className="flex flex-col gap-1.5">
|
||||
{warnings.map((w) => (
|
||||
<li key={w.phase} className="ui-note">
|
||||
{w.summary?.split('\n')[0] ?? w.phase}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
<p className="text-sm text-white/40 mt-3">자동으로 채우지 못한 값입니다. 지어내지 않고 빈 자리를 그대로 두었습니다.</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<p className="text-white/30 text-xs mt-10">
|
||||
갱신 {build.updated_at.slice(0, 16).replace('T', ' ')}
|
||||
{' · '}
|
||||
<Link to={`/discovery/${build.clinic_id}`} className="underline underline-offset-4">진단 리포트</Link>
|
||||
</p>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -10,7 +10,7 @@ export default function SupportersBuildPage() {
|
||||
if (!id) return null;
|
||||
return (
|
||||
<div className="[word-break:keep-all]">
|
||||
<section className="on-dark bg-[#0A1128] text-white relative overflow-hidden pt-28 pb-14 md:pt-36 md:pb-16 px-6">
|
||||
<section className="bg-[#0A1128] text-white relative overflow-hidden pt-28 pb-14 md:pt-36 md:pb-16 px-6">
|
||||
<div className="absolute inset-0 bg-[radial-gradient(ellipse_at_top_right,rgba(108,92,231,0.18),transparent_60%)]" />
|
||||
<div className="relative max-w-7xl mx-auto">
|
||||
<div className="inline-flex items-center gap-2 rounded-full bg-white/10 border border-white/10 px-4 py-1.5 mb-6">
|
||||
@ -20,8 +20,8 @@ export default function SupportersBuildPage() {
|
||||
<span className="bg-gradient-to-r from-purple-300 to-blue-300 bg-clip-text text-transparent">Supporters Preview</span>
|
||||
</h1>
|
||||
<p className="text-purple-200 text-base">병원 ID <span className="font-mono text-white">{id}</span></p>
|
||||
<p className="ui-ask ui-ask-block mt-4">
|
||||
아래에서 미리보기를 열고, 병원만 답할 수 있는 항목을 입력해 주세요.
|
||||
<p className="text-white/60 text-sm mt-4">
|
||||
홈페이지 공개 자료만으로 만든 서포터즈 사이트 초안입니다. 아래에서 미리보기를 열고, 병원만 답할 수 있는 항목을 입력해 주세요.
|
||||
{' '}<Link to="/discovery" className="underline underline-offset-4 text-purple-300">AI Discovery 소개</Link>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@ -1,65 +0,0 @@
|
||||
-- ═══════════════════════════════════════════════════════════════
|
||||
-- 서포터즈 사이트 주소: 병원별 서브도메인
|
||||
-- ═══════════════════════════════════════════════════════════════
|
||||
-- 서포터즈 사이트는 공용 도메인(webforai.kr)의 병원별 서브도메인에 둔다.
|
||||
-- 병원마다 도메인을 사면 병원 수만큼 갱신 대상이 늘고, 한 건만 놓쳐도 그 병원 사이트가 멈춘다.
|
||||
-- 근거: docs/business/INFINITH_서포터즈_사이트주소_방식비교_2026-09-14.docx
|
||||
--
|
||||
-- 이 표가 서브도메인의 단일 원본이다. 워커·배포 스크립트가 이름을 매번 짓지 않고 여기서 읽는다.
|
||||
-- 이름을 코드가 지으면 "뷰"·"연세"·"그랜드"처럼 여러 병원이 쓰는 이름에서 충돌이 난다.
|
||||
|
||||
ALTER TABLE clinic_registry
|
||||
ADD COLUMN IF NOT EXISTS subdomain TEXT,
|
||||
-- 워커가 쓰는 병원 id(--clinic). 서브도메인과 다를 수 있어 따로 둔다.
|
||||
ADD COLUMN IF NOT EXISTS clinic_slug TEXT,
|
||||
-- 주소를 언제 붙였는지. 색인 전환 판단에 쓴다.
|
||||
ADD COLUMN IF NOT EXISTS subdomain_assigned_at TIMESTAMPTZ;
|
||||
|
||||
-- 한 서브도메인은 한 병원에만. 대소문자 구분 없이 막는다.
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS clinic_registry_subdomain_key
|
||||
ON clinic_registry (lower(subdomain))
|
||||
WHERE subdomain IS NOT NULL;
|
||||
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS clinic_registry_clinic_slug_key
|
||||
ON clinic_registry (lower(clinic_slug))
|
||||
WHERE clinic_slug IS NOT NULL;
|
||||
|
||||
-- 형식: 소문자·숫자·붙임표만. 2~40자(1자는 실수일 가능성이 높고 짧은 이름을 병원에 내주게 된다). 붙임표로 시작하거나 끝날 수 없고 연속 붙임표도 막는다.
|
||||
-- 대문자·밑줄·점을 넣으면 DNS 레이블로 쓸 수 없거나 브라우저가 다르게 해석한다.
|
||||
ALTER TABLE clinic_registry
|
||||
DROP CONSTRAINT IF EXISTS clinic_registry_subdomain_format;
|
||||
ALTER TABLE clinic_registry
|
||||
ADD CONSTRAINT clinic_registry_subdomain_format
|
||||
CHECK (subdomain IS NULL OR (subdomain ~ '^[a-z0-9][a-z0-9-]{0,38}[a-z0-9]$' AND subdomain !~ '--'));
|
||||
|
||||
-- 예약어. 서비스가 쓰거나 쓸 수 있는 이름은 병원에 주지 않는다.
|
||||
-- 여기 있는 이름을 병원에 내주면 나중에 메일·API·관리 화면을 붙일 때 주소를 회수해야 한다.
|
||||
-- 회수하면 그 시점에 이미 색인된 주소가 깨진다.
|
||||
ALTER TABLE clinic_registry
|
||||
DROP CONSTRAINT IF EXISTS clinic_registry_subdomain_reserved;
|
||||
ALTER TABLE clinic_registry
|
||||
ADD CONSTRAINT clinic_registry_subdomain_reserved
|
||||
CHECK (subdomain IS NULL OR subdomain NOT IN (
|
||||
-- 웹·서비스 기본
|
||||
'www', 'web', 'app', 'apps', 'api', 'admin', 'dashboard', 'console', 'portal',
|
||||
'static', 'assets', 'cdn', 'img', 'images', 'media', 'files', 'download', 'downloads',
|
||||
-- 메일·인증 (잘못 내주면 메일 수신과 도메인 소유 확인이 깨진다)
|
||||
'mail', 'email', 'smtp', 'imap', 'pop', 'mx', 'ns', 'ns1', 'ns2', 'dns',
|
||||
'autodiscover', 'autoconfig', 'mta-sts', 'dmarc', 'spf', 'dkim',
|
||||
-- 환경·배포
|
||||
'dev', 'test', 'stage', 'staging', 'preview', 'demo', 'sandbox', 'beta', 'alpha', 'local',
|
||||
'status', 'health', 'metrics', 'log', 'logs', 'monitor',
|
||||
-- 제품·조직
|
||||
'infinith', 'webforai', 'supporters', 'clinic', 'clinics', 'hospital', 'report', 'reports',
|
||||
'discovery', 'plan', 'planner', 'blog', 'news', 'newsroom', 'help', 'support', 'docs',
|
||||
'about', 'contact', 'login', 'auth', 'account', 'billing', 'pay', 'payment',
|
||||
-- 오인 방지
|
||||
'en', 'ko', 'ja', 'zh', 'kr', 'go', 'or', 'co', 'ne', 're'
|
||||
));
|
||||
|
||||
COMMENT ON COLUMN clinic_registry.subdomain IS
|
||||
'서포터즈 사이트 주소의 서브도메인 라벨. 공용 도메인 앞에 붙는다(예: viewpl → viewpl.webforai.kr). 소문자·숫자·붙임표만.';
|
||||
COMMENT ON COLUMN clinic_registry.clinic_slug IS
|
||||
'워커 --clinic 에 넘기는 병원 id. 빌드 폴더·Vercel 프로젝트 이름에 쓰인다. 서브도메인과 다를 수 있다.';
|
||||
COMMENT ON COLUMN clinic_registry.subdomain_assigned_at IS
|
||||
'서브도메인을 배정한 시각. 색인 허용 전환 전에 주소를 바꾸는 편이 싸므로 그 판단에 쓴다.';
|
||||
@ -9,9 +9,7 @@
|
||||
"gangnamunni": "https://www.gangnamunni.com/hospitals/125",
|
||||
"factSheet": {
|
||||
"kind": "피부과",
|
||||
"areaLabel": "서울 강남 청담",
|
||||
"name": "오라클피부과의원",
|
||||
"shortName": "오라클피부과"
|
||||
"areaLabel": "서울 강남 청담"
|
||||
},
|
||||
"site": {
|
||||
"heroImage": {
|
||||
@ -37,16 +35,11 @@
|
||||
"file": "supporters/briefs/oracle/hq-building.jpg",
|
||||
"alt": "오라클피부과의원 본사 건물 외관. 흰색 외벽 건물 상단과 1층 입구에 오라클 간판",
|
||||
"caption": "본사 건물 외관. 출처: 오라클메디컬그룹 홈페이지"
|
||||
},
|
||||
"naverSiteVerification": "f083f28e6b117805e9722f0a1adf7834a8219688",
|
||||
"shareNameEn": "Oracle Dermatology"
|
||||
}
|
||||
},
|
||||
"excludeImages": [
|
||||
"sub_0"
|
||||
],
|
||||
"_logoNote": "2026-09-10 haewon 지적(녹색 배너형 GIF는 완성도가 떨어짐) → 그룹 사이트 oraclemedicalgroup.com/images/logo_oracle_02.png (176×68, 투명 배경 워드마크)로 교체. 클리닉 사이트에는 CI 페이지가 없음(메뉴 CI소개가 빈 링크). 이전 배너형은 logo-horizontal.png 로 보존. haewon 제안(2026-09-10)대로 원본 배너에서 왼쪽 문구 영역을 잘라낸 logo-horizontal-cropped.png(133×49, 녹색 배경 유지)도 보관. 녹색 박스형을 쓰려면 logo 값을 이 파일로 바꾸고 --from youtube 재실행.",
|
||||
"_imageNote": "2026-09-10: 본사 건물 외관은 그룹 사이트 oraclemedicalgroup.com/images/company_02.jpg (렌더링 이미지, 간판 오라클피부과의원). 같은 페이지의 다른 사진(화장품·모델·장비·전자차트)은 제외.",
|
||||
"_factSheetNote": "상호 고정. 홈 제목 추정은 슬로건을 상호로 삼을 수 있다. 근거: supporters-oracle 기존 배포본 표기.",
|
||||
"_verifyNote": "네이버 서치어드바이저 소유 확인 값(2026-09-14 발급, oracle.webforai.kr 용). 사이트마다 값이 달라 다른 병원에 옮겨 쓰면 확인되지 않는다.",
|
||||
"_shareNameEnNote": "shareNameEn: 링크 공유 이미지(og.jpg)의 영문 병원명. 출처: 공식 영문 사이트 en.cdoracleclinic.com/about 'Introduction to Oracle Dermatology' (2026-09-15 확인)"
|
||||
}
|
||||
"_imageNote": "2026-09-10: 본사 건물 외관은 그룹 사이트 oraclemedicalgroup.com/images/company_02.jpg (렌더링 이미지, 간판 오라클피부과의원). 같은 페이지의 다른 사진(화장품·모델·장비·전자차트)은 제외."
|
||||
}
|
||||
@ -1,33 +0,0 @@
|
||||
{
|
||||
"_comment": "병원별 고정값. 워커가 --name 등 인자보다 먼저 읽는다. factSheet 값은 뷰성형외과 홈페이지 표기와 대조해 확정한 것으로, 수작업 샘플 supporters/src/data/factSheet.json 과 같다. 홈 제목에서 상호를 추정하면 슬로건까지 들어가므로(\"안전을 최우선하는 뷰성형외과\") 여기서 고정한다.",
|
||||
"factSheet": {
|
||||
"name": "뷰성형외과의원",
|
||||
"shortName": "뷰성형외과",
|
||||
"kind": "성형외과",
|
||||
"areaLabel": "서울 강남 신논현역",
|
||||
"founded": "2005",
|
||||
"urlEn": "https://www.viewplasticsurgery.com"
|
||||
},
|
||||
"excludeImages": [
|
||||
"img02",
|
||||
"stemcell_about_01",
|
||||
"stemcell_count_01_mo",
|
||||
"view-abdominoplasty",
|
||||
"view-motiva-preserve-premium-standard",
|
||||
"view-motiva-preserve-motiva-smooth-silk"
|
||||
],
|
||||
"_excludeNote": "2026-09-12 육안 확인으로 제외. img02 는 밑선 절개 흉터의 1개월·6개월 경과를 나란히 놓은 전후 사진이고, stemcell_about_01 은 팔에 주사·드레싱을 하는 시술 행위 사진이며, view-abdominoplasty 두 장과 view-motiva-preserve-premium-standard 두 장은 '이미지 광고 모델' 표기가 있는 모델 신체 사진, stemcell_count_01_mo 는 장비 제조사의 성능 주장 그래픽이다. 파일명과 alt 에 신호가 없어 자동 분류가 전부 시설(clinic)로 넣었다. 2026-09-12 추가: view-motiva-preserve-motiva-smooth-silk 는 가슴·보형물 라인 일러스트다. 규칙상 시술 설명 일러스트로 허용되는 유형이지만 랜딩의 '상담 전 질문' 카드 썸네일로 붙어 주제가 어긋나고 첫 화면에 신체 그림이 노출된다(haewon 지적).",
|
||||
"site": {
|
||||
"heroImage": {
|
||||
"srcMatch": "view-building_pc",
|
||||
"alt": "뷰성형외과 건물 외관",
|
||||
"width": 886,
|
||||
"height": 885
|
||||
},
|
||||
"naverSiteVerification": "e47011473eef74d7b2c4f20e93f97a889cb699cc",
|
||||
"shareNameEn": "VIEW Plastic Surgery"
|
||||
},
|
||||
"_siteNote": "홈 히어로가 가슴 라인 일러스트로 잡혀 있었고 건물 사진의 alt 도 '가슴확대 시설 사진'으로 잘못 붙어 있었다. 건물 외관으로 고정한다.",
|
||||
"_verifyNote": "네이버 서치어드바이저 소유 확인 값(2026-09-14 발급, viewpl.webforai.kr 용). 사이트마다 값이 다르므로 다른 병원에 옮겨 쓰면 확인되지 않는다. 구글은 webforai.kr 도메인 속성을 DNS TXT 로 확인해 서브도메인 전부가 함께 확인된다.",
|
||||
"_shareNameEnNote": "shareNameEn: 링크 공유 이미지(og.jpg)의 영문 병원명. 출처: 병원 홈페이지 로고·본문 표기 VIEW PLASTIC SURGERY (근거 수집 OCR)"
|
||||
}
|
||||
@ -2,25 +2,5 @@
|
||||
"_comment": "병원별 고정값. 워커가 --youtube 등 인자보다 먼저 읽는다. haewon 결정 2026-09-08: 유튜브 기준 채널은 @wjwonjin (UC-EBM4e0VST1W_PQ0NzFMQg, 원장 설명 영상 736편). @wj2541(UCNBaQBCok5xIViCnrGdisoQ)은 브랜드 필름 채널이라 쓰지 않는다.",
|
||||
"youtube": "UC-EBM4e0VST1W_PQ0NzFMQg",
|
||||
"youtubeHandle": "@wjwonjin",
|
||||
"logo": "supporters/briefs/wonjin/logo-horizontal.svg",
|
||||
"factSheet": {
|
||||
"name": "원진성형외과의원",
|
||||
"shortName": "원진성형외과",
|
||||
"kind": "성형외과"
|
||||
},
|
||||
"_factSheetNote": "상호 고정. 홈 제목에서 추정하면 슬로건 '당신만 봅니다'가 상호가 되어 화면 전체와 네이버 지역검색(주변 안내 기준 좌표)까지 틀어진다. 근거: supporters-wonjin 기존 배포본 표기. kind 는 상호에 들어 있는 값이다. areaLabel·founded 는 근거를 확인하지 못해 비워 둔다.",
|
||||
"site": {
|
||||
"heroImage": {
|
||||
"srcMatch": "img_about_cover_03",
|
||||
"alt": "원진성형외과 글로벌 허브 리셉션과 대기 공간",
|
||||
"width": 1600,
|
||||
"height": 633
|
||||
},
|
||||
"naverSiteVerification": "5df40b24e88fd3fdf7ece9dd0fb69cf83439ad44",
|
||||
"shareNameEn": "WONJIN Plastic Surgery"
|
||||
},
|
||||
"_siteNote": "2026-09-12 haewon 지정. 랜딩 히어로를 img_about_cover_03(글로벌 허브 리셉션·대기 공간)으로 고정한다. 이전 값 img_about_cover_01 은 원장이 모니터를 가리키는 사진이라 글 카드와 겹쳐 보였다. alt 는 수집 원문 대신 무엇을 찍은 것인지만 밝히는 문구로 쓴다.",
|
||||
"_imageReviewNote": "2026-09-12 haewon 육안 검토: 원진 수집 이미지 66장 전부 사용 가능. 제외할 것 없음.",
|
||||
"_verifyNote": "네이버 서치어드바이저 소유 확인 값(2026-09-14 발급, wonjin.webforai.kr 용). 사이트마다 값이 다르므로 다른 병원에 옮겨 쓰면 확인되지 않는다.",
|
||||
"_shareNameEnNote": "shareNameEn: 링크 공유 이미지(og.jpg)의 영문 병원명. 출처: 병원 로고 표기 WJ WONJIN PLASTIC SURGERY (근거 수집 OCR)"
|
||||
"logo": "supporters/briefs/wonjin/logo-horizontal.svg"
|
||||
}
|
||||
|
||||
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 |
@ -1,164 +0,0 @@
|
||||
// 글마다 서로 다른 대표 이미지를 배정한다.
|
||||
// node scripts/assign_post_heroes.mjs --site <siteDir> [--dry]
|
||||
//
|
||||
// 왜 필요한가: PostCard 의 썸네일 순서는 "thumbnail → hero.src → 첫 영상 → 건물 사진 → 로고" 다.
|
||||
// 앞의 셋이 없으면 모든 글이 건물 사진 한 장으로 떨어져 목록에 같은 사진이 나란히 선다.
|
||||
// 글마다 thumbnail 이 있어도 값이 같으면 결과는 똑같다. 그래서 "비어 있는 글"과 "겹치는 글"을 함께 다시 배정한다.
|
||||
//
|
||||
// 규칙(docs/INFINITH_Supporters_Image_Rules.md §2)
|
||||
// - 글 히어로의 지정 용도는 시술 설명 일러스트(procedure)와 장비(equipment)다. 시설(clinic)은 그 다음으로 쓴다.
|
||||
// - 의료진 사진(doctors)은 쓰지 않는다. 저자·검토자 표시가 지정 용도이고, 글 대표 이미지로 쓰면 그 원장이 쓴 글로 읽힌다.
|
||||
// - site.heroImage·buildingImage 로 이미 쓰는 사진은 뺀다. 홈에서 보이는 사진이 목록에 또 서면 중복으로 읽힌다.
|
||||
// - alt 는 수집 원문을 그대로 쓰지 않는다. 원문 alt 는 술기 설명 문단이라 고객 화면에 낼 수 없다. 첫 구절만 줄여 쓴다.
|
||||
// - 이미지가 글보다 적으면 남는 글은 비워 둔다. 같은 사진을 다시 쓰면 처음 문제로 돌아간다.
|
||||
import { readFileSync, writeFileSync, existsSync, readdirSync } from 'node:fs';
|
||||
import { join, resolve } from 'node:path';
|
||||
|
||||
const args = process.argv.slice(2);
|
||||
const opt = (k, d) => { const i = args.indexOf(`--${k}`); return i >= 0 && args[i + 1] && !args[i + 1].startsWith('--') ? args[i + 1] : d; };
|
||||
const flag = (k) => args.includes(`--${k}`);
|
||||
const SITE = resolve(opt('site', '.'));
|
||||
const DRY = flag('dry');
|
||||
const readJson = (p) => JSON.parse(readFileSync(p, 'utf8'));
|
||||
|
||||
const DATA = join(SITE, 'src', 'data');
|
||||
const POSTS = join(SITE, 'src', 'content', 'posts');
|
||||
if (!existsSync(POSTS)) { console.log('글 폴더 없음 · 건너뜀'); process.exit(0); }
|
||||
const manifestPath = join(DATA, 'images.json');
|
||||
if (!existsSync(manifestPath)) { console.log('images.json 없음 · 건너뜀 (수집된 이미지가 없다)'); process.exit(0); }
|
||||
|
||||
const manifest = readJson(manifestPath);
|
||||
const site = existsSync(join(DATA, 'site.json')) ? readJson(join(DATA, 'site.json')) : {};
|
||||
const fact = existsSync(join(DATA, 'factSheet.json')) ? readJson(join(DATA, 'factSheet.json')) : {};
|
||||
const clinicName = fact.shortName || fact.name || '';
|
||||
|
||||
// ---------- 글 읽기 ----------
|
||||
const HERO_BLOCK = /^hero:\n(?:[ \t]+\S.*\n)+/m; // 블록 표기
|
||||
const HERO_FLOW = /^hero:[ \t]*\{.*\}[ \t]*\n/m; // 한 줄 표기
|
||||
const THUMB_LINE = /^thumbnail:[ \t]*.*\n/m;
|
||||
|
||||
const posts = readdirSync(POSTS).filter((f) => f.endsWith('.md')).sort().map((file) => {
|
||||
const path = join(POSTS, file);
|
||||
const text = readFileSync(path, 'utf8');
|
||||
const fm = (text.match(/^---\n([\s\S]*?)\n---/) ?? [, ''])[1];
|
||||
const thumb = (fm.match(/^thumbnail:[ \t]*["']?([^"'\n]+)/m)?.[1] ?? '').trim();
|
||||
const heroSrc = (fm.match(/^hero:[\s\S]*?src:[ \t]*["']?([^"'\n,}]+)/m)?.[1] ?? '').trim();
|
||||
return {
|
||||
file, path, text, fm,
|
||||
slug: file.replace(/\.md$/, ''),
|
||||
title: (fm.match(/^title:[ \t]*(.+)$/m)?.[1] ?? '').trim(),
|
||||
category: (fm.match(/^category:[ \t]*(\S+)/m)?.[1] ?? 'A').trim(),
|
||||
tags: (fm.match(/^tags:\n((?:[ \t]+-[ \t].+\n)+)/m)?.[1] ?? '').replace(/[ \t]+-[ \t]/g, ' '),
|
||||
// 카드에 실제로 쓰이는 값. PostCard 가 thumbnail 을 먼저 본다.
|
||||
current: thumb || heroSrc,
|
||||
};
|
||||
});
|
||||
|
||||
// 홈 히어로·건물 사진과 같은 사진을 쓰는 글도 다시 배정한다. 홈 맨 위 사진이 바로 아래 카드에 또 나오면 중복으로 읽힌다.
|
||||
const shown = new Set([site.heroImage?.src, site.buildingImage?.src].filter(Boolean));
|
||||
|
||||
// 겹치는 글은 앞의 한 편만 두고 나머지를 다시 배정한다.
|
||||
const seen = new Set(), dup = new Set();
|
||||
for (const p of posts) {
|
||||
if (!p.current) continue;
|
||||
if (seen.has(p.current)) dup.add(p.slug); else seen.add(p.current);
|
||||
}
|
||||
const kept = new Set(posts.filter((p) => p.current && !dup.has(p.slug) && !shown.has(p.current)).map((p) => p.current));
|
||||
const targets = posts.filter((p) => !p.current || dup.has(p.slug) || shown.has(p.current));
|
||||
|
||||
// ---------- 후보 풀 ----------
|
||||
const inGallery = new Set([...(site.insideGallery ?? []), ...(site.clinicGallery ?? [])].map((g) => g?.src).filter(Boolean));
|
||||
// 글 분류(A 병원 소개·B 방문·예약·C 선택 기준·D 시술 정보·E 안전·마취)마다 어울리는 이미지 유형이 다르다.
|
||||
// 방문 안내에 시술 일러스트가 붙으면 주제가 어긋난다. 분류별로 선호 순서를 둔다.
|
||||
const PREF = {
|
||||
A: ['clinic', 'equipment', 'procedure'],
|
||||
B: ['clinic', 'equipment', 'procedure'],
|
||||
C: ['clinic', 'procedure', 'equipment'],
|
||||
D: ['procedure', 'equipment', 'clinic'],
|
||||
E: ['equipment', 'clinic', 'procedure'],
|
||||
F: ['clinic', 'procedure', 'equipment'],
|
||||
G: ['clinic', 'procedure', 'equipment'],
|
||||
};
|
||||
const CATS = new Set(['procedure', 'equipment', 'clinic']);
|
||||
// 수집한 alt 가 효과·결과를 주장하면 그 문장을 쓰지 않는다. 카드와 글 머리의 alt 로 나가면
|
||||
// 의료법 56조 2항 8호(객관적 사실 과장) 대상이 된다. 이미지 자체는 문제가 없으므로
|
||||
// 빼지 않고 무엇을 찍은 것인지만 밝히는 중립 문구로 바꾼다.
|
||||
const base = (manifest.items ?? []).filter((i) => CATS.has(i.category) && !shown.has(i.src) && !kept.has(i.src));
|
||||
// 갤러리에 실린 사진은 뺀다. 홈에서 글 목록 바로 아래가 갤러리라, 카드 마지막 장과 갤러리 첫 장이
|
||||
// 같으면 나란히 두 번 보인다. 다만 이렇게 빼서 글 수를 못 채우면 더 나빠진다. 못 채운 글은 건물 사진으로
|
||||
// 떨어져 같은 사진이 여러 장 생기기 때문이다. 그래서 전부 채울 수 있을 때만 뺀다.
|
||||
const notInGallery = base.filter((i) => !inGallery.has(i.src));
|
||||
const pool = (notInGallery.length >= targets.length ? notInGallery : base).map((i) => ({ ...i, inGallery: inGallery.has(i.src) }));
|
||||
|
||||
if (!targets.length) { console.log(`글 ${posts.length}편 · 겹치는 대표 이미지 없음 · 배정 없음`); process.exit(0); }
|
||||
if (!pool.length) { console.log(`대상 ${targets.length}편이지만 쓸 수 있는 이미지가 없음 (의료진 사진은 글 히어로로 쓰지 않는다)`); process.exit(0); }
|
||||
|
||||
// ---------- 문구 ----------
|
||||
const EFFICACY = /개선|완화|제거|재생|촉진|강화|효과|없애|줄여|늘려|맑아|밝아|탄력|안전하게|빠른\s*회복|통증\s*없/;
|
||||
// 수집기의 분류를 그대로 믿지 않는다. 일러스트가 시설(clinic)로 들어오는 경우가 있어
|
||||
// 유형을 단정하는 문구 대신 출처만 밝힌다.
|
||||
function shortAlt(item) {
|
||||
const raw = String(item.alt || '').replace(/\s+/g, ' ').trim();
|
||||
const fallback = [clinicName, '홈페이지 이미지'].filter(Boolean).join(' ');
|
||||
if (!raw || EFFICACY.test(raw)) return fallback;
|
||||
let s = raw.split(/(?<=[.!?])\s|(?=\s\d\s)/)[0].replace(/\s*\d+\s*$/, '').trim();
|
||||
if (s.length > 60) s = s.slice(0, 58).replace(/\s\S*$/, '') + '…';
|
||||
return EFFICACY.test(s) ? fallback : (s || fallback);
|
||||
}
|
||||
function caption(item) {
|
||||
const t = String(item.page?.title || '').replace(/\s+/g, ' ').trim();
|
||||
const generic = !t || t === clinicName || t.includes(clinicName) || t.length < 3;
|
||||
const where = generic ? `${clinicName} 홈페이지`.trim() : `${clinicName} 홈페이지 ${t}`.trim();
|
||||
return `출처: ${where} (${item.capturedAt || manifest.capturedAt} 캡처)`;
|
||||
}
|
||||
|
||||
// ---------- 배정 ----------
|
||||
const words = (s) => String(s || '').toLowerCase().match(/[가-힣]{2,}|[a-z]{3,}/g) ?? [];
|
||||
function score(post, item) {
|
||||
const a = new Set(words(`${post.title} ${post.tags} ${post.slug}`));
|
||||
let n = 0;
|
||||
for (const w of words(`${item.alt} ${item.src} ${item.page?.url}`)) if (a.has(w)) n += 1;
|
||||
return n;
|
||||
}
|
||||
const prefRank = (post, item) => {
|
||||
const order = PREF[post.category] ?? PREF.A;
|
||||
const i = order.indexOf(item.category);
|
||||
return (i < 0 ? order.length : i) + (item.inGallery ? 0.5 : 0);
|
||||
};
|
||||
// 유형이 맞는 것을 먼저 쓰고, 같은 유형 안에서 낱말이 겹치는 짝을 먼저 확정한다.
|
||||
const pairs = [];
|
||||
for (const p of targets) for (const i of pool) pairs.push({ p, i, s: score(p, i), r: prefRank(p, i) });
|
||||
pairs.sort((a, b) => (a.r - b.r) || (b.s - a.s) || a.p.slug.localeCompare(b.p.slug));
|
||||
|
||||
const usedPost = new Set(), usedImg = new Set(), chosen = new Map();
|
||||
for (const { p, i, s } of pairs) {
|
||||
if (usedPost.has(p.slug) || usedImg.has(i.src)) continue;
|
||||
usedPost.add(p.slug); usedImg.add(i.src); chosen.set(p.slug, { item: i, s });
|
||||
}
|
||||
|
||||
// ---------- 쓰기 ----------
|
||||
let wrote = 0;
|
||||
for (const p of targets) {
|
||||
const pick = chosen.get(p.slug);
|
||||
if (!pick) { console.log(` ${p.slug} ← (배정 없음 · 남은 이미지 부족)`); continue; }
|
||||
const { item } = pick;
|
||||
const block = `hero:\n src: ${item.src}\n alt: ${JSON.stringify(shortAlt(item))}\n caption: ${JSON.stringify(caption(item))}\nthumbnail: ${item.src}\n`;
|
||||
let next = p.text;
|
||||
const hadHero = HERO_BLOCK.test(next) || HERO_FLOW.test(next);
|
||||
next = next.replace(HERO_BLOCK, '').replace(HERO_FLOW, '').replace(THUMB_LINE, '');
|
||||
if (hadHero || p.current) {
|
||||
// 있던 자리를 대신한다. description 다음 줄에 넣어 자리를 고정한다.
|
||||
const anchor = next.match(/^description:[ \t]*.+$/m);
|
||||
if (!anchor) { console.warn(` ! ${p.file}: description 줄이 없어 건너뜀`); continue; }
|
||||
next = next.replace(anchor[0], `${anchor[0]}\n${block.trimEnd()}`);
|
||||
} else {
|
||||
const anchor = next.match(/^description:[ \t]*.+$/m);
|
||||
if (!anchor) { console.warn(` ! ${p.file}: description 줄이 없어 건너뜀`); continue; }
|
||||
next = next.replace(anchor[0], `${anchor[0]}\n${block.trimEnd()}`);
|
||||
}
|
||||
if (!DRY) writeFileSync(p.path, next);
|
||||
wrote += 1;
|
||||
const why = dup.has(p.slug) ? '겹침 해소' : '새로 배정';
|
||||
console.log(` ${p.slug} [${p.category}] ← ${item.src.replace(/^\/img\//, '')} (${item.category} · ${why}${pick.s ? ` · 낱말 일치 ${pick.s}` : ''})`);
|
||||
}
|
||||
console.log(`대표 이미지 ${wrote}편 배정 / 대상 ${targets.length}편 · 후보 ${pool.length}장 · 그대로 둔 글 ${posts.length - targets.length}편${DRY ? ' · dry' : ''}`);
|
||||
@ -1,180 +0,0 @@
|
||||
// 수집 이미지 육안 검토 요청서를 만든다.
|
||||
// node scripts/build_image_review.mjs --out <파일.html> --site <id>=<siteDir> [--site ...]
|
||||
//
|
||||
// 왜 필요한가: 이미지 분류는 파일명·alt·페이지 유형으로 추정한다. 그런데 전후 사진·광고 모델 신체 사진·
|
||||
// 시술 행위 사진은 파일명이 img02 처럼 아무 신호도 주지 않는다. 2026-09-12 뷰성형외과에서 91장을
|
||||
// 파일명·alt 로 검사했을 때 걸린 것이 0장이었고, 눈으로 보니 12장 중 6장이 금지 유형이었다.
|
||||
// 그래서 이 단계는 사람 게이트로 둔다. 이 페이지는 그 판정을 한 화면에서 하고,
|
||||
// 결과를 briefs/<clinic>/clinic.json 의 excludeImages 에 넣을 수 있는 형태로 돌려준다.
|
||||
import { readFileSync, writeFileSync, existsSync, copyFileSync, mkdirSync, readdirSync } from 'node:fs';
|
||||
import { join, resolve, dirname, basename } from 'node:path';
|
||||
|
||||
const args = process.argv.slice(2);
|
||||
const opt = (k, d) => { const i = args.indexOf(`--${k}`); return i >= 0 && args[i + 1] && !args[i + 1].startsWith('--') ? args[i + 1] : d; };
|
||||
const sites = args.reduce((acc, a, i) => (args[i - 1] === '--site' ? [...acc, a] : acc), []);
|
||||
const OUT = resolve(opt('out', 'image-review.html'));
|
||||
const ASSETS = join(dirname(OUT), 'image-review-files');
|
||||
const ROOT = resolve(dirname(new URL(import.meta.url).pathname), '..', '..');
|
||||
const readJson = (p) => JSON.parse(readFileSync(p, 'utf8'));
|
||||
const esc = (s) => String(s ?? '').replace(/[&<>"]/g, (c) => ({ '&': '&', '<': '<', '>': '>', '"': '"' }[c]));
|
||||
// 파일명에서 수집기가 붙인 해시를 떼어 excludeImages 에 쓸 접두어를 만든다.
|
||||
const prefixOf = (src) => basename(src).replace(/-[0-9a-f]{10}\.\w+$/, '').replace(/\.\w+$/, '');
|
||||
|
||||
const blocks = [];
|
||||
for (const spec of sites) {
|
||||
const [id, dir] = spec.split('=');
|
||||
const SITE = resolve(dir);
|
||||
const DATA = join(SITE, 'src', 'data');
|
||||
const fact = existsSync(join(DATA, 'factSheet.json')) ? readJson(join(DATA, 'factSheet.json')) : {};
|
||||
const briefPath = join(ROOT, 'supporters', 'briefs', id, 'clinic.json');
|
||||
const already = existsSync(briefPath) ? (readJson(briefPath).excludeImages ?? []) : [];
|
||||
if (!existsSync(join(DATA, 'images.json'))) { blocks.push({ id, clinic: fact.shortName || id, items: [], already, note: '수집된 이미지가 없습니다. 검토할 것이 없습니다.' }); continue; }
|
||||
|
||||
const manifest = readJson(join(DATA, 'images.json'));
|
||||
const site = existsSync(join(DATA, 'site.json')) ? readJson(join(DATA, 'site.json')) : {};
|
||||
|
||||
// 화면 어디에 쓰이는지. 실제로 노출되는 것부터 보게 한다.
|
||||
const usage = new Map();
|
||||
const mark = (src, where) => { if (!src) return; usage.set(src, [...(usage.get(src) ?? []), where]); };
|
||||
mark(site.heroImage?.src, '홈 히어로');
|
||||
mark(site.buildingImage?.src, '건물 사진');
|
||||
for (const g of site.insideGallery ?? []) mark(g?.src, '내부 갤러리');
|
||||
for (const g of site.clinicGallery ?? []) mark(g?.src, '병원 갤러리');
|
||||
const POSTS = join(SITE, 'src', 'content', 'posts');
|
||||
if (existsSync(POSTS)) for (const f of readdirSync(POSTS).filter((x) => x.endsWith('.md'))) {
|
||||
const t = readFileSync(join(POSTS, f), 'utf8');
|
||||
for (const m of t.matchAll(/(?:thumbnail:|src:)[ \t]*["']?(\/img\/[^\s"',}]+)/g)) mark(m[1], `글 카드 ${f.replace(/\.md$/, '')}`);
|
||||
}
|
||||
|
||||
mkdirSync(join(ASSETS, id), { recursive: true });
|
||||
const items = (manifest.items ?? []).map((it) => {
|
||||
const from = join(SITE, 'public', it.src.replace(/^\//, ''));
|
||||
const name = `${it.category}__${basename(it.src)}`;
|
||||
if (existsSync(from)) copyFileSync(from, join(ASSETS, id, name));
|
||||
return { ...it, name, prefix: prefixOf(it.src), used: usage.get(it.src) ?? [] };
|
||||
}).sort((a, b) => (b.used.length > 0) - (a.used.length > 0) || a.category.localeCompare(b.category) || a.src.localeCompare(b.src));
|
||||
blocks.push({ id, clinic: fact.shortName || id, items, already, counts: manifest.counts ?? {}, capturedAt: manifest.capturedAt });
|
||||
}
|
||||
|
||||
const style = `
|
||||
:root{--ink:#1D0024;--muted:#5b5570;--line:#e6e2ee;--bad:#B3261E;--live:#7A4B00;--bg:#fbfafd;--accent:#4F1DA1}
|
||||
*{box-sizing:border-box}body{margin:0;font:15px/1.65 Pretendard,-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;color:var(--ink);background:var(--bg);word-break:keep-all}
|
||||
.wrap{max-width:1200px;margin:0 auto;padding:2.4rem 20px 5rem}
|
||||
h1{font-size:1.75rem;margin:0 0 .5rem}h2{font-size:1.3rem;margin:2.8rem 0 .2rem;padding-top:1.5rem;border-top:1px solid var(--line)}
|
||||
.lede{color:var(--muted);margin:0 0 1.5rem;max-width:70ch}
|
||||
.ask{background:#fff;border:1px solid var(--line);border-left:4px solid var(--accent);border-radius:10px;padding:1rem 1.2rem;margin:1.2rem 0 1.8rem}
|
||||
.ask p{margin:.3rem 0}
|
||||
.sum{background:#fff;border:1px solid var(--line);border-radius:12px;padding:1rem 1.2rem;margin:1.2rem 0}
|
||||
table{border-collapse:collapse;width:100%;font-size:.87rem}th,td{text-align:left;padding:.5rem .6rem;border-bottom:1px solid var(--line);vertical-align:top}
|
||||
.grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(248px,1fr));gap:1rem;margin-top:1rem}
|
||||
.card{background:#fff;border:1px solid var(--line);border-radius:12px;overflow:hidden;display:flex;flex-direction:column;position:relative}
|
||||
.card.live{border-color:#E0B000;box-shadow:0 0 0 2px rgba(224,176,0,.16)}
|
||||
.card.out{opacity:.45}
|
||||
.card img{width:100%;height:180px;object-fit:contain;background:#f1eff6;display:block}
|
||||
.meta{padding:.7rem .8rem .9rem;font-size:.8rem;flex:1;display:flex;flex-direction:column;gap:.3rem}
|
||||
.cat{font-weight:700;font-size:.68rem;letter-spacing:.05em;text-transform:uppercase;color:var(--muted)}
|
||||
.fn{font-family:ui-monospace,Menlo,monospace;font-size:.7rem;word-break:break-all;color:var(--muted)}
|
||||
.alt{color:var(--ink)}
|
||||
.use{font-size:.72rem;color:var(--live);font-weight:700}
|
||||
.pick{display:flex;gap:.4rem;align-items:center;padding:.55rem .8rem;border-top:1px solid var(--line);background:#fdfcff;font-size:.85rem;cursor:pointer}
|
||||
.pick input{width:16px;height:16px;accent-color:var(--bad);cursor:pointer}
|
||||
.out-box{position:sticky;bottom:0;background:#fff;border:1px solid var(--line);border-radius:12px;padding:1rem 1.2rem;margin-top:1.6rem;box-shadow:0 -4px 18px rgba(29,0,36,.07)}
|
||||
textarea{width:100%;min-height:96px;font-family:ui-monospace,Menlo,monospace;font-size:.78rem;border:1px solid var(--line);border-radius:8px;padding:.6rem;background:#fbfafd;color:var(--ink)}
|
||||
button{font:inherit;font-size:.85rem;padding:.45rem .9rem;border-radius:8px;border:1px solid var(--accent);background:var(--accent);color:#fff;cursor:pointer}
|
||||
code{background:#f1eff6;padding:.1rem .35rem;border-radius:4px;font-size:.86em}
|
||||
.badge{display:inline-block;font-size:.7rem;font-weight:700;padding:.1rem .4rem;border-radius:5px;background:#f1eff6;color:var(--muted);margin-left:.3rem}
|
||||
.none{color:var(--muted);padding:1rem 0}
|
||||
@media (max-width:640px){.grid{grid-template-columns:repeat(auto-fill,minmax(150px,1fr))}.card img{height:130px}}
|
||||
`;
|
||||
|
||||
const body = blocks.map((b) => {
|
||||
const head = `<h2>${esc(b.clinic)} <span style="font-weight:400;color:var(--muted);font-size:.88rem">${esc(Object.entries(b.counts ?? {}).map(([k, v]) => `${k} ${v}`).join(' · '))}${b.capturedAt ? ` · ${esc(b.capturedAt)} 수집` : ''}</span></h2>`;
|
||||
if (!b.items.length) return `${head}<p class="none">${esc(b.note ?? '수집된 이미지가 없습니다.')}</p>`;
|
||||
const liveCount = b.items.filter((i) => i.used.length).length;
|
||||
const cards = b.items.map((it) => `
|
||||
<figure class="card${it.used.length ? ' live' : ''}" data-prefix="${esc(it.prefix)}">
|
||||
<img src="image-review-files/${esc(b.id)}/${esc(it.name)}" alt="${esc(String(it.alt).slice(0, 120))}" loading="lazy">
|
||||
<figcaption class="meta">
|
||||
<span class="cat">${esc(it.category)}${it.used.length ? '<span class="badge">화면에 노출</span>' : ''}</span>
|
||||
<div class="fn">${esc(basename(it.src))}</div>
|
||||
<div class="alt">${esc(String(it.alt).slice(0, 100))}${String(it.alt).length > 100 ? '…' : ''}</div>
|
||||
${it.used.length ? `<div class="use">${esc(it.used.join(' · '))}</div>` : ''}
|
||||
</figcaption>
|
||||
<label class="pick"><input type="checkbox" data-clinic="${esc(b.id)}" data-prefix="${esc(it.prefix)}"> 이 이미지 빼기</label>
|
||||
</figure>`).join('');
|
||||
const alreadyRow = b.already.length
|
||||
? `<p style="margin:.6rem 0 0;color:var(--muted);font-size:.85rem">이미 빠져 있는 것 ${b.already.length}건: <code>${esc(b.already.join('</code>, <code>'))}</code></p>`
|
||||
: '';
|
||||
return `${head}
|
||||
<p style="margin:.4rem 0 0;color:var(--muted)">${b.items.length}장 · 이 중 <strong style="color:var(--live)">${liveCount}장이 지금 화면에 나갑니다</strong>(노란 테두리). 이것부터 보시면 됩니다.${alreadyRow ? '' : ''}</p>
|
||||
${alreadyRow}
|
||||
<div class="grid">${cards}</div>`;
|
||||
}).join('\n');
|
||||
|
||||
const html = `<!doctype html><html lang="ko"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
|
||||
<title>서포터즈 수집 이미지 육안 검토 요청</title><style>${style}</style></head><body><div class="wrap">
|
||||
<h1>서포터즈 수집 이미지 육안 검토 요청</h1>
|
||||
<p class="lede">병원 홈페이지에서 자동으로 모은 이미지입니다. 어떤 이미지를 빼야 하는지는 파일명이나 설명문으로 가려지지 않아 눈으로 보셔야 합니다.</p>
|
||||
|
||||
<div class="ask">
|
||||
<p><strong>부탁드리는 것</strong></p>
|
||||
<p>아래 표의 유형에 해당하는 이미지를 찾아 카드 아래 <strong>"이 이미지 빼기"</strong>를 체크해 주세요. 다 고르시면 맨 아래 상자에 제외 목록이 만들어집니다. 그 값을 알려주시면 다음 빌드부터 빠집니다.</p>
|
||||
<p><strong>노란 테두리가 지금 화면에 나가는 이미지</strong>입니다. 시간이 없으시면 그것만 보셔도 됩니다.</p>
|
||||
</div>
|
||||
|
||||
<div class="sum">
|
||||
<p style="margin:.2rem 0 .7rem"><strong>빼야 하는 유형</strong> · 근거 <code>docs/INFINITH_Supporters_Image_Rules.md</code></p>
|
||||
<table>
|
||||
<tr><th style="width:22%">유형</th><th style="width:34%">어떻게 생겼나</th><th>근거</th></tr>
|
||||
<tr><td>전후 사진 · 경과 비교</td><td>같은 부위를 두 시점으로 나란히 놓은 사진. "수술 후 1개월 / 6개월" 같은 글자가 박혀 있기도 합니다</td><td>의료광고 심의 기준. 동일 조건 촬영·무보정·환자 동의·부작용 병기 네 조건을 자동으로 확인할 수 없습니다</td></tr>
|
||||
<tr><td>환자 · 광고 모델 신체 사진</td><td>얼굴이나 몸이 나오는 인물 사진. 구석에 "이미지 광고 모델" 표기가 있는 경우가 많습니다</td><td>의료법 56조 2항 2호 · 개인정보보호법 · 초상권</td></tr>
|
||||
<tr><td>시술 행위 장면</td><td>주사·절개·드레싱처럼 시술하는 손과 환자 신체가 함께 나오는 사진. 수술실 공간만 찍은 사진은 괜찮습니다</td><td>의료법 56조 2항 6호</td></tr>
|
||||
<tr><td>효과 · 성능 주장 그래픽</td><td>"개선", "재생", 숫자와 화살표로 성능을 내세운 제조사 홍보 이미지</td><td>의료법 56조 2항 8호</td></tr>
|
||||
<tr><td>인증 · 수상 마크</td><td>상장, 어워드 로고, 인증 배지</td><td>의료법 56조 2항 14호</td></tr>
|
||||
<tr><td>타 병원 비교</td><td>다른 병원과 견주는 표나 그래프</td><td>의료법 56조 2항 4호</td></tr>
|
||||
</table>
|
||||
<p style="margin:.9rem 0 0;color:var(--muted)">판단이 서지 않으면 빼는 쪽으로 봐 주세요. 뺀 이미지는 병원이 조건을 서면으로 확인해 주면 다시 넣을 수 있습니다.</p>
|
||||
</div>
|
||||
|
||||
${body}
|
||||
|
||||
<div class="out-box">
|
||||
<p style="margin:.1rem 0 .5rem"><strong>제외 목록</strong> <span style="color:var(--muted);font-size:.85rem">체크하면 여기에 쌓입니다. 이 내용을 그대로 알려주시면 됩니다.</span></p>
|
||||
<textarea id="out" readonly placeholder="아직 고른 것이 없습니다."></textarea>
|
||||
<p style="margin:.6rem 0 0"><button id="copy">복사</button> <span id="msg" style="color:var(--muted);font-size:.85rem;margin-left:.5rem"></span></p>
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
(function () {
|
||||
var out = document.getElementById('out'), msg = document.getElementById('msg');
|
||||
function refresh() {
|
||||
var by = {};
|
||||
document.querySelectorAll('input[type=checkbox]').forEach(function (c) {
|
||||
var card = c.closest('.card');
|
||||
if (card) card.classList.toggle('out', c.checked);
|
||||
if (!c.checked) return;
|
||||
(by[c.dataset.clinic] = by[c.dataset.clinic] || []).push(c.dataset.prefix);
|
||||
});
|
||||
var keys = Object.keys(by);
|
||||
out.value = keys.length ? keys.map(function (k) {
|
||||
return k + ': ' + JSON.stringify(by[k].filter(function (v, i, a) { return a.indexOf(v) === i; }));
|
||||
}).join('\\n') : '';
|
||||
}
|
||||
document.addEventListener('change', function (e) { if (e.target.type === 'checkbox') refresh(); });
|
||||
document.getElementById('copy').addEventListener('click', function () {
|
||||
if (!out.value) { msg.textContent = '고른 것이 없습니다.'; return; }
|
||||
out.select();
|
||||
try { document.execCommand('copy'); msg.textContent = '복사했습니다.'; }
|
||||
catch (e) { msg.textContent = '직접 선택해 복사해 주세요.'; }
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
</body></html>`;
|
||||
|
||||
mkdirSync(dirname(OUT), { recursive: true });
|
||||
writeFileSync(OUT, html);
|
||||
const total = blocks.reduce((n, b) => n + b.items.length, 0);
|
||||
const live = blocks.reduce((n, b) => n + b.items.filter((i) => i.used.length).length, 0);
|
||||
console.log(`검토 요청서 → ${OUT}`);
|
||||
for (const b of blocks) console.log(` ${b.clinic}: ${b.items.length}장 (화면 노출 ${b.items.filter((i) => i.used.length).length}장 · 이미 제외 ${b.already.length}건)`);
|
||||
console.log(`합계 ${total}장 · 화면 노출 ${live}장`);
|
||||
@ -36,14 +36,9 @@ function walk(dir, out = []) {
|
||||
return out;
|
||||
}
|
||||
|
||||
// 홈페이지 40자 일치 검사에서 뺄 블록. 둘 다 출처를 밝히고 병원 원문을 그대로 옮기는 자리라 설계상 일치한다.
|
||||
// #recovery-quote : /stay 의 "시술 후 관리 안내" 인용(출처 링크·확인일 표시)
|
||||
// .doctor-strip : 글의 검토자 표시줄. 원장 이력을 병원 의료진 페이지에서 옮기고 프로필 링크를 단다.
|
||||
// 다른 말로 바꿔 쓰면 이력이 사실과 달라지므로 원문 그대로 두는 것이 맞다.
|
||||
const QUOTED_BLOCKS = ['#recovery-quote', '.doctor-strip'];
|
||||
function withoutQuotedBlocks(html) {
|
||||
function withoutRecoveryQuote(html) {
|
||||
const root = parseHtml(html);
|
||||
for (const sel of QUOTED_BLOCKS) root.querySelectorAll(sel).forEach((el) => el.remove());
|
||||
root.querySelectorAll('#recovery-quote').forEach((el) => el.remove());
|
||||
return root.toString();
|
||||
}
|
||||
|
||||
@ -78,11 +73,9 @@ for (const f of files) {
|
||||
const h1 = (html.match(/<h1\b/g) || []).length; if (h1 !== 1) add(rel, [{ level: R.E, code: 'H1_COUNT', msg: `H1 ${h1}개 (1개여야 함)` }]);
|
||||
// 금칙 표현·홈페이지 중복·운영자 어휘·제목 화법
|
||||
add(rel, R.checkBannedBody(text));
|
||||
// 홈페이지 40자 연속 일치(§3-11)는 글이 홈페이지 문장을 베끼는 것을 막는 규칙이다.
|
||||
// 출처를 밝힌 인용 블록(QUOTED_BLOCKS)은 설계상 일치하므로 빼고 본다.
|
||||
// 금칙어·운영자 어휘 검사는 인용 블록에도 그대로 적용한다.
|
||||
const quoted = /id="recovery-quote"|class="[^"]*doctor-strip/.test(html);
|
||||
add(rel, R.checkHomeDuplicate(quoted ? R.stripHtml(withoutQuotedBlocks(html)) : text, HOME_TEXT));
|
||||
// 홈페이지 40자 연속 일치(§3-11)는 글이 홈페이지 문장을 베끼는 것을 막는 규칙이다. /stay 의 "시술 후 관리 안내"(#recovery-quote)는
|
||||
// 출처 링크·확인일을 붙인 병원 원문 인용이라 설계상 일치하므로 그 블록만 빼고 본다. 금칙어·운영자 어휘 검사는 인용 블록에도 그대로 적용한다.
|
||||
add(rel, R.checkHomeDuplicate(/id="recovery-quote"/.test(html) ? R.stripHtml(withoutRecoveryQuote(html)) : text, HOME_TEXT));
|
||||
add(rel, R.checkOperatorVocab(text, rel));
|
||||
add(rel, R.checkHeadingEndings(html));
|
||||
// §12-2 (1) 글 첫 부분 지원 고지. site.sponsorNotice 가 켜져 있으면 error, 아니면 warn
|
||||
|
||||
@ -45,17 +45,9 @@ pkg.name = 'supporters-template';
|
||||
writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + '\n');
|
||||
|
||||
// 남은 병원 고유 문자열 검사
|
||||
// 상호뿐 아니라 지명·건물 같은 병원 고유 사실도 잡는다. 상호만 찾으면
|
||||
// "Sinnonhyeon Station Exit 3" 처럼 지명으로 적힌 사실이 그대로 템플릿에 남는다.
|
||||
const CLINIC_RE = /뷰성형외과|viewclinic|ViewclinicKR|안성민|o2oteam|View Plastic|Sinnonhyeon|Bongeunsa|Gimpo|신논현|봉은사|19-storey/;
|
||||
const CLINIC_RE = /뷰성형외과|viewclinic|ViewclinicKR|안성민|o2oteam/;
|
||||
const leaks = [];
|
||||
const walk = (d) => { for (const f of readdirSync(d, { withFileTypes: true })) { const p = join(d, f.name); if (f.isDirectory()) { if (f.name !== 'node_modules') walk(p); } else if (/\.(astro|ts|mjs|json|css)$/.test(f.name) && !p.endsWith('export_template.mjs') && !p.includes('/scripts/gate/fixtures/') && CLINIC_RE.test(readFileSync(p, 'utf8'))) leaks.push(relative(DEST, p)); } };
|
||||
walk(DEST);
|
||||
// 문장을 그리는 코드에 병원 고유 사실이 박히면 다른 병원 화면에 틀린 말이 나가므로 내보내기를 멈춘다.
|
||||
// 데이터(src/data)는 워커가 병원마다 갈아끼우고 빌드 도구(scripts)는 고객이 보지 않으므로 알림만 한다.
|
||||
const RENDER_RE = /^src\/(pages|layouts|components|lib)\//;
|
||||
const blocking = leaks.filter((f) => RENDER_RE.test(f));
|
||||
const notes = leaks.filter((f) => !RENDER_RE.test(f));
|
||||
if (notes.length) console.log('참고 · 병원 이름이 남은 파일(고객 화면 아님):\n ' + notes.join('\n '));
|
||||
if (blocking.length) { console.error('고객 화면 코드에 병원 고유 사실이 남았습니다. 데이터에서 읽도록 고치세요:\n ' + blocking.join('\n ')); process.exit(1); }
|
||||
if (leaks.length) { console.error('병원 고유 문자열이 남은 파일:\n ' + leaks.join('\n ')); process.exit(1); }
|
||||
console.log(`템플릿 내보내기 완료: ${DEST}${existsSync(join(DEST, 'node_modules')) ? '' : ' (npm ci 필요)'}`);
|
||||
|
||||
@ -17,7 +17,9 @@
|
||||
"a",
|
||||
"a",
|
||||
"a",
|
||||
"a.lang-switch",
|
||||
"div.langswitch",
|
||||
"a.on",
|
||||
"a",
|
||||
"a.cta",
|
||||
"main",
|
||||
"section.hero-light",
|
||||
@ -258,7 +260,6 @@
|
||||
"a",
|
||||
"a",
|
||||
"a",
|
||||
"a",
|
||||
"div",
|
||||
"p",
|
||||
"img",
|
||||
@ -289,7 +290,9 @@
|
||||
"a",
|
||||
"a",
|
||||
"a",
|
||||
"a.lang-switch",
|
||||
"div.langswitch",
|
||||
"a.on",
|
||||
"a",
|
||||
"a.cta",
|
||||
"div.backbar-wrap",
|
||||
"div.backbar.wrap-wide",
|
||||
@ -487,7 +490,6 @@
|
||||
"a",
|
||||
"a",
|
||||
"a",
|
||||
"a",
|
||||
"div",
|
||||
"p",
|
||||
"img",
|
||||
@ -518,7 +520,9 @@
|
||||
"a",
|
||||
"a",
|
||||
"a",
|
||||
"a.lang-switch",
|
||||
"div.langswitch",
|
||||
"a.on",
|
||||
"a",
|
||||
"a.cta",
|
||||
"div.backbar-wrap",
|
||||
"div.backbar.wrap-wide",
|
||||
@ -742,7 +746,6 @@
|
||||
"a",
|
||||
"a",
|
||||
"a",
|
||||
"a",
|
||||
"div",
|
||||
"p",
|
||||
"img",
|
||||
|
||||
@ -1,25 +0,0 @@
|
||||
{
|
||||
"_comment": "영어 페이지용 병원 사실. 값의 원본은 factSheet.json 이고 여기에는 영어 표기만 둔다. 비어 있는 값은 영어 화면에서 해당 조각을 렌더하지 않는다. 지어내지 않는다.",
|
||||
"name": "",
|
||||
"shortName": "",
|
||||
"kind": "",
|
||||
"areaLabel": "",
|
||||
"address": {
|
||||
"full": "",
|
||||
"mapQuery": ""
|
||||
},
|
||||
"transit": "",
|
||||
"airport": "",
|
||||
"airportShort": "",
|
||||
"parking": "",
|
||||
"hoursDays": {
|
||||
"월·화·수·목": "",
|
||||
"금": "",
|
||||
"토": "",
|
||||
"일·공휴일": ""
|
||||
},
|
||||
"hoursClosed": "",
|
||||
"building": "",
|
||||
"arrival": "",
|
||||
"specialties": []
|
||||
}
|
||||