o2o-site-AEO/postgres-init/migrations/0012_place_faqs_template_source.sql
민헌 8a09af6599 [feat] solution,postgres-init: FAQ 를 20개까지 채운다 — 펜션 공통 질문 30개 + 문의 안내
COPY 잡은 확인된 fact 로만 FAQ 를 써서 4~8개에서 끝났다(실측 로컬: 스테이머뭄 fact 8건,
산하연 풀빌라 fact 4건 · FAQ 4건). fact 가 0건이면 start_copy 가 FAQ_UNGROUNDED 로 잡을 만들지 않아 0개였다.
생성 상한을 20으로 올리고, 모자라면 펜션 카탈로그에서 겹치지 않는 질문을 **문의 안내** 답으로 채운다.
공통 답에 값·가능 여부를 적으면 업종 시드 FAQ 가 가공의 가격을 사이트에 내보낸 사고와 같다 —
답은 "…은 전화(…)로 문의해 주시면 안내해 드립니다" 뿐이고, 그래서 화면에만 나간다.

- common/faq_catalog(신규): 로더 + resources/pension.json 30문항. fact_keys 가 업종 스키마에 없으면 로드 시 예외
- services/faq_fill.py(신규): 고르기 규칙 — fact 로 답할 수 있는 질문 · 기존 FAQ 와 근거 key 또는 질문 키워드가
  겹치는 질문은 건너뛴다(LLM 은 "주차 및 와이파이" 처럼 묶어 쓰고, 사장님 입력은 근거 key 가 없다)
- copy_service: max_faqs=20, 생성 뒤 _fill_faqs. 근거가 없거나 키가 없으면 LLM 없이 채우기만
- place_service.start_copy: 카탈로그가 있으면 fact 0건이어도 잡 생성(FAQ_UNGROUNDED 는 카탈로그 없는 업종만)
- SourceType.TEMPLATE=5(백엔드·shared·orval 모델). fact_service 규칙 4 로 fact 에는 못 쓴다
- faq_crud.expire_generated: TEMPLATE 도 재생성 때 내린다 — 새 fact 로 답이 생긴 주제에 옛 문의 안내가 남지 않게
- prompts/copy: fact 로 답할 수 있는 카탈로그 질문을 싣고 "한 문항 한 주제" 규칙(생성 FAQ 4건 중 3건이 묶여 있었다)
- shared selectAnsweredFaqs · jsonld · llms · prerender(↔ conftest) · seo_audit: 문의 안내는 FAQPage JSON-LD ·
  llms.txt · 고유 콘텐츠 계수 · FAQ 점수에서 뺀다 — 모든 펜션에 같은 문구라 세면 빈 사이트가 게이트를 통과한다
- site FaqSection: 문의 안내가 섞이면 "모두 사업자가 확인한 내용" 문구를 달지 않는다
- frontend FaqPanel "노출 N건 (문의 안내 M)" · notifyCopy 가 faq_fill 을 본다
- postgres-init: 컬럼 변경 없음(CHECK 없는 SMALLINT). 0012 + init.sql 에 generated_by·source_fact_ids COMMENT ON,
  0012 는 컬럼이 있을 때만(DO $$ IF EXISTS). init.sql 의 "비면 발행 게이트가 반려" 주석은 사실이 아니어서 고쳤다
- docs/DECISIONS.md 8절 · DATA_MODEL.md · DEVLOG.md

백엔드 664 passed(신규 test_faq_fill 10건 · test_copy_api 3건). 실패 2건은 이 변경 전 HEAD 에서도 같다:
test_rate_limit_closes_the_tap · test_사이트_디렉터리_밖의_thumbs_에_올린다
site·frontend·admin tsc 통과 · site vitest 63 passed · FaqPanel·collectNotify eslint 통과
로컬 실사업장(하늘물빛정원, fact 4건): 생성 4건 + 문의 안내 16건 = 20건, 질문 중복 0
0012: 새 DB(init.sql → migrate 규칙)와 로컬 DB 사본 양쪽에서 두 번씩 적용 통과

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011yLDuinzgyCxmqAutE1tse
2026-09-14 17:05:43 +09:00

30 lines
1.8 KiB
SQL

-- 0012 · place_faqs 출처 코드값 5(TEMPLATE) — FAQ 20개 채우기의 문의 안내 문항
--
-- ★ 컬럼·제약 변경은 없다. generated_by 는 CHECK 없는 SMALLINT 라 값 5 가 그대로 들어간다
-- (로컬 DB 제약 조회 2026-09-14: place_faqs_pkey 하나뿐). 이 파일은 코드값의 뜻을 DB 에 남긴다 —
-- psql 로 행을 보는 사람이 5 를 모르는 값으로 읽지 않게.
-- ★ 옛 주석 "source_fact_ids 가 비면 발행 게이트가 반려한다" 는 사실이 아니었다 — 그런 검사는 없고,
-- 근거 강제는 저장 시점(copy_service)에 있다. 규칙: docs/DECISIONS.md 8절
-- ★ 컬럼이 있을 때만 단다. 0005(표 이름 이동) 전 구조의 DB 에서 손으로 돌려도 실패하지 않게 —
-- COMMENT ON 은 대상이 없으면 에러다. 대상이 있으면 덮어쓰기라 재실행해도 안전하다.
-- init.sql 의 place_faqs 뒤에 같은 COMMENT ON 이 있다 — 문구를 바꾸면 둘 다 고친다.
DO $$
BEGIN
IF EXISTS (
SELECT 1 FROM information_schema.columns
WHERE table_schema = 'public' AND table_name = 'place_faqs' AND column_name = 'generated_by'
) THEN
COMMENT ON COLUMN public.place_faqs.generated_by IS
'SourceType: 1=owner(사장님 입력·정정) 4=llm(확인된 fact 로 생성) 5=template(20개 채우기 문의 안내 — 화면만, JSON-LD·llms.txt 제외)';
END IF;
IF EXISTS (
SELECT 1 FROM information_schema.columns
WHERE table_schema = 'public' AND table_name = 'place_faqs' AND column_name = 'source_fact_ids'
) THEN
COMMENT ON COLUMN public.place_faqs.source_fact_ids IS
'근거 fact key 배열. llm 생성분만 채운다(없으면 저장하지 않는다). owner·template 은 NULL 일 수 있다.';
END IF;
END $$;