o2o-infinith-demo/templates/supporters-astro/briefs
Haewon Kam dd754cf212 feat(supporters): 글마다 다른 대표 이미지 배정 · 뷰 수집 이미지에서 의료광고 금지 유형 제외
haewon 지적: 목록 화면에 같은 사진이 나란히 선다.

원인은 PostCard 의 대체 순서다. thumbnail·hero·영상이 모두 없으면 건물 사진 하나로
수렴한다. 뷰는 5편 전부, 오라클은 8편 전부가 그랬고, 원진은 6편 중 3편이 같은
소개 커버를 쓰고 있었다.

heroes 단계를 워커에 넣었다(generate 다음, build 앞). scripts/assign_post_heroes.mjs 가
images.json 에서 글마다 다른 사진을 고른다.
- 글 분류별로 어울리는 유형을 먼저 쓴다. 병원 소개·방문은 시설, 시술 정보는 시술 설명
  일러스트, 안전은 장비 순이다. 방문 안내에 시술 일러스트가 붙는 것을 막는다.
- 의료진 사진은 쓰지 않는다. 저자·검토자 표시가 지정 용도이고, 글 대표로 쓰면 그 원장이
  쓴 글로 읽힌다.
- 홈 히어로·건물 사진과 겹치는 글도 다시 배정한다.
- 수집 원문 alt 를 그대로 쓰지 않는다. 원문은 술기 설명 문단이라 고객 화면에 낼 수 없어
  첫 구절만 줄여 쓴다. 캡션의 출처도 페이지 제목이 슬로건일 때 상호로 바꾼다.
- 이미지가 글보다 적으면 남는 글은 비워 둔다. 다시 쓰면 처음 문제로 돌아간다.

작업 중 더 큰 문제를 찾았다. 뷰의 수집 이미지 중 다섯 장이 의료광고 금지 유형인데
전부 시설(clinic)로 분류돼 홈과 /clinic 에 나가고 있었다.
- img02: 밑선 절개 흉터의 1개월·6개월 경과를 나란히 놓은 전후 사진
- stemcell_about_01: 팔에 주사·드레싱을 하는 시술 행위 사진
- view-abdominoplasty 2장, view-motiva-preserve-premium-standard 2장: "이미지 광고 모델" 표기가 있는 모델 신체 사진
- stemcell_count_01_mo: 장비 제조사의 성능 주장 그래픽
파일명과 alt 에 아무 신호가 없어 규칙 문서가 예고한 파일명·alt 검사로는 잡히지 않는다.
briefs/viewclinic/clinic.json 의 excludeImages 로 뺐고, 홈 히어로가 가슴 라인
일러스트였던 것과 건물 사진 alt 가 "가슴확대 시설 사진"이던 것도 함께 고정했다.

육안 검토용 scripts/build_image_review.mjs 를 만들었다. 세 병원 수집 이미지를 한 화면에
모아 분류·쓰이는 곳·의심 표시를 보여준다. 91장 중 파일명·alt 로 걸러진 것은 0장이었다.
이 유형은 사람이 봐야 갈린다는 뜻이다.

결과: 홈·목록의 연속 중복이 뷰 1, 원진 1 로 내려갔다(이전 뷰 6, 원진 3).
오라클은 수집 이미지가 0장이라 배정할 것이 없다. 근거의 이미지 alt 가 EUC-KR 에서
깨져(????) 분류기가 하나도 고르지 못하는 것이 원인으로 좁혀졌으나, 병원 사이트가
응답하지 않아 재수집·검증을 하지 못했다.

검증: 게이트 41/41, check.mjs 오류 0건, tsc 0 에러, 내보내기 종료코드 0. 모두 미배포.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-12 16:15:52 +09:00
..
oracle feat(supporters): 글마다 다른 대표 이미지 배정 · 뷰 수집 이미지에서 의료광고 금지 유형 제외 2026-09-12 16:15:52 +09:00
viewclinic feat(supporters): 글마다 다른 대표 이미지 배정 · 뷰 수집 이미지에서 의료광고 금지 유형 제외 2026-09-12 16:15:52 +09:00
wonjin feat(supporters): 글마다 다른 대표 이미지 배정 · 뷰 수집 이미지에서 의료광고 금지 유형 제외 2026-09-12 16:15:52 +09:00
README.md feat(supporters): 회복 일정 플래너를 템플릿·워커에 통합 (/plan·/en/plan), /recovery·/stay 를 /plan 으로 정리 2026-09-11 11:27:59 +09:00

기획 목록(brief) 형식

글 생성기(scripts/generate_posts.mjs)의 입력이다. v2 §3-11의 "기획 목록 승인"은 이 파일을 사람이 확인하는 것으로 한다. 생성기는 brief 밖의 근거를 쓰지 않는다.

node scripts/generate_posts.mjs --clinic <id> --brief briefs/<id>/<name>.json [--out DIR] [--dry-run] [--no-verify]
node scripts/generate_posts.mjs --clinic viewclinic --from-golden        # 정답지 18편에서 brief 를 자동 추출해 회귀
node scripts/default_briefs.mjs --clinic <id> --evidence <evidence/<id>> --site <siteDir> [--out briefs.json] [--max 6] [--industry derm|plastic] [--areas N]   # 첫 배치 자동 제안

