From 2eb3d5cd06fc540ec9c67395f2594b1a8045a532 Mon Sep 17 00:00:00 2001 From: Haewon Kam Date: Wed, 9 Sep 2026 10:53:32 +0900 Subject: [PATCH 01/17] =?UTF-8?q?feat(sentiment):=20=EA=B0=90=EC=84=B1=20?= =?UTF-8?q?=ED=8C=90=EC=A0=95=20=EA=B8=B0=EC=A4=80=ED=91=9C=20v0.3=20?= =?UTF-8?q?=E2=80=94=201=EC=B0=A8=20=EB=B3=B4=EC=A0=95=20=EB=B0=98?= =?UTF-8?q?=EC=98=81,=20=EA=B2=B0=EC=A0=95=EB=A1=A0=EC=A0=81=20=EC=96=B8?= =?UTF-8?q?=EA=B8=89=20=ED=83=90=EC=A7=80=EA=B8=B0,=20=EC=9B=94=EA=B0=84?= =?UTF-8?q?=20=EC=8B=A4=EC=B8=A1=20=EC=B2=B4=EC=9D=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit haewon 보정 라벨 29건으로 1차 보정을 마쳤다. sentiment 일치율이 75%로 기준선 80%에 미달해 불일치 7건에서 규칙 R1~R5를 뽑아 기준표와 판정기 프롬프트에 넣었다. 규칙 (기준표 §2) - R1 결론 단락이 개별 항목보다 우선한다 - R2 mixed 는 긍정과 부정이 대등하게 병존할 때만 - R3 "후기 수가 많다"는 규모 서술이며 긍정 평가가 아니다 - R4 평가 축이 있는 목록에 포함되면 positive - R5 강점 서술은 출처가 병원 홍보라도 positive R4-a(목록에서 결론이 대상 병원을 빼면 positive 아님)와 R6(결론 없는 카탈로그는 neutral)은 시험 투입 후 철회했다. 일치율이 93%에서 89%로 떨어졌다. 하나 고치면 하나 깨진다. n=29 에서는 과적합이므로 R1~R5 에서 멈춘다. 결정론적 언급 탐지기 (기준표 §2-1, mention_kind) sentiment 를 매기기 전에 상호 존재를 정규식으로 먼저 판정하고, 판정기가 언급이라 해도 탐지기가 없다고 하면 not_mentioned 로 덮는다. 언급 수가 모든 비율의 분모다. - URL 을 먼저 지운다. news.nate.com/view/... 의 "view" 를 언급으로 세던 오탐 3건 제거 - "리뷰"의 "뷰" 처럼 다른 낱말의 일부인 약칭을 뺀다. G-04 오탐 제거 - 한글 상호의 음절 사이 공백을 허용한다. "뷰 성형외과" 미탐지 2건 해소 언급 84 → 83, 약한 언급 3 → 1. 수치 (뷰성형외과 240건) 감성 점수 53.6 → 58.4. 약한 언급 1건 제외 시 57.9. mixed 10 → 0. haewon 라벨 대비 일치율 93%는 같은 표본으로 규칙을 고친 값이라 확정 근거가 아니다. 기준표 확정은 새 30건 2차 보정으로 한다. 리포트와 요약 JSON 에 confirmed=false 로 적었다. 월간 실측 체인 (scripts/monthly_ai_measure.py, 핸드오버 §2-4) 질문 뱅크 러너 → 감성 판정 → 리포트 → supporter_builds.report.sentiment 를 한 명령으로 묶었다. 보정은 사람 게이트이므로 체인에 넣지 않았다. 질문 뱅크 러너가 아직 뷰성형외과 전용이라 다른 병원으로 넓히려면 러너를 먼저 매개변수화해야 한다. 버그 수정 - --summary 를 --calibration 없이 돌리면 §7 생성에서 죽던 문제. 핸드오버가 권장하는 명령이 그대로 터졌다(--calibration 은 haewon 라벨을 지우므로 빼야 한다) - 결과 jsonl 의 rubric 필드와 리포트 머리말의 기준표 버전이 v0.1 로 하드코딩돼 있었다 Co-Authored-By: Claude Opus 5 (1M context) --- docs/AI_ANSWER_SENTIMENT_RUBRIC_v0.1.md | 65 ++++ docs/AI_ANSWER_SENTIMENT_RUBRIC_v0.3.md | 180 +++++++++++ .../Viewclinic_QB_Sentiment.md | 81 +++++ .../Viewclinic_QB_Sentiment_calibration.xlsx | Bin 0 -> 36973 bytes scripts/monthly_ai_measure.py | 109 +++++++ scripts/out/qb_sentiment_openai.jsonl | 120 ++++++++ scripts/out/qb_sentiment_perplexity.jsonl | 120 ++++++++ scripts/sentiment_qb.py | 291 ++++++++++++++++++ 8 files changed, 966 insertions(+) create mode 100644 docs/AI_ANSWER_SENTIMENT_RUBRIC_v0.1.md create mode 100644 docs/AI_ANSWER_SENTIMENT_RUBRIC_v0.3.md create mode 100644 docs/reports/viewclinic/03_question_bank/Viewclinic_QB_Sentiment.md create mode 100644 docs/reports/viewclinic/03_question_bank/Viewclinic_QB_Sentiment_calibration.xlsx create mode 100644 scripts/monthly_ai_measure.py create mode 100644 scripts/out/qb_sentiment_openai.jsonl create mode 100644 scripts/out/qb_sentiment_perplexity.jsonl create mode 100644 scripts/sentiment_qb.py diff --git a/docs/AI_ANSWER_SENTIMENT_RUBRIC_v0.1.md b/docs/AI_ANSWER_SENTIMENT_RUBRIC_v0.1.md new file mode 100644 index 0000000..f62692b --- /dev/null +++ b/docs/AI_ANSWER_SENTIMENT_RUBRIC_v0.1.md @@ -0,0 +1,65 @@ +# 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점. +- **다른 병원 추천 문맥**: 대상 병원이 언급되지 않고 경쟁 병원만 추천된 답변을 별도 지표(경쟁 노출)로 셀지. diff --git a/docs/AI_ANSWER_SENTIMENT_RUBRIC_v0.3.md b/docs/AI_ANSWER_SENTIMENT_RUBRIC_v0.3.md new file mode 100644 index 0000000..849cacb --- /dev/null +++ b/docs/AI_ANSWER_SENTIMENT_RUBRIC_v0.3.md @@ -0,0 +1,180 @@ +# 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차 보정 표본에서 같은 유형이 반복되면 그때 다시 본다. diff --git a/docs/reports/viewclinic/03_question_bank/Viewclinic_QB_Sentiment.md b/docs/reports/viewclinic/03_question_bank/Viewclinic_QB_Sentiment.md new file mode 100644 index 0000000..8ad3ed2 --- /dev/null +++ b/docs/reports/viewclinic/03_question_bank/Viewclinic_QB_Sentiment.md @@ -0,0 +1,81 @@ +# AI 답변 감성 판정 결과 · 뷰성형외과 + +판정 2026-09-09 · 기준표 v0.3(`docs/AI_ANSWER_SENTIMENT_RUBRIC_v0.3.md`) · 모델 gpt-4.1 · 답변 240건 + +감성 점수 = (positive×100 + neutral×50 + mixed×25 + negative×0) ÷ 언급된 답변 수. 규제 언급률과 추천율은 언급된 답변 기준. + +## 1. 엔진별 + +| 엔진 | 질문군 | 답변 | 언급 | 감성 점수 | 긍정 | 중립 | 혼합 | 부정 | 부정 비율 | 규제 언급률 | 추천율 | +|---|---|---|---|---|---|---|---|---|---|---|---| +| 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% | + +전체 합산: 언급 83/240 · 감성 점수 **58.4** · 부정 비율 7.2% · 규제 언급률 4.8% · 추천율 24.1% + +약한 언급 1건(G-08(openai))은 정식 상호 없이 영문 약칭만 나온 답변이다. 이를 빼면 언급 82건 · 감성 점수 **57.9**. 포함 여부는 기준표 §6 미결이다. + +## 2. 측면 태그 (언급된 답변) + +- procedure: 39건 +- access: 39건 +- reviews: 33건 +- doctors: 29건 +- safety: 28건 +- price: 11건 +- media: 8건 +- regulatory: 5건 + +## 3. 부정·혼합 판정 전건 (6건) + +| 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 | 뷰성형외과 부작용 후기나 안 좋은 얘기 있어? | 네. 검색 결과만 보면 **뷰성형외과에 대한 안 좋은 후기나 부작용 언급은 있습니다**. | + +## 4. 논브랜드 질문에서 긍정 언급 (15건, 가장 가치 있는 신호) + +| 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] | + +## 5. 언급 판정 불일치 (판정기 vs 추출기) 1건 + +- C-14 openai: 판정기 False / 추출기 True · + +## 6. 측정 가능 범위 + +- 재는 것: 질문 뱅크 문항에 대한 공식 API 답변 안에서 대상 병원 문장의 어조. 엔진은 ChatGPT(gpt-4o + 웹 검색)·Perplexity(sonar). +- 재지 않는 것: 소비자 화면(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에 있다. diff --git a/docs/reports/viewclinic/03_question_bank/Viewclinic_QB_Sentiment_calibration.xlsx b/docs/reports/viewclinic/03_question_bank/Viewclinic_QB_Sentiment_calibration.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..bb407132552e43736d45bb252886c5738a9f3016 GIT binary patch literal 36973 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 literal 0 HcmV?d00001 diff --git a/scripts/monthly_ai_measure.py b/scripts/monthly_ai_measure.py new file mode 100644 index 0000000..8687db8 --- /dev/null +++ b/scripts/monthly_ai_measure.py @@ -0,0 +1,109 @@ +#!/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 # 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") +ENGINES = ("openai", "perplexity") + +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 patch_supabase(build_id, sentiment): + """supporter_builds.report 에 sentiment 키만 병합한다. 다른 단계가 쓴 키를 지우지 않는다.""" + 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 + 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 갱신 (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="기존 감성 판정을 지우고 다시 판정한다(기준표를 고친 뒤)") + a = ap.parse_args() + + 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']}") + + if a.build_id: patch_supabase(a.build_id, sentiment) + else: print(" --build-id 없음. Supabase 반영은 건너뛴다.") + + +if __name__ == "__main__": + main() diff --git a/scripts/out/qb_sentiment_openai.jsonl b/scripts/out/qb_sentiment_openai.jsonl new file mode 100644 index 0000000..8c42a8d --- /dev/null +++ b/scripts/out/qb_sentiment_openai.jsonl @@ -0,0 +1,120 @@ +{"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"} diff --git a/scripts/out/qb_sentiment_perplexity.jsonl b/scripts/out/qb_sentiment_perplexity.jsonl new file mode 100644 index 0000000..e182efc --- /dev/null +++ b/scripts/out/qb_sentiment_perplexity.jsonl @@ -0,0 +1,120 @@ +{"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"} diff --git a/scripts/sentiment_qb.py b/scripts/sentiment_qb.py new file mode 100644 index 0000000..69456a9 --- /dev/null +++ b/scripts/sentiment_qb.py @@ -0,0 +1,291 @@ +#!/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"(? Date: Thu, 10 Sep 2026 16:00:26 +0900 Subject: [PATCH 02/17] =?UTF-8?q?feat(supporters):=20=ED=9A=8C=EB=B3=B5=20?= =?UTF-8?q?=EC=9D=BC=EC=A0=95=20=ED=94=8C=EB=9E=98=EB=84=88=20/plan=20?= =?UTF-8?q?=C2=B7=20/en/plan=20=C2=B7=20=EC=8B=9C=EC=88=A0=EB=B3=84=20?= =?UTF-8?q?=ED=9A=8C=EB=B3=B5=20=EA=B7=9C=EC=B9=99=ED=91=9C,=20=ED=81=90?= =?UTF-8?q?=EB=A0=88=EC=9D=B4=EC=85=98=20=EC=9E=A5=EC=86=8C,=20=EC=98=88?= =?UTF-8?q?=EC=95=BD=20=EB=94=A5=EB=A7=81=ED=81=AC,=20=ED=97=A4=EB=8D=94?= =?UTF-8?q?=20=EB=82=B4=EB=B9=84=20=EC=97=B0=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - src/pages/plan.astro, src/pages/en/plan.astro: 한국어·영어 페이지, hreflang 상호 연결 - src/components/Planner.astro: 5단계 흐름(언어·국적 → 시술·수술일 → 선호 → 일정표 → 예약·저장), 브라우저 안 계산, URL 해시 공유, ICS 저장 - src/lib/plan.ts: 입국 권장일·출국 가능 최소일·외출 등급·식사 단계·추천 점수·딥링크·ICS 순수 함수 - src/data/procedures.json: 시술 8종 규칙표. 값마다 clinic/provisional 표시, 병원 확인 전 잠정값은 화면 배지 - src/data/places.json: 신논현역 기준 큐레이션 31곳 (구글 지도·예약 사이트 검색 링크만) - src/data/planStrings.json: UI 문자열 ko/en - scripts/gate/plan.test.mjs: 계산·데이터 정합성 검증 11건 - Base.astro: lang·alternates prop, 내비 '회복 일정', 푸터 링크. 구조 기준선 갱신(haewon 승인) - visit.astro: 플래너 진입 CTA 카드. sitemap에 /plan, /en/plan 추가 - 설계 근거: docs/INFINITH_Viewclinic_Supporters_Site_Design_v0.2.md §13 Co-Authored-By: Claude Fable 5.1 --- supporters/scripts/gate/layout-baseline.json | 9 + supporters/scripts/gate/plan.test.mjs | 98 ++ supporters/src/components/Planner.astro | 241 +++++ supporters/src/data/places.json | 47 + supporters/src/data/planStrings.json | 179 ++++ supporters/src/data/procedures.json | 989 +++++++++++++++++++ supporters/src/layouts/Base.astro | 12 +- supporters/src/lib/plan.ts | 173 ++++ supporters/src/pages/en/plan.astro | 16 + supporters/src/pages/plan.astro | 16 + supporters/src/pages/sitemap.xml.ts | 2 + supporters/src/pages/visit.astro | 7 + supporters/src/styles/plan.css | 109 ++ 13 files changed, 1894 insertions(+), 4 deletions(-) create mode 100644 supporters/scripts/gate/plan.test.mjs create mode 100644 supporters/src/components/Planner.astro create mode 100644 supporters/src/data/places.json create mode 100644 supporters/src/data/planStrings.json create mode 100644 supporters/src/data/procedures.json create mode 100644 supporters/src/lib/plan.ts create mode 100644 supporters/src/pages/en/plan.astro create mode 100644 supporters/src/pages/plan.astro create mode 100644 supporters/src/styles/plan.css diff --git a/supporters/scripts/gate/layout-baseline.json b/supporters/scripts/gate/layout-baseline.json index 24bbdbf..094e2d3 100644 --- a/supporters/scripts/gate/layout-baseline.json +++ b/supporters/scripts/gate/layout-baseline.json @@ -15,6 +15,7 @@ "a", "a", "a", + "a", "a.cta", "main", "section.hero-light", @@ -255,6 +256,8 @@ "a", "a", "a", + "a", + "a", "div", "p", "img", @@ -283,6 +286,7 @@ "a", "a", "a", + "a", "a.cta", "div.backbar-wrap", "div.backbar.wrap-wide", @@ -480,6 +484,8 @@ "a", "a", "a", + "a", + "a", "div", "p", "img", @@ -508,6 +514,7 @@ "a", "a", "a", + "a", "a.cta", "div.backbar-wrap", "div.backbar.wrap-wide", @@ -731,6 +738,8 @@ "a", "a", "a", + "a", + "a", "div", "p", "img", diff --git a/supporters/scripts/gate/plan.test.mjs b/supporters/scripts/gate/plan.test.mjs new file mode 100644 index 0000000..edf494d --- /dev/null +++ b/supporters/scripts/gate/plan.test.mjs @@ -0,0 +1,98 @@ +// 회복기 스케줄러 계산 로직 검증. node --experimental-strip-types scripts/gate/plan.test.mjs +import { readFileSync } from 'node:fs'; +import assert from 'node:assert/strict'; +import { buildPlan, addDays, decodeHash, encodeHash, bookingUrl, googleFlightsUrl, buildIcs } from '../../src/lib/plan.ts'; + +const here = (p) => new URL(p, import.meta.url); +const P = JSON.parse(readFileSync(here('../../src/data/procedures.json'), 'utf8')); +const PL = JSON.parse(readFileSync(here('../../src/data/places.json'), 'utf8')); +const byId = (id) => P.procedures.find((x) => x.id === id); +const nat = (code) => P.nationalities.list.find((x) => x.code === code); + +let n = 0; +const ok = (name, fn) => { fn(); n++; console.log(` ✓ ${name}`); }; + +ok('addDays: 월 경계', () => { assert.equal(addDays('2026-10-30', 3), '2026-11-02'); assert.equal(addDays('2026-11-01', -2), '2026-10-30'); }); + +ok('가슴: 입국일·실밥·출국·체류', () => { + const plan = buildPlan({ procedure: byId('breast'), surgeryDate: '2026-11-03', nationality: nat('JP'), prefs: ['shopping'] }, PL.places); + assert.equal(plan.arriveBy, '2026-11-01'); + assert.equal(plan.stitchDate, '2026-11-10'); + assert.equal(plan.earliestDeparture, '2026-11-13'); + assert.equal(plan.stayNights, 12); + assert.equal(plan.days[0].isArrival, true); + assert.equal(plan.days.at(-1).isDeparture, true); +}); + +ok('장거리 국적은 준비일 +1', () => { + const plan = buildPlan({ procedure: byId('breast'), surgeryDate: '2026-11-03', nationality: nat('US'), prefs: [] }, PL.places); + assert.equal(plan.arriveBy, '2026-10-31'); + assert.equal(plan.preOpDays, 3); +}); + +ok('윤곽: 유동식 단계에는 죽·스무디만 추천', () => { + const plan = buildPlan({ procedure: byId('contour'), surgeryDate: '2026-11-03', nationality: nat('JP'), prefs: ['food', 'shopping'] }, PL.places); + const day5 = plan.days.find((d) => d.offset === 5); + assert.equal(day5.level, 1); + assert.equal(day5.diet, 'liquid'); + for (const pick of day5.picks) { + assert.ok(pick.minLevel <= 1, `${pick.id} 등급 초과`); + if (pick.category === 'food') assert.ok(pick.dietOk.includes('liquid'), `${pick.id} 유동식 불가`); + } + assert.equal(plan.stitchDate, null); // 녹는 실 +}); + +ok('수술 당일·입원일·L0 은 추천 없음', () => { + const plan = buildPlan({ procedure: byId('breast'), surgeryDate: '2026-11-03', nationality: null, prefs: [] }, PL.places); + const d0 = plan.days.find((d) => d.offset === 0), d1 = plan.days.find((d) => d.offset === 1); + assert.deepEqual(d0.picks, []); assert.equal(d0.isSurgery, true); + assert.equal(d1.level, 0); assert.deepEqual(d1.picks, []); +}); + +ok('호텔은 일자 추천에 나오지 않음', () => { + const plan = buildPlan({ procedure: byId('skin'), surgeryDate: '2026-11-03', nationality: null, prefs: ['care'] }, PL.places); + for (const d of plan.days) for (const p of d.picks) assert.notEqual(p.category, 'hotel'); +}); + +ok('연속 이틀 같은 곳 반복 억제', () => { + const plan = buildPlan({ procedure: byId('eye'), surgeryDate: '2026-11-03', nationality: null, prefs: ['sightseeing'] }, PL.places); + const out = plan.days.filter((d) => d.picks.length && d.offset > 0); + let repeats = 0; + for (let i = 1; i < out.length; i++) for (const p of out[i].picks) if (out[i - 1].picks.some((q) => q.id === p.id)) repeats++; + assert.ok(repeats <= out.length, `연속 반복 ${repeats}`); +}); + +ok('해시 왕복', () => { + const s = { p: 'nose', d: '2026-12-01', l: 'en', n: 'US', pref: ['kpop', 'food'] }; + assert.deepEqual(decodeHash('#' + encodeHash(s)), s); + assert.equal(decodeHash('#d=notadate').d, undefined); +}); + +ok('예약 링크 형식', () => { + const u = new URL(bookingUrl('Hotel Cappuccino Seoul', '2026-11-01', '2026-11-13', 'en')); + assert.equal(u.hostname, 'www.booking.com'); + assert.equal(u.searchParams.get('checkin'), '2026-11-01'); + assert.ok(googleFlightsUrl('2026-11-01', '2026-11-13', 'en').startsWith('https://www.google.com/travel/flights')); +}); + +ok('ICS 에 일정 4개', () => { + const plan = buildPlan({ procedure: byId('nose'), surgeryDate: '2026-11-03', nationality: null, prefs: [] }, PL.places); + const ics = buildIcs(plan, { arrive: 'Arrive', surgery: 'Surgery', stitch: 'Stitch', departure: 'Depart' }, 'Test'); + assert.equal((ics.match(/BEGIN:VEVENT/g) || []).length, 4); + assert.ok(ics.includes('DTSTART;VALUE=DATE:20261103')); +}); + +ok('규칙표 정합성: 숫자마다 status, provisional 은 source 없어도 됨, clinic 은 source 필수', () => { + for (const p of P.procedures) { + for (const k of ['preOpDays', 'hospitalNights', 'stitchRemovalDay', 'earliestDepartureDay']) { + assert.ok(['clinic', 'provisional'].includes(p[k].status), `${p.id}.${k} status`); + if (p[k].status === 'clinic') assert.ok(p[k].source, `${p.id}.${k} clinic 인데 source 없음`); + } + for (const s of [...p.outingLevels, ...p.diet]) if (s.status === 'clinic') assert.ok(s.source, `${p.id} 단계 clinic 인데 source 없음`); + for (const c of p.constraints) assert.ok(P.constraints[c], `${p.id} 미정의 제약 ${c}`); + for (const s of p.sources) assert.ok(P.clinicSources.some((x) => x.id === s), `${p.id} 미정의 출처 ${s}`); + } + for (const pl of PL.places) { assert.ok(PL.categories[pl.category], `${pl.id} 카테고리`); assert.ok(pl.name.ko && pl.name.en, `${pl.id} 이름`); } +}); + +console.log(`plan.test: ${n}건 통과`); diff --git a/supporters/src/components/Planner.astro b/supporters/src/components/Planner.astro new file mode 100644 index 0000000..ae89916 --- /dev/null +++ b/supporters/src/components/Planner.astro @@ -0,0 +1,241 @@ +--- +// 회복기 스케줄러 본문 (설계 v0.2 §13). /plan(한국어)과 /en/plan(영어)이 같은 컴포넌트를 lang 만 바꿔 쓴다. +// 건강정보(시술·수술일)는 서버로 보내지 않는다. 상태는 URL 해시로만 공유하고, 언어 전환은 해시를 들고 다른 언어 페이지로 이동한다. +import '../styles/plan.css'; +import strings from '../data/planStrings.json'; +interface Props { lang: 'ko' | 'en' } +const { lang } = Astro.props; +const t = strings[lang]; +--- +
+
+ +
{t.eyebrow}
+