파일

{
  "industry": "plastic",                   // default_briefs 가 적는 업종. plastic(성형외과) | derm(피부과). 글마다 industry 를 두면 그것이 우선
  "posts": [
    {
      "id": "slug",                        // 파일명. posts/<id>.md
      "industry": "derm",                  // 선택. derm 이면 생성 프롬프트에 피부과 추가 규칙(조건 표 8행·시술자·상표·금액 금지·부작용 어휘)이 붙고, 게이트가 PRACTITIONER_MISSING 을 본다
      "title": "질문형 한 문장?",           // H1. 물음표로 끝난다
      "category": "D", "categoryLabel": "시술 정보",
      "qbIds": ["D2-02"],                  // 질문 뱅크 문항 ID (화면에는 안 나옴)
      "intent": "무엇을 어떤 형식으로 정리할지 한 문단",   // 선택
      "mustCover": ["..."], "avoid": ["..."],              // 선택
      "reviewerCandidate": "dr-cho",       // authors.json physicians 키. 후보일 뿐, reviewStatus 는 항상 pending
      "evidence": {
        "pages": ["https://.../system/precautions/"],       // 병원 페이지. evidence/<clinic>/pages 에 있어야 함
        "shorts": ["videoId"],                              // videos.json shortsInfo 에 정리 답이 있는 영상
        "transcripts": [{ "id": "", "title": "", "speaker": "", "text": "" }],  // 선택. 자막이 있으면 여기로
        "news": ["https://..."],                            // news.json 에 있는 기사 URL (제목·매체·날짜만 쓴다)
        "regulation": [{ "label": "", "url": "", "note": "확인된 내용 한 줄" }],  // note 가 없으면 링크로만 안내
        "platform": false                                   // true 면 팩트 시트 surfaces 를 sources 에 붙인다
      },
      "videos": [{ "id": "", "title": "", "speaker": "", "published": "", "note": "" }],  // 글에 붙는 영상 (제목·화자만)
      "hero": { "src": "/img/...", "alt": "", "caption": "" },   // 선택
      "gallery": [], "thumbnail": "",                             // 선택
      "sources": []                        // 주면 evidence 로부터의 자동 조립 대신 이것을 쓴다 (정답지 회귀용)
    }
  ]
}

생성 흐름

  1. 컨텍스트: evidence 를 §4 순서(병원 페이지 → 영상 정리 답 → 기사 → 규제 원문 → 팩트 시트)로 붙인다. 없는 근거는 missingEvidence 로 리포트에 남긴다.
  2. 생성(1회): 요약 3·본문·FAQ·태그·pending(근거에 없어 못 쓴 것)·usedRefs.
  3. 후처리: 근거 태그([C1]) 제거, 본문의 FAQ·참고 자료·연락처 섹션 제거(템플릿이 붙인다).
  4. 검사: 소스 게이트(gate/rules.mjs 전부 + 금칙어 + 비교 수치 + 홈페이지 40자 중복) + 수치 대조(글의 숫자+단위가 근거에 그대로 있어야 함) + 모델 근거 대조(--no-verify 로 생략).
  5. 오류가 있거나 경고가 --warn-threshold(기본 4) 이상이면 오류 목록을 되돌려 수정 1회. 그래도 남은 근거 플래그는 ok_needs_review 로 표시해 사람이 본다.
  6. 저장: generated/<clinic>/<시각>/posts/*.md + report.md(편당 비용·시간·오류·확인 대기 목록). generated/ 는 커밋하지 않는다. 채택할 글은 사람이 src/content/posts/ 로 옮긴다.

생성기가 하지 않는 것

  • reviewedAt·reviewStatus: reviewed 를 쓰지 않는다 (§3-7 사람 게이트).
  • 자막 없는 영상의 발언을 만들지 않는다. 영상 근거는 shortsInfo[].answer 또는 transcripts 뿐이다.
  • 기사 본문을 추정하지 않는다. 제목·매체·날짜만.
  • 근거에 없는 수치를 쓰지 않는다. 쓰면 NUMBER_NOT_IN_EVIDENCE 로 수정 대상이 된다.

업종 (industry)

default_briefs.mjs 가 정하고 brief 파일 최상위와 글마다 industry 로 적는다. 생성기·게이트는 글의 industry 를 우선 보고, 없으면 환경변수 SUPPORTERS_INDUSTRY, 그것도 없으면 plastic 이다.

판정 달라지는 것
plastic (기본) --industry plastic, 또는 병원 이름에 "성형외과" 기존 동작 그대로. 시술 영역은 URL 세그먼트(/contents/facial/...)로 묶고, 세그먼트가 일반어(new·doc·index.php·숫자)일 때만 제목·헤딩·본문 키워드 폴백
derm --industry derm, 병원 이름에 "피부과"(성형외과보다 앞에 있으면), 또는 시술 페이지 제목의 피부과 어휘가 3개 이상이고 성형외과 어휘보다 많을 때 시술 페이지를 URL 과 무관하게 아래 8영역으로 배정. 시술 글 제목·intent 가 조건 표 8행을 요구. avoid 에 피부과 금칙 추가. 지점 페이지(branches 유형)나 facts.draft.json·팩트 시트의 branches 가 2개 이상이면 "지점 선택 가이드"(B) 후보 추가. 시술 영역 기본 8개(--areas)

피부과 8영역 (질문 뱅크 D1~D8 과 1:1)

key 영역 문항 판정 낱말(일부)
pigment 색소·토닝 D1 색소, 토닝, 기미, 잡티, 검버섯, 피코, 미백
lifting 리프팅·탄력 D2 리프팅, 탄력, 처짐, 울쎄라, 써마지, 인모드, 슈링크, 고주파
booster 스킨부스터·재생 D3 스킨부스터, 리쥬란, 물광, 엑소좀, 재생, 콜라겐
toxin-filler 톡신·필러 D4 보톡스, 톡신, 필러, 주름, 사각턱, 이마, 미간
acne 여드름·흉터·모공 D5 여드름, 흉터, 모공, 피지, 스케일링, 필링, 프락셀
hair-body 제모·바디 D6 제모, 바디, 체형, 겨드랑이, 셀룰라이트, 쿨스컬프팅
disease-hair 피부질환·모발 D7 홍조, 혈관, 다한증, 액취증, 아토피, 사마귀, 탈모, 두피
plan 계획·주기·조합 D8 시술 주기, 회차, 병행, 조합, 시술 순서, 패키지

점수: 제목·H1·URL 에 낱말이 있으면 5점, 헤딩 2점, 본문 등장 횟수 × 0.5(최대 2.5). 2점 미만이면 영역 없음(기획하지 않음). 근거 충분성(영역의 페이지 글자 합 1,500자 이상)은 업종과 무관하게 같다. 본문이 이미지 글자인 페이지는 글자 수가 0에 가까워 자동으로 빠진다.

피부과 시술 글의 intent 는 시술마다 조건 표 8행(시술시간·마취·통증 정도·회복(다운타임)·유지기간·권장 회차·권장 주기·시술자)을 요구하고, 페이지에 없는 칸은 "확인", 시술자 표기가 없으면 "확인 대기"로 두라고 지시한다. 고정 글(병원 소개·방문 안내·상담 질문 체크리스트)은 업종과 관계없이 나온다.

게이트 규칙 (피부과 추가분, 성형외과 글에도 적용)

scripts/gate/rules.mjscheckPostSource 에 들어 있다. 근거 조문은 코드 주석에 있고, 법 해석은 자문이 아니다.

코드 수준 조건 근거
PRICE_MENTION error 요약·본문·FAQ 에 금액(150,000원·15만원·₩·%할인·이벤트가 99,000·1+1). "비급여 진료비용 고지" 안내 문장은 통째로 허용 의료법 45조(비급여 고지는 의료기관의 의무, 서포터즈는 링크만) · 56조 2항(유인)
DEVICE_CLAIM error (1) 식약처 허가·인증, FDA 승인·허가, MFDS·KFDA, CE 인증 표기가 있는데 sourcesregulation 이 없음 (2) 영구·완치·재발 없음·100%·평생 유지·한 번으로 끝 같은 효과 보장. 부정문("영구라는 뜻은 아닙니다")·"반영구"(시술명)는 통과 의료기기법 24조 · 약사법 68조 · 의료법 56조 2항
PRACTITIONER_MISSING warn industry: derm 이고 분류 D 인 글에 시술자 표기("시술자" 행, "의사 직접", "의사 감독")가 없음. 성형외과 글은 검사하지 않음 의료법 27조(무면허 의료행위, 대리시술)
BANNED_PHRASE (사전 확장) error "정품 정량 인증"·"정품 인증"(인증 어휘. "정품·정량 사용을 밝힙니다" 같은 서약 사실 서술은 통과), "비포애프터"·"전후 사진을 보세요/확인하세요/공개" 유도 문구("전후 사진을 싣지 않습니다" 원칙 서술과 "수술 전후 모니터링" 시기 표현은 통과) 의료법 56조 2항 2호·14호

부작용 고지 문안은 팩트 시트 sideEffectNotice 를 템플릿이 붙이므로 글 단위 검사는 없다. 생성 프롬프트(gen/prompt.mjsDERM_RULES)는 시술 글에 "## 시술 후 관리와 부작용" 절과 화상·색소침착·멍·부기·감염 같은 구체 어휘를 요구한다.

픽스처: scripts/gate/fixtures/price-mention.md(실패) · price-notice-link.md(통과) · device-claim-approval.md·device-claim-guarantee.md(실패) · device-claim-ok.md(통과) · practitioner-missing.md(경고) · practitioner-present.md·practitioner-plastic-skip.md(경고 없음) · derm-cert-phrase.html·before-after-phrase.html(실패) · derm-cert-ok.html(통과). npm run gate:test 로 돈다. 픽스처 frontmatter 의 expectWarn:·expectNoWarn: 으로 warn 코드를 검사한다.