{t.h_hero}

+

{t.title} {t.lede}

+
+
+ +
+
+
+ + diff --git a/supporters/src/data/places.json b/supporters/src/data/places.json new file mode 100644 index 0000000..bbf62d5 --- /dev/null +++ b/supporters/src/data/places.json @@ -0,0 +1,47 @@ +{ + "_comment": "회복기 스케줄러 큐레이션 장소 (설계 v0.2 §13.5 1순위 소스). 기준점은 신논현역(뷰성형외과). distanceKm 는 직선 거리 근사값이고 verify 가 true 인 항목은 출시 전 영업 여부·거리를 확인한다. 링크는 구글 지도 검색과 예약 사이트 검색 결과만 쓴다(지어낸 상세 URL 없음). 축제·행사(날짜 의존)는 한국관광공사 Tour API 연결(P1) 뒤에 추가한다.", + "origin": { "label": { "ko": "신논현역 3번 출구", "en": "Sinnonhyeon Station Exit 3" }, "lat": 37.5045, "lng": 127.0250 }, + "categories": { + "sightseeing": { "ko": "관광", "en": "Sightseeing" }, + "shopping": { "ko": "쇼핑", "en": "Shopping" }, + "food": { "ko": "미식", "en": "Food" }, + "kpop": { "ko": "K-pop·핫플", "en": "K-pop & hotspots" }, + "care": { "ko": "휴식·케어", "en": "Rest & care" }, + "essentials": { "ko": "생활", "en": "Essentials" }, + "hotel": { "ko": "숙소", "en": "Hotels" } + }, + "places": [ + { "id": "pharmacy-sinnonhyeon", "name": { "ko": "신논현역 주변 약국", "en": "Pharmacies near Sinnonhyeon Station" }, "category": "essentials", "minLevel": 1, "distanceKm": 0.2, "indoor": true, "area": { "ko": "신논현", "en": "Sinnonhyeon" }, "note": { "ko": "병원 건물 옆 골목에 약국이 있습니다. 처방약과 냉찜질 팩을 구할 수 있습니다.", "en": "Pharmacies in the alley next to the clinic building. Prescriptions and cold packs." }, "mapQuery": "약국 신논현역", "verify": true }, + { "id": "convenience-sinnonhyeon", "name": { "ko": "편의점 (신논현역 일대)", "en": "Convenience stores (Sinnonhyeon area)" }, "category": "essentials", "minLevel": 1, "distanceKm": 0.1, "indoor": true, "area": { "ko": "신논현", "en": "Sinnonhyeon" }, "note": { "ko": "생수, 죽, 빨대, 얼음을 24시간 살 수 있습니다.", "en": "Water, porridge, straws and ice, 24 hours." }, "mapQuery": "편의점 신논현역", "verify": false }, + { "id": "porridge-chain", "name": { "ko": "죽 전문점 (본죽 등 체인)", "en": "Korean porridge (Bonjuk and similar chains)" }, "category": "food", "minLevel": 1, "dietOk": ["liquid", "soft", "mild", "normal"], "distanceKm": 0.4, "indoor": true, "area": { "ko": "신논현·강남", "en": "Sinnonhyeon / Gangnam" }, "note": { "ko": "유동식 단계에서 먹을 수 있는 죽을 배달과 포장으로 받을 수 있습니다.", "en": "Porridge suitable for the liquid-diet stage, by delivery or takeout." }, "mapQuery": "본죽 신논현역", "verify": true }, + { "id": "smoothie-cafe", "name": { "ko": "스무디·주스 카페 (강남대로)", "en": "Smoothie and juice cafes (Gangnam-daero)" }, "category": "food", "minLevel": 1, "dietOk": ["liquid", "soft", "mild", "normal"], "distanceKm": 0.5, "indoor": true, "area": { "ko": "강남대로", "en": "Gangnam-daero" }, "note": { "ko": "빨대로 마실 수 있는 메뉴가 많습니다. 뜨겁지 않은 것으로 고르세요.", "en": "Plenty of straw-friendly options. Choose nothing hot." }, "mapQuery": "스무디 카페 신논현역", "verify": true }, + { "id": "seolleongtang", "name": { "ko": "설렁탕·곰탕 (강남 일대)", "en": "Seolleongtang beef bone soup (Gangnam area)" }, "category": "food", "minLevel": 1, "dietOk": ["soft", "mild", "normal"], "distanceKm": 0.6, "indoor": true, "area": { "ko": "강남", "en": "Gangnam" }, "note": { "ko": "국물 위주라 부드러운 음식 단계부터 가능합니다. 밥을 말아 천천히 드세요.", "en": "Broth-based, fine from the soft-food stage. Eat slowly with rice in the soup." }, "mapQuery": "설렁탕 강남역", "verify": true }, + { "id": "sulbing", "name": { "ko": "설빙 (빙수 디저트)", "en": "Sulbing shaved-ice dessert" }, "category": "food", "minLevel": 1, "dietOk": ["soft", "mild", "normal"], "distanceKm": 0.6, "indoor": true, "area": { "ko": "강남", "en": "Gangnam" }, "note": { "ko": "차갑고 부드러워 부기가 있을 때도 먹기 편합니다.", "en": "Cold and soft, comfortable even with swelling." }, "mapQuery": "설빙 강남", "verify": true }, + { "id": "cafe-garosugil", "name": { "ko": "가로수길 카페", "en": "Garosu-gil cafes" }, "category": "food", "minLevel": 2, "dietOk": ["soft", "mild", "normal"], "distanceKm": 1.2, "indoor": true, "area": { "ko": "신사동 가로수길", "en": "Garosu-gil, Sinsa" }, "note": { "ko": "조용한 카페가 많아 오후에 잠깐 앉아 있기 좋습니다.", "en": "Quiet cafes, good for a short afternoon sit-down." }, "mapQuery": "가로수길 카페", "verify": false }, + { "id": "korean-bbq-gangnam", "name": { "ko": "강남 한식·구이", "en": "Korean BBQ and set meals (Gangnam)" }, "category": "food", "minLevel": 2, "dietOk": ["normal"], "distanceKm": 0.8, "indoor": true, "area": { "ko": "강남", "en": "Gangnam" }, "note": { "ko": "일반식이 가능한 단계부터입니다. 담당 원장이 술을 허용하기 전에는 음료로 대신하세요.", "en": "From the regular-diet stage. Skip alcohol until your surgeon allows it." }, "mapQuery": "한식 맛집 강남역", "verify": false }, + { "id": "garosugil", "name": { "ko": "가로수길", "en": "Garosu-gil" }, "category": "shopping", "minLevel": 2, "distanceKm": 1.2, "indoor": false, "area": { "ko": "신사동", "en": "Sinsa" }, "note": { "ko": "편집숍과 뷰티 매장이 모여 있는 거리입니다. 택시로 5분.", "en": "A street of boutiques and beauty stores. 5 minutes by taxi." }, "mapQuery": "가로수길", "verify": false }, + { "id": "oliveyoung-gangnam", "name": { "ko": "올리브영 강남 타운", "en": "Olive Young Gangnam Town" }, "category": "shopping", "minLevel": 2, "distanceKm": 0.9, "indoor": true, "area": { "ko": "강남역", "en": "Gangnam Station" }, "note": { "ko": "K-뷰티 대형 매장입니다. 수술 부위에 닿는 화장품은 담당 원장 안내를 따르세요.", "en": "Large K-beauty store. Follow your surgeon's guidance on products near the surgical area." }, "mapQuery": "올리브영 강남타운", "verify": true }, + { "id": "shinsegae-gangnam", "name": { "ko": "신세계백화점 강남점", "en": "Shinsegae Department Store Gangnam" }, "category": "shopping", "minLevel": 2, "distanceKm": 2.4, "indoor": true, "area": { "ko": "고속터미널", "en": "Express Bus Terminal" }, "note": { "ko": "지하 파미에스테이션과 연결됩니다. 실내라 날씨와 상관없이 다닐 수 있습니다.", "en": "Connected to the Famille Station underground mall. Fully indoors." }, "mapQuery": "신세계백화점 강남점", "verify": false }, + { "id": "hyundai-apgujeong", "name": { "ko": "현대백화점 압구정본점", "en": "Hyundai Department Store Apgujeong" }, "category": "shopping", "minLevel": 2, "distanceKm": 2.0, "indoor": true, "area": { "ko": "압구정", "en": "Apgujeong" }, "note": { "ko": "택시로 10분. 배송 서비스를 이용하면 짐을 들지 않아도 됩니다.", "en": "10 minutes by taxi. Use the delivery counter so you carry nothing." }, "mapQuery": "현대백화점 압구정본점", "verify": false }, + { "id": "coex-mall", "name": { "ko": "코엑스몰·별마당도서관", "en": "COEX Mall and Starfield Library" }, "category": "shopping", "minLevel": 2, "distanceKm": 3.4, "indoor": true, "area": { "ko": "삼성동", "en": "Samseong" }, "note": { "ko": "실내 쇼핑몰과 도서관, 아쿠아리움이 한 건물에 있습니다.", "en": "Indoor mall, library and aquarium in one building." }, "mapQuery": "코엑스몰", "verify": false }, + { "id": "apgujeong-rodeo", "name": { "ko": "압구정 로데오·K-Star Road", "en": "Apgujeong Rodeo and K-Star Road" }, "category": "kpop", "minLevel": 2, "distanceKm": 2.6, "indoor": false, "area": { "ko": "압구정", "en": "Apgujeong" }, "note": { "ko": "기획사 건물과 아이돌 조형물이 있는 거리입니다.", "en": "Street of entertainment agencies and idol art figures." }, "mapQuery": "K-Star Road 압구정", "verify": true }, + { "id": "kwangya-seongsu", "name": { "ko": "KWANGYA@SEOUL (성수)", "en": "KWANGYA@SEOUL (Seongsu)" }, "category": "kpop", "minLevel": 3, "distanceKm": 5.5, "indoor": true, "area": { "ko": "성수동", "en": "Seongsu" }, "note": { "ko": "SM 엔터테인먼트 공식 굿즈 매장입니다. 운영 시간을 확인하고 가세요.", "en": "SM Entertainment's official merchandise store. Check hours before visiting." }, "mapQuery": "KWANGYA@SEOUL", "verify": true }, + { "id": "seongsu", "name": { "ko": "성수동 카페·팝업", "en": "Seongsu cafes and pop-ups" }, "category": "kpop", "minLevel": 3, "distanceKm": 5.5, "indoor": false, "area": { "ko": "성수동", "en": "Seongsu" }, "note": { "ko": "브랜드 팝업과 카페가 모인 동네입니다. 주말은 인파가 많습니다.", "en": "Brand pop-ups and cafes. Crowded on weekends." }, "mapQuery": "성수동 팝업", "verify": false }, + { "id": "hongdae", "name": { "ko": "홍대", "en": "Hongdae" }, "category": "kpop", "minLevel": 3, "distanceKm": 11, "indoor": false, "area": { "ko": "홍대입구", "en": "Hongik Univ." }, "note": { "ko": "버스킹과 굿즈숍이 있습니다. 왕복 1시간 이상 걸리니 자유 외출 단계에서 가세요.", "en": "Busking and merch shops. Over an hour round trip, so save it for full-day outings." }, "mapQuery": "홍대 걷고싶은거리", "verify": false }, + { "id": "kakao-friends-gangnam", "name": { "ko": "카카오프렌즈 강남 플래그십", "en": "Kakao Friends Gangnam Flagship" }, "category": "kpop", "minLevel": 2, "distanceKm": 0.9, "indoor": true, "area": { "ko": "강남역", "en": "Gangnam Station" }, "note": { "ko": "캐릭터 굿즈 매장입니다. 걸어서도 갈 수 있는 거리입니다.", "en": "Character merchandise store, within walking distance." }, "mapQuery": "카카오프렌즈 강남플래그십스토어", "verify": true }, + { "id": "bongeunsa", "name": { "ko": "봉은사", "en": "Bongeunsa Temple" }, "category": "sightseeing", "minLevel": 2, "distanceKm": 3.6, "indoor": false, "area": { "ko": "삼성동", "en": "Samseong" }, "note": { "ko": "코엑스 맞은편의 사찰입니다. 경사가 완만해 천천히 걷기 좋습니다.", "en": "Temple across from COEX. Gentle slopes, good for a slow walk." }, "mapQuery": "봉은사", "verify": false }, + { "id": "seonjeongneung", "name": { "ko": "선정릉", "en": "Seonjeongneung Royal Tombs" }, "category": "sightseeing", "minLevel": 2, "distanceKm": 2.4, "indoor": false, "area": { "ko": "선릉", "en": "Seolleung" }, "note": { "ko": "유네스코 세계유산 왕릉입니다. 숲길이 그늘져서 햇빛을 피하기 좋습니다.", "en": "UNESCO World Heritage royal tombs. Shaded forest paths." }, "mapQuery": "선정릉", "verify": false }, + { "id": "banpo-hangang", "name": { "ko": "반포한강공원·달빛무지개분수", "en": "Banpo Hangang Park and Moonlight Rainbow Fountain" }, "category": "sightseeing", "minLevel": 2, "distanceKm": 2.8, "indoor": false, "area": { "ko": "반포", "en": "Banpo" }, "note": { "ko": "저녁에 분수 공연을 볼 수 있습니다. 운영 기간을 확인하세요.", "en": "Evening fountain show. Check the season schedule." }, "mapQuery": "반포한강공원", "verify": false }, + { "id": "gyeongbokgung", "name": { "ko": "경복궁·북촌", "en": "Gyeongbokgung Palace and Bukchon" }, "category": "sightseeing", "minLevel": 3, "distanceKm": 9, "indoor": false, "area": { "ko": "종로", "en": "Jongno" }, "note": { "ko": "택시로 30분 이상 걸립니다. 걷는 시간이 길어 자유 외출 단계에서 가세요.", "en": "Over 30 minutes by taxi and a lot of walking. Save for full-day outings." }, "mapQuery": "경복궁", "verify": false }, + { "id": "lotte-tower", "name": { "ko": "롯데월드타워·서울스카이", "en": "Lotte World Tower and Seoul Sky" }, "category": "sightseeing", "minLevel": 3, "distanceKm": 8, "indoor": true, "area": { "ko": "잠실", "en": "Jamsil" }, "note": { "ko": "실내 전망대와 쇼핑몰입니다. 택시로 25분.", "en": "Indoor observatory and mall. 25 minutes by taxi." }, "mapQuery": "롯데월드타워", "verify": false }, + { "id": "myeongdong", "name": { "ko": "명동", "en": "Myeongdong" }, "category": "shopping", "minLevel": 3, "distanceKm": 7, "indoor": false, "area": { "ko": "중구", "en": "Jung-gu" }, "note": { "ko": "화장품과 길거리 음식이 많습니다. 인파가 많으니 부딪히지 않게 다니세요.", "en": "Cosmetics and street food. Crowded, so mind bumps." }, "mapQuery": "명동", "verify": false }, + { "id": "clinic-aftercare", "name": { "ko": "뷰성형외과 애프터케어", "en": "View Plastic Surgery Aftercare" }, "category": "care", "minLevel": 1, "distanceKm": 0.05, "indoor": true, "area": { "ko": "병원", "en": "Clinic" }, "note": { "ko": "고주파 관리와 경과 확인은 병원 안내에 따라 예약합니다.", "en": "Radiofrequency care and follow-up checks, booked per the clinic's guidance." }, "mapQuery": "뷰성형외과", "verify": false }, + { "id": "clinic-dental", "name": { "ko": "뷰성형외과 15층 치과", "en": "View Plastic Surgery Dental Clinic (15F)" }, "category": "care", "minLevel": 1, "distanceKm": 0.05, "indoor": true, "area": { "ko": "병원", "en": "Clinic" }, "note": { "ko": "양악·윤곽 협진 치과입니다. 여유 시간에 검진을 받을 수 있는지 상담에서 물어보세요.", "en": "Dental clinic for jaw and contouring cases. Ask at consultation about a check-up during your stay." }, "mapQuery": "뷰성형외과", "verify": false }, + { "id": "hotel-spa-gangnam", "name": { "ko": "호텔 스파·라운지 (숙소 안)", "en": "Hotel spa and lounge (at your hotel)" }, "category": "care", "minLevel": 0, "distanceKm": 0, "indoor": true, "area": { "ko": "숙소", "en": "Your hotel" }, "note": { "ko": "사우나와 마사지는 담당 원장이 허용하기 전에는 피하고 라운지와 룸서비스만 이용하세요.", "en": "Skip sauna and massage until your surgeon allows. Lounge and room service are fine." }, "mapQuery": null, "verify": false }, + { "id": "hotel-cappuccino", "name": { "ko": "호텔 카푸치노", "en": "Hotel Cappuccino" }, "category": "hotel", "minLevel": 0, "distanceKm": 0.5, "indoor": true, "area": { "ko": "논현동 봉은사로", "en": "Nonhyeon, Bongeunsa-ro" }, "note": { "ko": "병원과 같은 봉은사로에 있어 걸어서 다닐 수 있습니다.", "en": "On the same road as the clinic, within walking distance." }, "mapQuery": "호텔 카푸치노 논현", "bookingQuery": "Hotel Cappuccino Seoul", "verify": true }, + { "id": "novotel-gangnam", "name": { "ko": "노보텔 앰배서더 강남", "en": "Novotel Ambassador Seoul Gangnam" }, "category": "hotel", "minLevel": 0, "distanceKm": 1.0, "indoor": true, "area": { "ko": "역삼동", "en": "Yeoksam" }, "note": { "ko": "강남역 근처 대형 호텔입니다. 룸서비스와 24시간 프런트가 있습니다.", "en": "Large hotel near Gangnam Station with room service and a 24-hour front desk." }, "mapQuery": "노보텔 앰배서더 강남", "bookingQuery": "Novotel Ambassador Seoul Gangnam", "verify": true }, + { "id": "l7-gangnam", "name": { "ko": "L7 강남 바이 롯데", "en": "L7 Gangnam by Lotte" }, "category": "hotel", "minLevel": 0, "distanceKm": 1.4, "indoor": true, "area": { "ko": "강남역", "en": "Gangnam Station" }, "note": { "ko": "강남역 인근이며 택시로 5분입니다.", "en": "Near Gangnam Station, 5 minutes by taxi." }, "mapQuery": "L7 강남", "bookingQuery": "L7 Gangnam by Lotte", "verify": true }, + { "id": "andaz-gangnam", "name": { "ko": "안다즈 서울 강남", "en": "Andaz Seoul Gangnam" }, "category": "hotel", "minLevel": 0, "distanceKm": 2.6, "indoor": true, "area": { "ko": "압구정 로데오", "en": "Apgujeong Rodeo" }, "note": { "ko": "압구정 로데오에 있는 고급 호텔입니다. 병원까지 택시로 10분.", "en": "Upscale hotel at Apgujeong Rodeo. 10 minutes to the clinic by taxi." }, "mapQuery": "안다즈 서울 강남", "bookingQuery": "Andaz Seoul Gangnam", "verify": true }, + { "id": "josun-palace", "name": { "ko": "조선 팰리스 강남", "en": "Josun Palace Seoul Gangnam" }, "category": "hotel", "minLevel": 0, "distanceKm": 1.8, "indoor": true, "area": { "ko": "역삼동", "en": "Yeoksam" }, "note": { "ko": "역삼동의 고급 호텔입니다. 병원까지 택시로 10분.", "en": "Luxury hotel in Yeoksam. 10 minutes to the clinic by taxi." }, "mapQuery": "조선 팰리스 서울 강남", "bookingQuery": "Josun Palace Seoul Gangnam", "verify": true } + ] +} diff --git a/supporters/src/data/planStrings.json b/supporters/src/data/planStrings.json new file mode 100644 index 0000000..b2e8a15 --- /dev/null +++ b/supporters/src/data/planStrings.json @@ -0,0 +1,179 @@ +{ + "_comment": "회복기 스케줄러 UI 문자열. ko/en 두 언어. 일본어·중국어는 P1에서 같은 키로 추가한다. 고객 화면이므로 운영자 어휘를 쓰지 않는다.", + "ko": { + "eyebrow": "Recovery Planner", + "title": "수술일을 정하셨다면, 여행 일정은 여기서 짜세요.", + "lede": "시술과 수술일을 고르면 입국 권장일, 출국 가능 최소일, 회복기 동안 외출할 수 있는 날과 그날 갈 만한 곳을 정리해 드립니다. 계산은 이 화면 안에서만 이루어지고 어디에도 저장되지 않습니다.", + "step1": "언어와 국적", + "step2": "시술과 수술일", + "step3": "여유 시간에 하고 싶은 것", + "step4": "일정표", + "step5": "예약과 저장", + "lang": "언어", + "nationality": "국적", + "procedure": "시술", + "surgeryDate": "수술일", + "surgeryDateHint": "상담에서 확정한 날짜를 넣으세요. 아직 정하지 않았다면 예상일도 괜찮습니다.", + "prefs": "여러 개 고를 수 있습니다", + "pref_sightseeing": "관광", + "pref_shopping": "쇼핑", + "pref_food": "미식", + "pref_kpop": "K-pop·핫플", + "pref_care": "휴식·케어", + "generate": "일정표 만들기", + "arriveBy": "입국 권장일", + "surgery": "수술일", + "hospital": "입원", + "hospitalNights": "{n}박", + "hospitalNone": "당일 귀가", + "stitch": "실밥·부목 제거", + "departure": "출국 가능 최소일", + "stayNights": "총 체류", + "nights": "{n}박", + "pending": "확인", + "provisional": "병원 확인 전", + "clinicSourced": "병원 안내 기준", + "departureNote": "출국 가능 최소일은 최소 기준입니다. 담당 원장이 경과를 보고 다르게 안내할 수 있으니 항공권은 변경 가능한 조건으로 예약하세요.", + "dayBefore": "수술 {n}일 전", + "daySurgery": "수술 당일", + "dayAfter": "수술 후 {n}일", + "level": "외출", + "diet": "식사", + "picks": "추천", + "noPicks": "이 날은 쉬는 날입니다. 배달과 룸서비스를 이용하세요.", + "constraints": "회복기 동안 지킬 것", + "visaTitle": "체류 기간 확인", + "visaOk": "{country} 국적의 무비자 체류 한도는 {days}일이고 이 일정은 {stay}일입니다. 한도 안에 있습니다.", + "visaOver": "{country} 국적의 무비자 체류 한도는 {days}일인데 이 일정은 {stay}일입니다. 비자가 필요할 수 있으니 확인하세요.", + "visaRequired": "{country} 국적은 사전 비자가 필요할 수 있습니다. 출발 전 확인하세요.", + "visaVerify": "체류 한도는 2026년 9월 기준이며 바뀔 수 있습니다. 대한민국 비자포털에서 확인하세요.", + "visaPortal": "대한민국 비자포털", + "hotels": "병원 근처 숙소", + "hotelHint": "입국 권장일부터 출국 가능 최소일까지 날짜를 넣어 검색합니다. 엘리베이터, 룸서비스, 24시간 프런트가 있는 곳을 골랐습니다.", + "hotelSearch": "이 날짜로 검색", + "flights": "항공권", + "flightHint": "인천공항 도착 기준으로 날짜를 채워 검색합니다. 변경 가능한 요금을 권합니다.", + "flightSearch": "구글 항공권에서 보기", + "consult": "상담 예약", + "consultHint": "아래 일정 요약을 복사해 상담 신청 때 함께 보내면 병원이 회복 일정에 맞춰 안내할 수 있습니다.", + "copySummary": "일정 요약 복사", + "copied": "복사했습니다", + "shareLink": "링크 복사", + "ics": "캘린더에 저장", + "print": "인쇄", + "map": "지도", + "readMore": "회복 일정 자세히", + "affiliate": "숙소·항공 링크는 각 예약 사이트로 이동하며, 제휴 링크가 적용될 경우 이 매체가 소정의 수수료를 받을 수 있습니다. 예약 조건과 결제는 각 사이트의 안내를 따릅니다.", + "medical": "이 일정표는 병원 공개 안내를 바탕으로 한 일반 정보입니다. 본인 수술 범위와 경과에 따라 담당 원장의 지시가 우선합니다.", + "km": "{km}km", + "walk": "도보", + "taxi": "택시", + "indoor": "실내", + "summaryTitle": "회복 일정 요약", + "levelLabel": "외출 단계", + "sourceLabel": "근거", + "reset": "처음부터", + "provisionalNote": "\"병원 확인 전\"으로 표시된 값은 병원 공개 안내에 없어 일반 기준으로 넣은 잠정값입니다. 상담에서 본인 일정으로 확정하세요.", + "noRemoval": "제거 방문 없음", + "h_hero": "Plan Your Recovery Stay", + "h_step1": "Language & Nationality", + "h_step2": "Procedure & Surgery Date", + "h_step3": "Your Free Time", + "h_step4": "Your Schedule", + "h_visa": "Length of Stay", + "h_rules": "During Recovery", + "h_days": "Day by Day", + "h_book": "Book & Save", + "h_hotels": "Hotels Near the Clinic", + "h_flights": "Flights", + "h_consult": "Consultation" + }, + "en": { + "eyebrow": "Recovery Planner", + "title": "Surgery date set? Plan the rest of your trip here.", + "lede": "Pick your procedure and surgery date. We work out when to arrive, the earliest you can fly home, which recovery days you can go out, and where to go on those days. Everything is calculated on this page and stored nowhere.", + "step1": "", + "step2": "", + "step3": "", + "step4": "", + "step5": "", + "lang": "Language", + "nationality": "Nationality", + "procedure": "Procedure", + "surgeryDate": "Surgery date", + "surgeryDateHint": "Use the date confirmed at consultation. An estimate is fine if it's not set yet.", + "prefs": "Choose as many as you like", + "pref_sightseeing": "Sightseeing", + "pref_shopping": "Shopping", + "pref_food": "Food", + "pref_kpop": "K-pop & hotspots", + "pref_care": "Rest & care", + "generate": "Build my schedule", + "arriveBy": "Arrive by", + "surgery": "Surgery", + "hospital": "Hospital stay", + "hospitalNights": "{n} night(s)", + "hospitalNone": "Same-day discharge", + "stitch": "Stitch / splint removal", + "departure": "Earliest departure", + "stayNights": "Total stay", + "nights": "{n} nights", + "pending": "To confirm", + "provisional": "Provisional", + "clinicSourced": "From clinic guidance", + "departureNote": "Earliest departure is a minimum. Your surgeon may advise differently after follow-up, so book flights with flexible terms.", + "dayBefore": "{n} day(s) before surgery", + "daySurgery": "Surgery day", + "dayAfter": "Day {n} after surgery", + "level": "Outing", + "diet": "Diet", + "picks": "Suggestions", + "noPicks": "A rest day. Use delivery and room service.", + "constraints": "During recovery", + "visaTitle": "Length of stay check", + "visaOk": "Visa-free stay for {country} nationals is {days} days and this plan is {stay} days. You're within the limit.", + "visaOver": "Visa-free stay for {country} nationals is {days} days but this plan is {stay} days. You may need a visa, please check.", + "visaRequired": "{country} nationals may need a visa in advance. Check before you travel.", + "visaVerify": "Stay limits are as of September 2026 and may change. Confirm at the Korea Visa Portal.", + "visaPortal": "Korea Visa Portal", + "hotels": "Hotels near the clinic", + "hotelHint": "Searches with your arrival and earliest-departure dates filled in. We picked places with elevators, room service and a 24-hour front desk.", + "hotelSearch": "Search these dates", + "flights": "Flights", + "flightHint": "Searches with your dates filled in, arriving at Incheon. Flexible fares recommended.", + "flightSearch": "Open in Google Flights", + "consult": "Book a consultation", + "consultHint": "Copy the summary below and send it with your consultation request so the clinic can plan around your recovery dates.", + "copySummary": "Copy summary", + "copied": "Copied", + "shareLink": "Copy link", + "ics": "Save to calendar", + "print": "Print", + "map": "Map", + "readMore": "Full recovery timeline", + "affiliate": "Hotel and flight links open the booking sites. Where an affiliate link applies, this site may earn a small commission. Booking terms and payment follow each site.", + "medical": "This schedule is general information based on the clinic's published guidance. Your surgeon's instructions for your own case take priority.", + "km": "{km} km", + "walk": "walk", + "taxi": "taxi", + "indoor": "indoor", + "summaryTitle": "Recovery schedule summary", + "levelLabel": "Outing level", + "sourceLabel": "Source", + "reset": "Start over", + "provisionalNote": "Values marked \"Provisional\" are not in the clinic's published guidance and use general estimates. Confirm your own dates at consultation.", + "noRemoval": "No removal visit", + "h_hero": "Plan Your Recovery Stay", + "h_step1": "Language & Nationality", + "h_step2": "Procedure & Surgery Date", + "h_step3": "Your Free Time", + "h_step4": "Your Schedule", + "h_visa": "Length of Stay", + "h_rules": "During Recovery", + "h_days": "Day by Day", + "h_book": "Book & Save", + "h_hotels": "Hotels Near the Clinic", + "h_flights": "Flights", + "h_consult": "Consultation" + } +} diff --git a/supporters/src/data/procedures.json b/supporters/src/data/procedures.json new file mode 100644 index 0000000..4ec9dc7 --- /dev/null +++ b/supporters/src/data/procedures.json @@ -0,0 +1,989 @@ +{ + "_comment": "회복기 스케줄러 시술별 규칙표 (설계 v0.2 §13.4). 숫자마다 status 가 붙는다. clinic = 병원 공개 주의사항·애프터케어에 있는 값, provisional = 병원 확인 전 잠정값(화면에 배지 표시), null = 값 없음(화면에 '확인'). 병원 확인이 끝나면 status 를 clinic 으로 바꾸고 source 를 채운다.", + "clinicSources": [ + { + "id": "precautions", + "label": { + "ko": "뷰성형외과 홈페이지 · 수술 전·후 주의사항", + "en": "View Plastic Surgery · Pre/Post-operative precautions" + }, + "url": "https://www.viewclinic.com/system/precautions/", + "accessed": "2026-09-07" + }, + { + "id": "aftercare", + "label": { + "ko": "뷰성형외과 홈페이지 · 애프터케어", + "en": "View Plastic Surgery · Aftercare" + }, + "url": "https://www.viewclinic.com/system/after-care/", + "accessed": "2026-09-07" + } + ], + "levels": [ + { + "level": 0, + "label": { + "ko": "실내 안정", + "en": "Rest indoors" + }, + "desc": { + "ko": "병실이나 호텔에서 쉬는 날입니다. 필요한 것은 배달과 룸서비스로 해결합니다.", + "en": "Rest at the hospital or hotel. Use delivery and room service." + } + }, + { + "level": 1, + "label": { + "ko": "근거리 외출", + "en": "Short walks" + }, + "desc": { + "ko": "호텔 반경 500m 안에서 짧게 다녀옵니다. 도보 10분 이내.", + "en": "Short trips within 500 m of your hotel. Up to a 10-minute walk." + } + }, + { + "level": 2, + "label": { + "ko": "반나절 외출", + "en": "Half-day outing" + }, + "desc": { + "ko": "택시로 반경 3km 안 쇼핑·식사·가벼운 관광이 가능합니다.", + "en": "Shopping, dining and light sightseeing within 3 km by taxi." + } + }, + { + "level": 3, + "label": { + "ko": "자유 외출", + "en": "Full day out" + }, + "desc": { + "ko": "하루 단위 외출이 가능합니다. 시내 관광, 행사, 인파가 있는 곳도 담당 원장 안내 안에서 가능합니다.", + "en": "Full-day outings. City sightseeing and events, within your surgeon's guidance." + } + } + ], + "diets": { + "liquid": { + "label": { + "ko": "유동식", + "en": "Liquid diet" + }, + "desc": { + "ko": "건더기 없는 유동식만 드십니다.", + "en": "Liquids only, no solids." + } + }, + "soft": { + "label": { + "ko": "부드러운 음식", + "en": "Soft foods" + }, + "desc": { + "ko": "건더기가 있어도 부드러운 음식까지 가능합니다.", + "en": "Soft foods are fine. Avoid anything chewy or hard." + } + }, + "mild": { + "label": { + "ko": "자극 없는 일반식", + "en": "Regular, non-spicy" + }, + "desc": { + "ko": "맵고 짜거나 딱딱한 음식은 피합니다.", + "en": "Regular meals. Avoid spicy, salty or hard foods." + } + }, + "normal": { + "label": { + "ko": "일반식", + "en": "Regular diet" + }, + "desc": { + "ko": "식사 제한이 없습니다.", + "en": "No dietary restrictions." + } + } + }, + "constraints": { + "no-heavy-lifting": { + "ko": "무거운 짐과 팔을 많이 쓰는 동작을 피합니다. 쇼핑은 배송 서비스를 이용하세요.", + "en": "Avoid heavy bags and arm-heavy movement. Use delivery for shopping." + }, + "sports-bra": { + "ko": "스포츠브라를 1~3개월 유지합니다.", + "en": "Wear a sports bra for 1 to 3 months." + }, + "no-alcohol-30d": { + "ko": "음주와 흡연은 최소 1개월 이후입니다.", + "en": "No alcohol or smoking for at least 1 month." + }, + "no-sauna-60d": { + "ko": "사우나, 찜질방, 수영, 과한 운동은 약 2개월 후입니다.", + "en": "No sauna, spa, swimming or intense exercise for about 2 months." + }, + "compression-band": { + "ko": "압박밴드를 2~4주 착용합니다. 취침 시에는 하지 않습니다.", + "en": "Wear the compression band for 2 to 4 weeks, not while sleeping." + }, + "sleep-elevated": { + "ko": "머리를 심장보다 높게 하고 정면 자세로 잡니다. 1주 후부터 옆으로 누워도 됩니다.", + "en": "Sleep with your head above heart level, facing up. Side sleeping from week 2." + }, + "saline-gargle": { + "ko": "약 1개월간 생리식염수 가글을 꾸준히 합니다.", + "en": "Rinse with saline regularly for about 1 month." + }, + "no-hard-food-30d": { + "ko": "질기고 딱딱한 음식은 한 달간 피합니다.", + "en": "Avoid chewy or hard food for 1 month." + }, + "no-sun": { + "ko": "직사광선을 피합니다. 모자와 선글라스를 챙기세요.", + "en": "Avoid direct sun. Bring a hat and sunglasses." + }, + "mask": { + "ko": "외출 시 마스크를 쓰면 부기와 멍이 덜 보입니다.", + "en": "A mask helps cover swelling and bruising when out." + }, + "no-glasses": { + "ko": "안경 착용을 피합니다. 콘택트렌즈나 안경 없이 다닐 계획을 세우세요.", + "en": "Avoid glasses resting on your nose. Plan for contacts or going without." + }, + "no-lens": { + "ko": "콘택트렌즈는 담당 원장이 허용할 때까지 쓰지 않습니다.", + "en": "No contact lenses until your surgeon allows." + }, + "cold-compress": { + "ko": "초기에는 냉찜질, 이후 온찜질을 합니다.", + "en": "Cold compresses at first, then warm." + }, + "no-nose-blow": { + "ko": "코를 세게 풀거나 코 부위에 압력을 주지 않습니다.", + "en": "Do not blow your nose or press on the nose area." + }, + "no-massage": { + "ko": "수술 부위 마사지와 강한 압박을 피합니다.", + "en": "No massage or firm pressure on the surgical area." + }, + "no-hat-pressure": { + "ko": "이식 부위를 누르는 모자나 헬멧을 피합니다.", + "en": "Avoid hats or helmets that press on the graft area." + }, + "no-scratch": { + "ko": "가려워도 긁지 않습니다.", + "en": "Do not scratch, even if itchy." + }, + "driving": { + "ko": "수술 당일은 운전할 수 없습니다. 보호자 동반을 권합니다.", + "en": "No driving on surgery day. A companion is recommended." + } + }, + "procedures": [ + { + "id": "breast", + "label": { + "ko": "가슴성형", + "en": "Breast Augmentation" + }, + "group": { + "ko": "가슴", + "en": "Breast" + }, + "readMore": "/posts/breast-recovery-timeline", + "preOpDays": { + "value": 2, + "status": "provisional", + "note": { + "ko": "검진과 상담 1일, 여유 1일", + "en": "1 day for tests and consultation, 1 buffer day" + } + }, + "hospitalNights": { + "value": 1, + "status": "provisional" + }, + "stitchRemovalDay": { + "value": 7, + "range": [ + 5, + 10 + ], + "status": "clinic", + "source": "precautions", + "note": { + "ko": "실밥이 있으면 약 1주에 제거. 피부 접착제를 쓴 경우 실밥 없음", + "en": "Stitches, if any, removed at about 1 week. No stitches if skin adhesive was used" + } + }, + "earliestDepartureDay": { + "value": 10, + "status": "provisional" + }, + "outingLevels": [ + { + "fromDay": 0, + "level": 0, + "status": "clinic", + "source": "precautions", + "note": { + "ko": "부기는 2~3일째 가장 심합니다", + "en": "Swelling peaks on days 2 to 3" + } + }, + { + "fromDay": 3, + "level": 1, + "status": "clinic", + "source": "precautions", + "note": { + "ko": "3일 이후 붕대를 뗍니다", + "en": "Bandage removed after day 3" + } + }, + { + "fromDay": 7, + "level": 2, + "status": "provisional" + }, + { + "fromDay": 14, + "level": 3, + "status": "provisional" + } + ], + "diet": [ + { + "fromDay": 0, + "type": "normal", + "status": "clinic", + "source": "precautions" + } + ], + "constraints": [ + "driving", + "no-heavy-lifting", + "sports-bra", + "no-alcohol-30d", + "no-sauna-60d" + ], + "sources": [ + "precautions", + "aftercare" + ] + }, + { + "id": "contour", + "label": { + "ko": "안면윤곽·양악", + "en": "Facial Contouring / Jaw Surgery" + }, + "group": { + "ko": "얼굴뼈", + "en": "Facial bone" + }, + "readMore": "/posts/contour-recovery", + "preOpDays": { + "value": 2, + "status": "provisional", + "note": { + "ko": "3D-CT 촬영과 검진, 상담", + "en": "3D-CT, tests and consultation" + } + }, + "hospitalNights": { + "value": 1, + "status": "provisional" + }, + "stitchRemovalDay": { + "value": null, + "status": "clinic", + "source": "precautions", + "note": { + "ko": "입안 실은 녹는 실이라 보통 한 달 후 저절로 없어집니다. 별도 제거 방문 없음", + "en": "Intraoral stitches dissolve on their own in about a month. No removal visit" + }, + "display": { + "ko": "제거 방문 없음", + "en": "No removal visit" + } + }, + "earliestDepartureDay": { + "value": 14, + "status": "provisional" + }, + "outingLevels": [ + { + "fromDay": 0, + "level": 0, + "status": "clinic", + "source": "precautions", + "note": { + "ko": "부기는 3~4일까지 늘다가 가라앉습니다", + "en": "Swelling increases until day 3 to 4, then subsides" + } + }, + { + "fromDay": 5, + "level": 1, + "status": "clinic", + "source": "precautions", + "note": { + "ko": "5~7일 후 일상생활이 가능합니다", + "en": "Daily activities possible after day 5 to 7" + } + }, + { + "fromDay": 10, + "level": 2, + "status": "provisional" + }, + { + "fromDay": 21, + "level": 3, + "status": "provisional" + } + ], + "diet": [ + { + "fromDay": 0, + "type": "liquid", + "status": "clinic", + "source": "precautions" + }, + { + "fromDay": 7, + "type": "soft", + "status": "clinic", + "source": "precautions" + }, + { + "fromDay": 14, + "type": "mild", + "status": "clinic", + "source": "precautions" + }, + { + "fromDay": 30, + "type": "normal", + "status": "clinic", + "source": "precautions" + } + ], + "constraints": [ + "driving", + "compression-band", + "sleep-elevated", + "saline-gargle", + "no-hard-food-30d", + "no-alcohol-30d", + "no-sauna-60d", + "no-massage" + ], + "sources": [ + "precautions", + "aftercare" + ] + }, + { + "id": "eye", + "label": { + "ko": "눈성형", + "en": "Eyelid Surgery" + }, + "group": { + "ko": "눈", + "en": "Eyes" + }, + "readMore": "/posts/eye-canthoplasty-types", + "preOpDays": { + "value": 1, + "status": "provisional" + }, + "hospitalNights": { + "value": 0, + "status": "provisional" + }, + "stitchRemovalDay": { + "value": 6, + "range": [ + 5, + 7 + ], + "status": "provisional" + }, + "earliestDepartureDay": { + "value": 7, + "status": "provisional" + }, + "outingLevels": [ + { + "fromDay": 0, + "level": 0, + "status": "provisional" + }, + { + "fromDay": 2, + "level": 1, + "status": "provisional" + }, + { + "fromDay": 5, + "level": 2, + "status": "provisional" + }, + { + "fromDay": 10, + "level": 3, + "status": "provisional" + } + ], + "diet": [ + { + "fromDay": 0, + "type": "normal", + "status": "provisional" + } + ], + "constraints": [ + "driving", + "cold-compress", + "no-sun", + "no-lens", + "no-scratch", + "no-alcohol-30d" + ], + "sources": [] + }, + { + "id": "nose", + "label": { + "ko": "코성형", + "en": "Rhinoplasty" + }, + "group": { + "ko": "코", + "en": "Nose" + }, + "readMore": "/posts/functional-rhinoplasty-ent", + "preOpDays": { + "value": 2, + "status": "provisional" + }, + "hospitalNights": { + "value": 0, + "status": "provisional" + }, + "stitchRemovalDay": { + "value": 7, + "range": [ + 5, + 7 + ], + "status": "provisional", + "note": { + "ko": "부목과 실밥 제거", + "en": "Splint and stitch removal" + } + }, + "earliestDepartureDay": { + "value": 10, + "status": "provisional" + }, + "outingLevels": [ + { + "fromDay": 0, + "level": 0, + "status": "provisional" + }, + { + "fromDay": 3, + "level": 1, + "status": "provisional" + }, + { + "fromDay": 7, + "level": 2, + "status": "provisional" + }, + { + "fromDay": 14, + "level": 3, + "status": "provisional" + } + ], + "diet": [ + { + "fromDay": 0, + "type": "mild", + "status": "provisional" + }, + { + "fromDay": 14, + "type": "normal", + "status": "provisional" + } + ], + "constraints": [ + "driving", + "mask", + "no-glasses", + "no-nose-blow", + "no-sun", + "no-alcohol-30d", + "no-sauna-60d" + ], + "sources": [] + }, + { + "id": "fat", + "label": { + "ko": "지방성형", + "en": "Liposuction / Fat Grafting" + }, + "group": { + "ko": "지방", + "en": "Fat" + }, + "readMore": null, + "preOpDays": { + "value": 1, + "status": "provisional" + }, + "hospitalNights": { + "value": 0, + "status": "provisional" + }, + "stitchRemovalDay": { + "value": 7, + "status": "provisional" + }, + "earliestDepartureDay": { + "value": 7, + "status": "provisional" + }, + "outingLevels": [ + { + "fromDay": 0, + "level": 0, + "status": "provisional" + }, + { + "fromDay": 2, + "level": 1, + "status": "provisional" + }, + { + "fromDay": 5, + "level": 2, + "status": "provisional" + }, + { + "fromDay": 14, + "level": 3, + "status": "provisional" + } + ], + "diet": [ + { + "fromDay": 0, + "type": "normal", + "status": "provisional" + } + ], + "constraints": [ + "driving", + "compression-band", + "no-massage", + "no-alcohol-30d", + "no-sauna-60d" + ], + "sources": [] + }, + { + "id": "lifting", + "label": { + "ko": "안티에이징·리프팅", + "en": "Anti-aging / Face Lift" + }, + "group": { + "ko": "리프팅", + "en": "Lifting" + }, + "readMore": "/posts/thread-lift-vs-facelift", + "preOpDays": { + "value": 1, + "status": "provisional" + }, + "hospitalNights": { + "value": 0, + "status": "provisional" + }, + "stitchRemovalDay": { + "value": 7, + "status": "provisional" + }, + "earliestDepartureDay": { + "value": 10, + "status": "provisional" + }, + "outingLevels": [ + { + "fromDay": 0, + "level": 0, + "status": "provisional" + }, + { + "fromDay": 3, + "level": 1, + "status": "provisional" + }, + { + "fromDay": 7, + "level": 2, + "status": "provisional" + }, + { + "fromDay": 14, + "level": 3, + "status": "provisional" + } + ], + "diet": [ + { + "fromDay": 0, + "type": "soft", + "status": "provisional" + }, + { + "fromDay": 7, + "type": "normal", + "status": "provisional" + } + ], + "constraints": [ + "driving", + "mask", + "no-massage", + "no-sun", + "no-alcohol-30d", + "no-sauna-60d" + ], + "sources": [] + }, + { + "id": "skin", + "label": { + "ko": "피부과 시술", + "en": "Dermatology Treatment" + }, + "group": { + "ko": "피부", + "en": "Skin" + }, + "readMore": null, + "preOpDays": { + "value": 1, + "status": "provisional" + }, + "hospitalNights": { + "value": 0, + "status": "provisional" + }, + "stitchRemovalDay": { + "value": null, + "status": "provisional", + "note": { + "ko": "실밥 없음", + "en": "No stitches" + }, + "display": { + "ko": "제거 방문 없음", + "en": "No removal visit" + } + }, + "earliestDepartureDay": { + "value": 2, + "status": "provisional" + }, + "outingLevels": [ + { + "fromDay": 0, + "level": 1, + "status": "provisional" + }, + { + "fromDay": 1, + "level": 2, + "status": "provisional" + }, + { + "fromDay": 3, + "level": 3, + "status": "provisional" + } + ], + "diet": [ + { + "fromDay": 0, + "type": "normal", + "status": "provisional" + } + ], + "constraints": [ + "no-sun", + "no-sauna-60d" + ], + "sources": [] + }, + { + "id": "hair", + "label": { + "ko": "모발이식", + "en": "Hair Transplant" + }, + "group": { + "ko": "모발", + "en": "Hair" + }, + "readMore": null, + "preOpDays": { + "value": 1, + "status": "provisional" + }, + "hospitalNights": { + "value": 0, + "status": "provisional" + }, + "stitchRemovalDay": { + "value": null, + "status": "provisional", + "note": { + "ko": "채취 방식에 따라 다릅니다", + "en": "Depends on the harvesting method" + } + }, + "earliestDepartureDay": { + "value": 3, + "status": "provisional" + }, + "outingLevels": [ + { + "fromDay": 0, + "level": 0, + "status": "provisional" + }, + { + "fromDay": 1, + "level": 1, + "status": "provisional" + }, + { + "fromDay": 3, + "level": 2, + "status": "provisional" + }, + { + "fromDay": 7, + "level": 3, + "status": "provisional" + } + ], + "diet": [ + { + "fromDay": 0, + "type": "normal", + "status": "provisional" + } + ], + "constraints": [ + "no-sun", + "no-hat-pressure", + "no-scratch", + "no-alcohol-30d", + "no-sauna-60d" + ], + "sources": [] + } + ], + "nationalities": { + "_comment": "무비자 체류 한도(일). 2026-09 기준 확인 필요. null 은 사전 비자 필요. 화면은 항상 대한민국 비자포털 링크와 함께 보여준다.", + "portal": "https://www.visa.go.kr", + "list": [ + { + "code": "US", + "label": { + "ko": "미국", + "en": "United States" + }, + "visaFreeDays": 90, + "longHaul": true + }, + { + "code": "CA", + "label": { + "ko": "캐나다", + "en": "Canada" + }, + "visaFreeDays": 180, + "longHaul": true + }, + { + "code": "GB", + "label": { + "ko": "영국", + "en": "United Kingdom" + }, + "visaFreeDays": 180, + "longHaul": true + }, + { + "code": "AU", + "label": { + "ko": "호주", + "en": "Australia" + }, + "visaFreeDays": 90, + "longHaul": true + }, + { + "code": "JP", + "label": { + "ko": "일본", + "en": "Japan" + }, + "visaFreeDays": 90, + "longHaul": false + }, + { + "code": "TW", + "label": { + "ko": "대만", + "en": "Taiwan" + }, + "visaFreeDays": 90, + "longHaul": false + }, + { + "code": "HK", + "label": { + "ko": "홍콩", + "en": "Hong Kong" + }, + "visaFreeDays": 90, + "longHaul": false + }, + { + "code": "SG", + "label": { + "ko": "싱가포르", + "en": "Singapore" + }, + "visaFreeDays": 90, + "longHaul": false + }, + { + "code": "TH", + "label": { + "ko": "태국", + "en": "Thailand" + }, + "visaFreeDays": 90, + "longHaul": false + }, + { + "code": "MY", + "label": { + "ko": "말레이시아", + "en": "Malaysia" + }, + "visaFreeDays": 90, + "longHaul": false + }, + { + "code": "AE", + "label": { + "ko": "아랍에미리트", + "en": "United Arab Emirates" + }, + "visaFreeDays": 90, + "longHaul": true + }, + { + "code": "RU", + "label": { + "ko": "러시아", + "en": "Russia" + }, + "visaFreeDays": 60, + "longHaul": true + }, + { + "code": "KZ", + "label": { + "ko": "카자흐스탄", + "en": "Kazakhstan" + }, + "visaFreeDays": 30, + "longHaul": true + }, + { + "code": "CN", + "label": { + "ko": "중국", + "en": "China" + }, + "visaFreeDays": null, + "longHaul": false + }, + { + "code": "VN", + "label": { + "ko": "베트남", + "en": "Vietnam" + }, + "visaFreeDays": null, + "longHaul": false + }, + { + "code": "ID", + "label": { + "ko": "인도네시아", + "en": "Indonesia" + }, + "visaFreeDays": null, + "longHaul": false + }, + { + "code": "PH", + "label": { + "ko": "필리핀", + "en": "Philippines" + }, + "visaFreeDays": null, + "longHaul": false + }, + { + "code": "MN", + "label": { + "ko": "몽골", + "en": "Mongolia" + }, + "visaFreeDays": null, + "longHaul": false + }, + { + "code": "OTHER", + "label": { + "ko": "그 외", + "en": "Other" + }, + "visaFreeDays": null, + "longHaul": true + } + ] + } +} diff --git a/supporters/src/layouts/Base.astro b/supporters/src/layouts/Base.astro index 1ace83c..15b26f7 100644 --- a/supporters/src/layouts/Base.astro +++ b/supporters/src/layouts/Base.astro @@ -11,15 +11,17 @@ interface Props { ogImage?: string; type?: 'website' | 'article'; noindex?: boolean; + lang?: 'ko' | 'en'; + alternates?: { lang: string; href: string }[]; } -const { title, description, jsonLd, ogImage = S.buildingImage?.src || '/favicon.svg', type = 'website', noindex = false } = Astro.props; +const { title, description, jsonLd, ogImage = S.buildingImage?.src || '/favicon.svg', type = 'website', noindex = false, lang = 'ko', alternates = [] } = Astro.props; const site = Astro.site?.toString().replace(/\/$/, '') ?? ''; const cleanPath = Astro.url.pathname.replace(/\/index\.html$/, '/').replace(/\.html$/, ''); const canonical = (new URL(cleanPath, Astro.site).toString().replace(/\/$/, '') || site); const ld = jsonLd ? JSON.stringify({ '@context': 'https://schema.org', ...(Array.isArray(jsonLd) ? { '@graph': jsonLd } : jsonLd) }) : null; --- - + @@ -33,7 +35,8 @@ const ld = jsonLd ? JSON.stringify({ '@context': 'https://schema.org', ...(Array - + + {alternates.map((a) => )} @@ -51,6 +54,7 @@ const ld = jsonLd ? JSON.stringify({ '@context': 'https://schema.org', ...(Array 의료진 영상 뉴스룸 방문 안내 + 회복 일정 매체 소개 상담 예약 @@ -80,7 +84,7 @@ const ld = jsonLd ? JSON.stringify({ '@context': 'https://schema.org', ...(Array

{SITE_NAME} · {SITE_TAGLINE}

이 매체는 {v(f.shortName, '병원')}의 지원을 받아 서포터즈가 운영합니다. 글의 의학적 내용은 {v(f.shortName, '병원')} 담당 원장의 검토를 거쳐 표시하며, 검토 전 글은 "의학 검토 대기"로 표시합니다. 편집 책임 {ed.name}.

{fact.sideEffectNotice}

-

병원 정보 · 방문 안내 · 이 사이트에 대해 · 정정 기록 · 정정 요청 · 편집 기준 (운영자용)

+

병원 정보 · 방문 안내 · 회복 일정 · Recovery Planner · 이 사이트에 대해 · 정정 기록 · 정정 요청 · 편집 기준 (운영자용)

{has(S.logoFoot?.src) &&

{S.logoFoot.alt}

} diff --git a/supporters/src/lib/plan.ts b/supporters/src/lib/plan.ts new file mode 100644 index 0000000..efb62fa --- /dev/null +++ b/supporters/src/lib/plan.ts @@ -0,0 +1,173 @@ +// 회복기 스케줄러 계산 로직 (설계 v0.2 §13.3·§13.7·§13.8). 화면과 분리된 순수 함수만 둔다. +// 입력: 시술 id, 수술일, 국적, 선호. 출력: 날짜별 외출 등급·식사·추천과 예약 링크. 서버 호출 없음. +// 검증: node --experimental-strip-types scripts/gate/plan.test.mjs + +export type Lang = 'ko' | 'en'; +export type L10n = Record; +export type Status = 'clinic' | 'provisional'; +export interface Valued { value: number | null; range?: [number, number]; status: Status; source?: string; note?: L10n } +export interface LevelStep { fromDay: number; level: number; status: Status; source?: string; note?: L10n } +export interface DietStep { fromDay: number; type: 'liquid' | 'soft' | 'mild' | 'normal'; status: Status; source?: string } +export interface Procedure { + id: string; label: L10n; group: L10n; readMore: string | null; + preOpDays: Valued; hospitalNights: Valued; stitchRemovalDay: Valued; earliestDepartureDay: Valued; + outingLevels: LevelStep[]; diet: DietStep[]; constraints: string[]; sources: string[]; +} +export interface Place { + id: string; name: L10n; category: string; minLevel: number; dietOk?: string[]; distanceKm: number; indoor: boolean; + area: L10n; note: L10n; mapQuery: string | null; bookingQuery?: string; verify: boolean; +} +export interface Nationality { code: string; label: L10n; visaFreeDays: number | null; longHaul: boolean } + +export interface PlanInput { procedure: Procedure; surgeryDate: string; nationality?: Nationality | null; prefs: string[] } +export interface PlanDay { date: string; offset: number; level: number; diet: DietStep['type']; picks: Place[]; isSurgery: boolean; isHospital: boolean; isStitch: boolean; isArrival: boolean; isDeparture: boolean } +export interface Plan { + arriveBy: string; surgeryDate: string; dischargeDate: string | null; stitchDate: string | null; earliestDeparture: string | null; + stayNights: number | null; preOpDays: number; days: PlanDay[]; +} + +const DAY = 86_400_000; +export const addDays = (iso: string, n: number): string => { + const d = new Date(`${iso}T00:00:00Z`); + return new Date(d.getTime() + n * DAY).toISOString().slice(0, 10); +}; +export const isIsoDate = (s: unknown): s is string => typeof s === 'string' && /^\d{4}-\d{2}-\d{2}$/.test(s) && !Number.isNaN(Date.parse(`${s}T00:00:00Z`)); + +/** fromDay 오름차순 단계표에서 offset 에 해당하는 단계. 첫 단계 이전이면 첫 단계. */ +function stepAt(steps: T[], offset: number): T { + const sorted = [...steps].sort((a, b) => a.fromDay - b.fromDay); + let cur = sorted[0]; + for (const s of sorted) if (offset >= s.fromDay) cur = s; + return cur; +} + +/** 장거리 국적은 시차 회복으로 준비일에 1일을 더한다 (§13.3). */ +export function effectivePreOpDays(p: Procedure, nat?: Nationality | null): number { + const base = p.preOpDays.value ?? 2; + return base + (nat?.longHaul ? 1 : 0); +} + +/** + * 장소 점수. 선호 일치가 가장 크고, 등급이 딱 맞을수록, 가까울수록 높다. + * 선호가 비어 있으면 카테고리 무관하게 거리와 등급만 본다. + */ +export function scorePlace(place: Place, level: number, diet: DietStep['type'], prefs: string[]): number { + if (place.category === 'hotel') return -1; + if (place.minLevel > level) return -1; + if (place.category === 'food' && place.dietOk && !place.dietOk.includes(diet)) return -1; + let s = 0; + if (prefs.length === 0 || prefs.includes(place.category)) s += 3; + if (place.category === 'essentials' || place.category === 'care') s += 1; // 회복에 직접 필요한 것은 기본 가점 + s += 2 - Math.min(2, level - place.minLevel); // 오늘 등급에 맞는 곳을 우선 + s += Math.max(0, 1.5 - place.distanceKm / 4); // 가까울수록 가점(최대 1.5) + return s; +} + +/** 하루에 최대 3곳. 직전 2일에 나온 곳은 뒤로 미뤄 날마다 다른 곳이 나오게 한다. */ +export function pickForDay(places: Place[], level: number, diet: DietStep['type'], prefs: string[], recent: Set, max = 3): Place[] { + return places + .map((p) => ({ p, s: scorePlace(p, level, diet, prefs) - (recent.has(p.id) ? 2.5 : 0) })) + .filter((x) => x.s >= 0) + .sort((a, b) => b.s - a.s || a.p.distanceKm - b.p.distanceKm) + .slice(0, max) + .map((x) => x.p); +} + +export function buildPlan(input: PlanInput, places: Place[]): Plan { + const { procedure: p, surgeryDate, nationality, prefs } = input; + const preOp = effectivePreOpDays(p, nationality); + const arriveBy = addDays(surgeryDate, -preOp); + const nights = p.hospitalNights.value; + const dischargeDate = nights === null ? null : addDays(surgeryDate, nights); + const stitchDate = p.stitchRemovalDay.value === null ? null : addDays(surgeryDate, p.stitchRemovalDay.value); + const dep = p.earliestDepartureDay.value; + const earliestDeparture = dep === null ? null : addDays(surgeryDate, dep); + const lastOffset = dep ?? Math.max(14, ...p.outingLevels.map((l) => l.fromDay)); + const days: PlanDay[] = []; + const recent: string[] = []; + for (let off = -preOp; off <= lastOffset; off++) { + const date = addDays(surgeryDate, off); + const isHospital = nights !== null && off >= 0 && off < nights; + const pre = off < 0; + const level = pre ? 2 : isHospital ? 0 : stepAt(p.outingLevels, off).level; + const diet = pre ? 'normal' : stepAt(p.diet, off).type; + const recentSet = new Set(recent.slice(-6)); + const picks = level === 0 || off === 0 ? [] : pickForDay(places, level, diet, prefs, recentSet); + recent.push(...picks.map((x) => x.id)); + days.push({ date, offset: off, level, diet, picks, isSurgery: off === 0, isHospital, isStitch: stitchDate === date, isArrival: off === -preOp, isDeparture: earliestDeparture === date }); + } + return { arriveBy, surgeryDate, dischargeDate, stitchDate, earliestDeparture, stayNights: dep === null ? null : preOp + dep, preOpDays: preOp, days }; +} + +// ---------- 예약 링크 (§13.7). 검색 결과 페이지 패턴만 쓴다. 제휴 ID 는 확정 후 aid 파라미터로 붙인다. ---------- +export function bookingUrl(query: string, checkin: string, checkout: string, lang: Lang, aid?: string): string { + const u = new URL('https://www.booking.com/searchresults.html'); + u.searchParams.set('ss', query); + u.searchParams.set('checkin', checkin); + u.searchParams.set('checkout', checkout); + u.searchParams.set('group_adults', '1'); + u.searchParams.set('no_rooms', '1'); + u.searchParams.set('lang', lang === 'ko' ? 'ko' : 'en-us'); + if (aid) u.searchParams.set('aid', aid); + return u.toString(); +} +export function googleFlightsUrl(arrive: string, depart: string, lang: Lang): string { + const q = `Flights to ICN on ${arrive} returning ${depart}`; + return `https://www.google.com/travel/flights?hl=${lang}&q=${encodeURIComponent(q)}`; +} +export function mapUrl(query: string): string { + return `https://www.google.com/maps/search/?api=1&query=${encodeURIComponent(query)}`; +} + +// ---------- 공유 URL 해시 (서버 미전송) ---------- +export interface HashState { p?: string; d?: string; l?: Lang; n?: string; pref?: string[] } +export function encodeHash(s: HashState): string { + const q = new URLSearchParams(); + if (s.p) q.set('p', s.p); + if (s.d) q.set('d', s.d); + if (s.l) q.set('l', s.l); + if (s.n) q.set('n', s.n); + if (s.pref?.length) q.set('pref', s.pref.join(',')); + return q.toString(); +} +export function decodeHash(hash: string): HashState { + const q = new URLSearchParams(hash.replace(/^#/, '')); + const l = q.get('l'); + return { + p: q.get('p') ?? undefined, + d: isIsoDate(q.get('d')) ? (q.get('d') as string) : undefined, + l: l === 'ko' || l === 'en' ? l : undefined, + n: q.get('n') ?? undefined, + pref: (q.get('pref') ?? '').split(',').filter(Boolean), + }; +} + +// ---------- ICS (종일 일정 4개: 입국·수술·실밥·출국) ---------- +export function buildIcs(plan: Plan, titles: { arrive: string; surgery: string; stitch: string; departure: string }, calName: string): string { + const ev = (date: string, title: string, uid: string) => [ + 'BEGIN:VEVENT', `UID:${uid}@view-supporters`, `DTSTAMP:${new Date().toISOString().replace(/[-:]/g, '').slice(0, 15)}Z`, + `DTSTART;VALUE=DATE:${date.replace(/-/g, '')}`, `DTEND;VALUE=DATE:${addDays(date, 1).replace(/-/g, '')}`, `SUMMARY:${title}`, 'END:VEVENT', + ]; + const lines = ['BEGIN:VCALENDAR', 'VERSION:2.0', 'PRODID:-//View Supporters//Recovery Planner//KO', `X-WR-CALNAME:${calName}`]; + lines.push(...ev(plan.arriveBy, titles.arrive, `arrive-${plan.arriveBy}`)); + lines.push(...ev(plan.surgeryDate, titles.surgery, `surgery-${plan.surgeryDate}`)); + if (plan.stitchDate) lines.push(...ev(plan.stitchDate, titles.stitch, `stitch-${plan.stitchDate}`)); + if (plan.earliestDeparture) lines.push(...ev(plan.earliestDeparture, titles.departure, `depart-${plan.earliestDeparture}`)); + lines.push('END:VCALENDAR'); + return lines.join('\r\n'); +} + +/** 상담 신청에 붙여 보낼 요약문. */ +export function summaryText(plan: Plan, p: Procedure, lang: Lang, t: Record): string { + const pending = t.pending; + const row = (k: string, v: string | null) => `${k}: ${v ?? pending}`; + return [ + `${t.summaryTitle} (${p.label[lang]})`, + row(t.arriveBy, plan.arriveBy), + row(t.surgery, plan.surgeryDate), + row(t.hospital, p.hospitalNights.value === null ? null : p.hospitalNights.value === 0 ? t.hospitalNone : t.hospitalNights.replace('{n}', String(p.hospitalNights.value))), + row(t.stitch, plan.stitchDate), + row(t.departure, plan.earliestDeparture), + row(t.stayNights, plan.stayNights === null ? null : t.nights.replace('{n}', String(plan.stayNights))), + ].join('\n'); +} diff --git a/supporters/src/pages/en/plan.astro b/supporters/src/pages/en/plan.astro new file mode 100644 index 0000000..fd0f2e9 --- /dev/null +++ b/supporters/src/pages/en/plan.astro @@ -0,0 +1,16 @@ +--- +// Recovery planner, English page. Body lives in components/Planner.astro. Korean page is /plan. +import Base from '../../layouts/Base.astro'; +import Planner from '../../components/Planner.astro'; +import { fact, SITE_NAME, clinicSchema, v } from '../../lib'; +const f = fact as Record; +const clinicEn = 'View Plastic Surgery'; +const site = Astro.site!.toString().replace(/\/$/, ''); +const ld = [ + { '@type': 'WebPage', '@id': `${site}/en/plan#page`, name: `${clinicEn} Recovery Planner`, url: `${site}/en/plan`, inLanguage: 'en', isPartOf: { '@id': `${site}/#website` }, about: { '@id': `${fact.url}/#clinic` }, publisher: { '@type': 'Organization', name: SITE_NAME, url: site } }, + clinicSchema(site), +]; +--- + + + diff --git a/supporters/src/pages/plan.astro b/supporters/src/pages/plan.astro new file mode 100644 index 0000000..013ee36 --- /dev/null +++ b/supporters/src/pages/plan.astro @@ -0,0 +1,16 @@ +--- +// 회복기 스케줄러 한국어 페이지. 본문은 components/Planner.astro. 영어 페이지는 /en/plan. +import Base from '../layouts/Base.astro'; +import Planner from '../components/Planner.astro'; +import { fact, SITE_NAME, clinicSchema, v } from '../lib'; +const f = fact as Record; +const clinic = v(f.shortName, '병원'); +const site = Astro.site!.toString().replace(/\/$/, ''); +const ld = [ + { '@type': 'WebPage', '@id': `${site}/plan#page`, name: `${clinic} 회복 일정 플래너`, url: `${site}/plan`, inLanguage: 'ko', isPartOf: { '@id': `${site}/#website` }, about: { '@id': `${fact.url}/#clinic` }, publisher: { '@type': 'Organization', name: SITE_NAME, url: site } }, + clinicSchema(site), +]; +--- + + + diff --git a/supporters/src/pages/sitemap.xml.ts b/supporters/src/pages/sitemap.xml.ts index ee86b2a..a357bd2 100644 --- a/supporters/src/pages/sitemap.xml.ts +++ b/supporters/src/pages/sitemap.xml.ts @@ -11,6 +11,8 @@ export const GET: APIRoute = async ({ site }) => { { loc: `${base}/posts`, lastmod: today }, { loc: `${base}/clinic`, lastmod: today }, { loc: `${base}/visit`, lastmod: today }, + { loc: `${base}/plan`, lastmod: today }, + { loc: `${base}/en/plan`, lastmod: today }, { loc: `${base}/newsroom`, lastmod: today }, { loc: `${base}/videos`, lastmod: today }, { loc: `${base}/about`, lastmod: today }, diff --git a/supporters/src/pages/visit.astro b/supporters/src/pages/visit.astro index f301bfc..398cdae 100644 --- a/supporters/src/pages/visit.astro +++ b/supporters/src/pages/visit.astro @@ -2,6 +2,7 @@ // 방문 안내. 병원 정보(/clinic)가 사실 표라면 이 페이지는 "예약하고, 찾아가고, 도착해서 뭘 하는지"의 행동 순서다. 값은 모두 factSheet.json에서 가져온다. import Base from '../layouts/Base.astro'; import Gallery from '../components/Gallery.astro'; +import '../styles/plan.css'; import { fact, site as S, SITE_NAME, clinicSchema, reservationUrl, v, has } from '../lib'; const f = fact as Record; const clinic = v(f.shortName, '병원'); @@ -36,6 +37,12 @@ const ld = [

4. 도착하면

{v(S.arrivalGuide, `도착 후 동선은 병원 확인 뒤 싣습니다. ${v(null)}.`)} 층별 구성 전체는 병원 정보에 있습니다.

+
+

외국에서 오신다면: 회복 일정 플래너

+

시술과 수술일을 고르면 입국 권장일, 출국 가능 최소일, 회복기 동안 외출할 수 있는 날과 갈 만한 곳, 병원 근처 숙소를 한 화면에서 정리해 드립니다. 영어 화면도 있습니다.

+ 일정표 만들기 +
+

5. 상담 전에 읽어볼 글

    {(S.visitReads ?? []).map((r) =>
  • {r.title} {r.note}
  • )} diff --git a/supporters/src/styles/plan.css b/supporters/src/styles/plan.css new file mode 100644 index 0000000..d95ec1c --- /dev/null +++ b/supporters/src/styles/plan.css @@ -0,0 +1,109 @@ +/* 회복기 스케줄러 (/plan). global.css 토큰만 쓴다. 흰 배경 위 강조는 인디고(#1D0024), 연보라는 다크 배경 전용. */ +:root { --indigo: #1D0024; } +.plan-hero { padding: 1.6rem 0 0.6rem; } +.plan-hero h1 { font-size: 2.6rem; font-weight: 700; letter-spacing: 0; line-height: 1.15; color: var(--primary-900); } +.plan-hero .lede { color: var(--slate-600); font-size: 1rem; max-width: 760px; margin: 0.6rem 0 0; } +.plan-hero .lede strong { color: var(--indigo); } +.plan-hero .lang { display: inline-flex; gap: 0.35rem; margin-bottom: 0.7rem; } +.plan-hero .lang a, .plan-steps .lang a, .plan-hero .lang button, .plan-steps .lang button { text-decoration: none; border: 1px solid var(--slate-200); background: #fff; color: var(--slate-600); border-radius: 999px; padding: 0.35rem 0.9rem; font: inherit; font-size: 0.85rem; font-weight: 600; cursor: pointer; } +.plan-hero .lang a.on, .plan-steps .lang a.on, .plan-hero .lang button.on, .plan-steps .lang button.on { background: var(--primary-900); color: #fff; border-color: var(--primary-900); } + +.plan-steps { display: grid; gap: 1.1rem; margin: 0.8rem 0 2rem; } +.step { background: #fff; border: 1px solid var(--slate-100); border-radius: 16px; padding: 1.4rem 1.6rem; box-shadow: var(--shadow); } +.phead { margin-bottom: 0.9rem; } +.phead .eyebrow { margin-bottom: 0.25rem; font-size: 0.72rem; } +.phead h2 { margin: 0; font-size: 1.5rem; font-weight: 700; color: var(--primary-900); letter-spacing: 0; } +.phead .sub { margin: 0.15rem 0 0; font-size: 0.95rem; color: var(--slate-600); } +.h3sub { font-size: 0.88rem; color: var(--slate-600); margin: -0.2rem 0 0.4rem; } +.step .hint { font-size: 0.88rem; color: var(--slate-500); margin: 0.5rem 0 0; } +.field { display: grid; gap: 0.35rem; margin-top: 0.8rem; } +.field label { font-size: 0.85rem; font-weight: 600; color: var(--slate-600); } +.field select, .field input[type="date"] { font: inherit; font-size: 1rem; padding: 0.6rem 0.8rem; border: 1px solid var(--slate-200); border-radius: 12px; background: #fff; color: var(--indigo); max-width: 320px; } +.field-row { display: grid; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); gap: 1rem; } + +.proc-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); gap: 0.6rem; } +.proc-grid button { text-align: left; border: 1px solid var(--slate-200); background: #fff; border-radius: 14px; padding: 0.8rem 0.9rem; font: inherit; cursor: pointer; color: var(--indigo); display: grid; gap: 0.15rem; } +.proc-grid button small { color: var(--slate-500); font-size: 0.75rem; letter-spacing: 0.04em; text-transform: uppercase; font-family: Inter, Pretendard, sans-serif; } +.proc-grid button b { font-size: 0.97rem; font-weight: 700; } +.proc-grid button.on { border-color: var(--accent); background: var(--st-good-bg); box-shadow: 0 0 0 2px rgba(108,92,231,0.25) inset; } + +.pref-row { display: flex; flex-wrap: wrap; gap: 0.5rem; } +.pref-row button { border: 1px solid var(--slate-200); background: #fff; color: var(--indigo); border-radius: 999px; padding: 0.5rem 1rem; font: inherit; font-size: 0.92rem; font-weight: 600; cursor: pointer; } +.pref-row button.on { background: linear-gradient(to right, var(--grad-start), var(--grad-end)); color: #fff; border-color: transparent; } + +.plan-actions { display: flex; flex-wrap: wrap; gap: 0.6rem; align-items: center; margin-top: 1rem; } +.btn.small { padding: 0.55rem 1rem; font-size: 0.88rem; } +.btn.ghost { background: #fff; color: var(--indigo); border: 1px solid var(--slate-200); } +button.btn { font: inherit; cursor: pointer; border: 0; } + +/* 결과 */ +.plan-result { display: grid; gap: 1.5rem; margin-bottom: 3rem; } +.kpi-row { display: grid; grid-template-columns: repeat(auto-fit, minmax(160px, 1fr)); gap: 0.8rem; } +.kpi-row .k { background: #fff; border: 1px solid var(--slate-100); border-radius: 16px; padding: 1rem 1.1rem; box-shadow: var(--shadow); display: grid; gap: 0.25rem; align-content: start; } +.kpi-row .k > span { font-size: 0.74rem; color: var(--slate-500); font-weight: 600; letter-spacing: 0.02em; } +.kpi-row .k b { font-size: 1.15rem; color: var(--indigo); line-height: 1.2; display: flex; flex-wrap: wrap; align-items: baseline; gap: 0.35rem; } +.kpi-row .k b .num { font-family: "Playfair Display", Georgia, serif; font-size: 1.45rem; font-weight: 700; letter-spacing: -0.01em; color: var(--primary-900); } +.kpi-row .k b .wd { font-size: 0.82rem; font-weight: 600; color: var(--slate-500); } +.kpi-row .k small { font-size: 0.75rem; color: var(--slate-500); line-height: 1.5; } +.kpi-row .k .tag { white-space: normal; margin-right: 0.2rem; } +.tag { display: inline-block; font-size: 0.72rem; font-weight: 700; padding: 0.12rem 0.55rem; border-radius: 999px; border: 1px solid transparent; vertical-align: middle; white-space: nowrap; } +.tag.prov { background: var(--st-warning-bg); color: var(--st-warning-text); border-color: var(--st-warning-border); } +.tag.clinic { background: var(--st-good-bg); color: var(--st-good-text); border-color: var(--st-good-border); } +.tag.l0 { background: var(--st-critical-bg); color: var(--st-critical-text); border-color: var(--st-critical-border); } +.tag.l1 { background: var(--st-warning-bg); color: var(--st-warning-text); border-color: var(--st-warning-border); } +.tag.l2 { background: var(--st-info-bg); color: var(--st-info-text); border-color: var(--st-info-border); } +.tag.l3 { background: var(--st-good-bg); color: var(--st-good-text); border-color: var(--st-good-border); } +.tag.diet { background: var(--slate-100); color: var(--slate-700); } + +.pcard { background: #fff; border: 1px solid var(--slate-100); border-radius: 16px; padding: 1.3rem 1.5rem; box-shadow: var(--shadow); } +.pcard h3 { margin: 1.2rem 0 0.35rem; font-size: 1.15rem; color: var(--primary-900); font-weight: 700; } +.pcard p { margin: 0 0 0.6rem; font-size: 0.95rem; } +.pcard ul { margin: 0.3rem 0 0; padding-left: 1.2rem; font-size: 0.93rem; } +.pcard.warn { background: var(--st-warning-bg); border-color: var(--st-warning-border); } +.pcard.warn p, .pcard.warn h2, .pcard.warn .eyebrow { color: var(--st-warning-text); } +.pcard.ok { background: var(--st-good-bg); border-color: var(--st-good-border); } +.pcard.ok p, .pcard.ok h2, .pcard.ok .eyebrow { color: var(--st-good-text); } + +.legend { display: flex; flex-wrap: wrap; gap: 0.5rem 1rem; font-size: 0.85rem; color: var(--slate-600); margin-bottom: 0.8rem; } +.legend span { display: inline-flex; align-items: center; gap: 0.35rem; } + +.timeline { display: grid; gap: 0.5rem; } +.day { display: grid; grid-template-columns: 150px 1fr; gap: 0.6rem 1.2rem; align-items: start; padding: 0.85rem 1rem; border-radius: 14px; border: 1px solid var(--slate-100); background: #fff; } +.day.mark { border-color: var(--st-good-border); background: #FBFAFF; } +.day .d { display: grid; gap: 0.15rem; } +.day .d b { color: var(--indigo); font-size: 0.98rem; } +.day .d span { font-size: 0.78rem; color: var(--slate-500); } +.day .d .ev { font-size: 0.78rem; font-weight: 700; color: var(--grad-start); } +.day .body { display: grid; gap: 0.45rem; } +.day .tags { display: flex; flex-wrap: wrap; gap: 0.35rem; align-items: center; } +.day .tags .desc { font-size: 0.85rem; color: var(--slate-600); } +.day .picks { display: flex; flex-wrap: wrap; gap: 0.4rem; } +.day .picks a, .day .picks span.pick { display: inline-flex; align-items: center; gap: 0.35rem; border: 1px solid var(--slate-200); border-radius: 999px; padding: 0.3rem 0.75rem; font-size: 0.85rem; color: var(--indigo); background: #fff; } +.day .picks a:hover { border-color: var(--accent); text-decoration: none; } +.day .picks small { color: var(--slate-500); font-size: 0.72rem; } +.day .rest { font-size: 0.88rem; color: var(--slate-500); } +@media (max-width: 640px) { .day { grid-template-columns: 1fr; } } + +.hotel-list { display: grid; grid-template-columns: repeat(auto-fill, minmax(240px, 1fr)); gap: 0.8rem; } +.hotel { border: 1px solid var(--slate-100); border-radius: 14px; padding: 0.95rem 1.05rem; display: grid; gap: 0.3rem; background: #fff; } +.hotel b { color: var(--indigo); font-size: 0.98rem; } +.hotel small { color: var(--slate-500); font-size: 0.8rem; } +.hotel p { font-size: 0.88rem; margin: 0; color: var(--slate-600); } +.hotel .links { display: flex; gap: 0.5rem; flex-wrap: wrap; margin-top: 0.3rem; } +.hotel .links a { font-size: 0.85rem; font-weight: 600; } + +.fine { font-size: 0.82rem; color: var(--slate-500); } +.sources-list { font-size: 0.85rem; color: var(--slate-600); } +.toast { position: fixed; left: 50%; bottom: 2rem; transform: translateX(-50%); background: var(--primary-900); color: #fff; padding: 0.6rem 1.1rem; border-radius: 999px; font-size: 0.9rem; opacity: 0; transition: opacity 0.2s; pointer-events: none; z-index: 20; } +.toast.show { opacity: 1; } + +.plan-entry { background: linear-gradient(to right, #fff3eb, #e4cfff, #f5f9ff); border-radius: 16px; padding: 1.3rem 1.5rem; box-shadow: var(--shadow); margin: 2rem 0; display: grid; gap: 0.4rem; } +.plan-entry h2 { margin: 0; font-size: 1.1rem; color: var(--indigo); } +.plan-entry p { margin: 0; color: var(--slate-700); font-size: 0.95rem; } +.plan-entry .btn { justify-self: start; margin-top: 0.4rem; } + +@media print { + .site-header, .backbar-wrap, .totop, .site-footer, .sample-banner, .plan-steps, .plan-actions, .toast, .no-print { display: none !important; } + .plan-hero { padding: 0 0 1rem; } + .pcard, .day, .kpi-row .k { box-shadow: none; break-inside: avoid; } +} From 27e77add6539d3c767e7258bbd14e2f7e8f20e83 Mon Sep 17 00:00:00 2001 From: Haewon Kam Date: Thu, 10 Sep 2026 16:10:29 +0900 Subject: [PATCH 03/17] =?UTF-8?q?design(supporters):=20=ED=9A=8C=EB=B3=B5?= =?UTF-8?q?=20=EC=9D=BC=EC=A0=95=20=ED=94=8C=EB=9E=98=EB=84=88=EB=A5=BC=20?= =?UTF-8?q?=ED=99=88=C2=B7=EC=98=81=EC=83=81=20=ED=8E=98=EC=9D=B4=EC=A7=80?= =?UTF-8?q?=EC=99=80=20=EA=B0=99=EC=9D=80=20=EC=84=B9=EC=85=98=20=EB=A6=AC?= =?UTF-8?q?=EB=93=AC=EC=9C=BC=EB=A1=9C=20=EC=9E=AC=EA=B5=AC=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 다크 히어로(세리프 그라디언트 제목, 언어 알약, 글래스 통계 4장) → 틴트(Step 1~3 카드 그리드) → 다크(핵심 날짜 6개 글래스 통계) → 라이트(체류 확인·회복 규칙·외출 단계 카드) → 틴트(타임라인 흰 카드) → 다크(숙소·항공·상담·저장 카드) - 데이터 타일의 웜 그라디언트 제거(가드레일: CTA 카드 한 장에만). 전역 .kpi 규칙과 충돌하던 클래스명 교체 - 영문 세리프 제목 + 한글 부제로 위계, 날짜·박 수는 Playfair 숫자 - 빈 상태(시술·날짜 미선택) 다크 섹션 추가 Co-Authored-By: Claude Fable 5.1 --- supporters/src/components/Planner.astro | 204 ++++++++++++++---------- supporters/src/data/planStrings.json | 36 ++++- supporters/src/styles/plan.css | 141 +++++++++------- 3 files changed, 236 insertions(+), 145 deletions(-) diff --git a/supporters/src/components/Planner.astro b/supporters/src/components/Planner.astro index ae89916..04a99c1 100644 --- a/supporters/src/components/Planner.astro +++ b/supporters/src/components/Planner.astro @@ -1,24 +1,41 @@ --- // 회복기 스케줄러 본문 (설계 v0.2 §13). /plan(한국어)과 /en/plan(영어)이 같은 컴포넌트를 lang 만 바꿔 쓴다. +// 구성은 홈·영상 페이지와 같은 섹션 리듬이다: 다크 히어로(글래스 통계) → 틴트(입력 카드) → 다크(핵심 날짜) → 라이트(확인·규칙) → 틴트(타임라인) → 다크(예약). // 건강정보(시술·수술일)는 서버로 보내지 않는다. 상태는 URL 해시로만 공유하고, 언어 전환은 해시를 들고 다른 언어 페이지로 이동한다. import '../styles/plan.css'; import strings from '../data/planStrings.json'; +import P from '../data/procedures.json'; +import PL from '../data/places.json'; interface Props { lang: 'ko' | 'en' } const { lang } = Astro.props; const t = strings[lang]; +const nProcs = P.procedures.length; +const nPlaces = PL.places.filter((x) => x.category !== 'hotel').length; +const nHotels = PL.places.filter((x) => x.category === 'hotel').length; --- -
    -
    - -
    {t.eyebrow}
    -

    {t.h_hero}

    -

    {t.title} {t.lede}

    -
    -
    - +
    +
    +
    +
    {t.eyebrow}
    +

    {t.h_hero}

    +

    {t.title} {t.lede}

    +
    -
    -
    +
    +
    {nProcs}{t.stat_procs}{t.stat_procs_d}
    +
    {nPlaces}{t.stat_places}{t.stat_places_d}
    +
    {nHotels}{t.stat_hotels}{t.stat_hotels_d}
    +
    0{t.stat_privacy}{t.stat_privacy_d}
    +
    +
+ +
+
+
+
diff --git a/supporters/src/data/factSheet.json b/supporters/src/data/factSheet.json index 2c1b0df..da0001a 100644 --- a/supporters/src/data/factSheet.json +++ b/supporters/src/data/factSheet.json @@ -213,5 +213,10 @@ "sideEffectNotice": "수술 후 개인에 따라 염증, 출혈, 신경손상 등의 부작용이 있을 수 있습니다.", "businessNo": "220-08-86777", "checkedAt": "2026-09-04", - "nextCheck": "2026-10-04" + "nextCheck": "2026-10-04", + "geo": { + "lat": 37.5049823, + "lng": 127.0254182, + "source": "네이버 지역검색 실측 2026-09-09" + } } diff --git a/supporters/src/pages/en/index.astro b/supporters/src/pages/en/index.astro index 8c89fb8..482e76e 100644 --- a/supporters/src/pages/en/index.astro +++ b/supporters/src/pages/en/index.astro @@ -4,6 +4,7 @@ import Base from '../../layouts/Base.astro'; import { fact, site as S, clinicSchema, surface, has, v } from '../../lib'; import EN from '../../data/factSheetEn.json'; +import VisitMap from '../../components/VisitMap.astro'; const f = fact as Record; const site = Astro.site!.toString().replace(/\/$/, ''); const yt = surface('youtube'); const gu = surface('gangnamunni'); const gg = surface('google'); @@ -60,6 +61,7 @@ const ld = [
From the airport

Incheon to Sinnonhyeon

{EN.airport}

On arrival

Where to go in the building

{EN.arrival}

{EN.parking}

+

Opening hours

{(f.hours?.rows ?? []).map((r: any) => )} diff --git a/supporters/src/pages/visit.astro b/supporters/src/pages/visit.astro index 398cdae..2ce23ae 100644 --- a/supporters/src/pages/visit.astro +++ b/supporters/src/pages/visit.astro @@ -2,6 +2,7 @@ // 방문 안내. 병원 정보(/clinic)가 사실 표라면 이 페이지는 "예약하고, 찾아가고, 도착해서 뭘 하는지"의 행동 순서다. 값은 모두 factSheet.json에서 가져온다. import Base from '../layouts/Base.astro'; import Gallery from '../components/Gallery.astro'; +import VisitMap from '../components/VisitMap.astro'; import '../styles/plan.css'; import { fact, site as S, SITE_NAME, clinicSchema, reservationUrl, v, has } from '../lib'; const f = fact as Record; @@ -32,6 +33,7 @@ const ld = [

주소 {v(f.address?.full)}{has(f.address?.postalNote) && <> (지번 {f.address.postalNote})}

지하철 {v(f.transit)}.{has(f.transitNote) && <> {f.transitNote}}

차로 올 때 {v(f.parking, `주차 안내 ${v(null)}`)}.

+ {S.visitMaps?.length > 0 && }

4. 도착하면

From d13ac35365f87488f461bf17b4ddcf00a9571e6b Mon Sep 17 00:00:00 2001 From: Haewon Kam Date: Fri, 11 Sep 2026 11:48:49 +0900 Subject: [PATCH 14/17] =?UTF-8?q?style(supporters):=20=EC=98=A4=EC=8B=9C?= =?UTF-8?q?=EB=8A=94=20=EA=B8=B8=20=EC=A7=80=EB=8F=84=EB=A5=BC=20=EC=B9=B4?= =?UTF-8?q?=EB=93=9C=20=EB=86=92=EC=9D=B4=EC=97=90=20=EB=A7=9E=EC=B6=B0=20?= =?UTF-8?q?=EB=8A=98=EB=A6=BC=20(=EC=97=B4=20=EB=B9=84=EC=9C=A8=201.5:1,?= =?UTF-8?q?=20invalidateSize)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Fable 5.1 --- supporters/src/components/VisitMap.astro | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/supporters/src/components/VisitMap.astro b/supporters/src/components/VisitMap.astro index 91ee282..757410c 100644 --- a/supporters/src/components/VisitMap.astro +++ b/supporters/src/components/VisitMap.astro @@ -51,8 +51,9 @@ const mapId = `visit-map-${Math.random().toString(36).slice(2, 8)}`; .vm-head { margin-bottom: 1.2rem; } .vm-head h2 { margin: 0.2rem 0 0.2rem; font-size: 1.6rem; } .vm-head .sub { margin: 0; color: var(--slate-600); } - .vm-grid { display: grid; grid-template-columns: minmax(0, 1.25fr) minmax(280px, 0.9fr); gap: 1.25rem; align-items: start; } - .vm-map { width: 100%; aspect-ratio: 16 / 10; min-height: 260px; border-radius: 16px; overflow: hidden; border: 1px solid var(--slate-100); box-shadow: var(--shadow); background: var(--primary-50); z-index: 0; } + .vm-grid { display: grid; grid-template-columns: minmax(0, 1.5fr) minmax(280px, 1fr); gap: 1.25rem; align-items: stretch; } + .vm-map-wrap { display: flex; flex-direction: column; min-height: 0; } + .vm-map { width: 100%; flex: 1 1 auto; min-height: 340px; border-radius: 16px; overflow: hidden; border: 1px solid var(--slate-100); box-shadow: var(--shadow); background: var(--primary-50); z-index: 0; } .vm-empty { display: grid; place-items: center; color: var(--slate-500); font-size: 0.92rem; padding: 1rem; text-align: center; } .vm-empty p { margin: 0; } .vm-attr { font-size: 0.74rem; color: var(--slate-500); margin: 0.45rem 0 0; } @@ -69,7 +70,7 @@ const mapId = `visit-map-${Math.random().toString(36).slice(2, 8)}`; .vm-btn.kakao { background: #FEE500; color: #191919; } .vm-btn.google { background: var(--primary-900); } .visit-map.compact .vm-grid { grid-template-columns: 1fr; } - @media (max-width: 760px) { .vm-grid { grid-template-columns: 1fr; } .vm-map { aspect-ratio: 4 / 3; } } + @media (max-width: 760px) { .vm-grid { grid-template-columns: 1fr; } .vm-map { flex: none; aspect-ratio: 4 / 3; min-height: 0; } } @media print { .vm-map { break-inside: avoid; } .vm-actions { display: none; } } @@ -92,6 +93,9 @@ const mapId = `visit-map-${Math.random().toString(36).slice(2, 8)}`; L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png', { maxZoom: 19, attribution: '© OpenStreetMap contributors' }).addTo(map); const icon = L.divIcon({ className: 'vm-pin', html: '', iconSize: [34, 44], iconAnchor: [17, 44], popupAnchor: [0, -40] }); L.marker([lat, lng], { icon }).addTo(map).bindPopup(`${el.dataset.name ?? ''}`); + // 카드 높이에 맞춰 늘어난 뒤 타일이 빈 곳 없이 채워지도록 + const fix = () => map.invalidateSize(); + requestAnimationFrame(fix); window.addEventListener('resize', fix, { passive: true }); if ('ResizeObserver' in window) new ResizeObserver(fix).observe(el); } }; document.head.appendChild(s); From 94d7edec4575b6bf391c18370c1baac5b91204fe Mon Sep 17 00:00:00 2001 From: Haewon Kam Date: Fri, 11 Sep 2026 11:51:53 +0900 Subject: [PATCH 15/17] =?UTF-8?q?style(supporters):=20=EC=A7=80=EB=8F=84?= =?UTF-8?q?=20=EC=B6=9C=EC=B2=98=20=EC=A4=84=EC=9D=84=20=ED=96=89=20?= =?UTF-8?q?=EB=B0=96=EC=9C=BC=EB=A1=9C=20=EC=98=AE=EA=B2=A8=20=EC=A7=80?= =?UTF-8?q?=EB=8F=84=20=EC=83=81=EC=9E=90=EC=99=80=20=EC=B9=B4=EB=93=9C=20?= =?UTF-8?q?=EC=95=84=EB=9E=98=EC=84=A0=EC=9D=84=20=EB=A7=9E=EC=B6=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Fable 5.1 --- supporters/src/components/VisitMap.astro | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/supporters/src/components/VisitMap.astro b/supporters/src/components/VisitMap.astro index 757410c..7fa8fc5 100644 --- a/supporters/src/components/VisitMap.astro +++ b/supporters/src/components/VisitMap.astro @@ -30,7 +30,6 @@ const mapId = `visit-map-${Math.random().toString(36).slice(2, 8)}`; {geo ? :

{T.noMap}

} - {geo &&

{T.attribution}

}
{T.address}
@@ -44,6 +43,7 @@ const mapId = `visit-map-${Math.random().toString(36).slice(2, 8)}`;
+ {geo &&

{T.attribution}

}
DayHours
{hoursDays[r.days] ?? r.days}{r.open ? `${r.open} to ${r.close}` : EN.hoursClosed}