o2o-site-AEO/solution/backend/tests
민헌 b2c8bb033e [feat] solution/backend,site: 발행 사이트 제목·keywords 메타에 SiteOntology 키워드 — 이 가게 자료로 거른 것만
숙박 사이트를 빌드할 때 SiteOntology(o2o-site-ontology)에 이 가게 프로필을 보내 검색 키워드를
받고, 이 가게의 확인된 자료로 거른 것만 `<meta name="keywords">` 와 제목 업종어 자리에 싣는다.
실측(2026-09-14, 스테이머뭄 프로필): 추천 10건 중 `군산 독채 마당 펜션`·`군산 독채 복층 펜션`·
`군산 커플 프라이빗 펜션` 이 status=ok 로 왔다 — SiteOntology 사실 필터는 수용 인원과 일부 시설만 본다.
사전에는 `선유도 독채펜션`(다른 권역)·`군산 펜션 최저가`(가격 주장)도 있다. 메타 태그와 제목은 AI 검색이
그대로 읽는 자리라, 키워드의 모든 낱말이 이 가게 자료에 있을 때만 싣는다.
SiteOntology 쪽 함정도 실측으로 막았다 — 없는 regionId 는 500(외래키), 해석 안 된 query 도 201 로
입력 문자열 검색 결과를 준다.

- services/external/site_ontology.py: publish(generate:false) → match 두 번 호출. 500 이면 지역 없이
  재시도, resolved 가 우리 place_id 가 아니면 실패로 본다
- services/seo_keywords.py: 스냅샷 → 프로필(있음=features · 없음=뺌 · 모름=unverified), 낱말 대조 거르기,
  업종어뿐인 단어 제외, 제목은 유형 레인 코어 중 시·군 이름을 품고 예약·추천이 없는 것. 숙박만
- services/build_service.py: 스냅샷 직후 호출해 snapshot["seo"] 에 싣는다(= 발행 기록). 실패해도 발행 계속
- services/site_payload.py · shared site-payload.ts: 선택 필드 `seo` — 옛 payload·목업은 그대로
- site/src/seo/meta.ts · head.ts: 제목 `<상호> · <대표 키워드>`(15자 미만이면 예전 제목), keywords 태그는
  키워드가 있을 때만(빈 태그를 만들지 않는다)
- config_models.py · .env.example: SITE_ONTOLOGY_URL — 비우면 호출하지 않는다
- docs/ARCHITECTURE.md 발행 파이프라인 · docs/DEVLOG.md

pytest tests/test_seo_keywords.py 16 passed · 백엔드 전체 650 passed(실패 2건은 이전부터:
test_rate_limit_closes_the_tap · test_사이트_디렉터리_밖의_thumbs_에_올린다)
site tsc·eslint 통과 · vitest 63 passed · frontend·admin tsc 통과
실제 발행 한 바퀴(로컬 SiteOntology :3100): 스테이머뭄 → `<title>스테이머뭄 · 군산 독채펜션</title>` +
keywords 9건, 렌더 게이트 불일치 0건. SiteOntology 없는 payload 는 제목·head 가 예전 그대로

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LBR4o9Nth3g4eoQEnL1iat
2026-09-14 14:38:24 +09:00
..
test_auth.py [refactor] solution/backend,frontend,postgres-init: 회사(테넌트) 제거 — 사장님 계정이 곧 스코프 2026-09-08 13:01:14 +09:00
test_azure_static.py 구조: 사장님(solution)과 내부 운영(admin)을 두 앱으로 가른다 2026-08-31 15:12:09 +09:00
test_build_publish.py [feat] solution: 지역 이야기 생성 · 발행본 섹션 손질 · 마이그레이션 주석 축약 2026-09-10 14:36:00 +09:00
test_category_schema.py 업종 4번째를 관광체험 → 피부과·성형외과 로 바꾸고, 로그인 관문을 에디터 진입으로 되돌린다 2026-09-02 15:42:34 +09:00
test_collect_api.py [refactor] solution/backend,frontend,postgres-init: 회사(테넌트) 제거 — 사장님 계정이 곧 스코프 2026-09-08 13:01:14 +09:00
test_collect_pipeline.py [feat] solution: 지역 이야기 생성 · 발행본 섹션 손질 · 마이그레이션 주석 축약 2026-09-10 14:36:00 +09:00
test_collector.py 구조: 사장님(solution)과 내부 운영(admin)을 두 앱으로 가른다 2026-08-31 15:12:09 +09:00
test_config.py [feat] solution/backend,frontend: id/pw 가입 · 구글 로그인 — 계정을 만들 길이 없던 걸 연다 2026-09-02 09:33:59 +09:00
test_copy_api.py [fix] solution/backend,docs: LLM 이 쓴 문장은 승인 없이 나간다 — 소개문이 생성되고도 영영 빈칸이던 것 2026-09-10 17:03:44 +09:00
test_cost.py 구조: 사장님(solution)과 내부 운영(admin)을 두 앱으로 가른다 2026-08-31 15:12:09 +09:00
test_extract_grounding.py 구조: 사장님(solution)과 내부 운영(admin)을 두 앱으로 가른다 2026-08-31 15:12:09 +09:00
test_fact_api.py [fix] solution/backend,docs: LLM 이 쓴 문장은 승인 없이 나간다 — 소개문이 생성되고도 영영 빈칸이던 것 2026-09-10 17:03:44 +09:00
test_fact_schema.py [feat] solution: 지역 이야기 생성 · 발행본 섹션 손질 · 마이그레이션 주석 축약 2026-09-10 14:36:00 +09:00
test_faq_api.py [fix] solution/backend,docs: LLM 이 쓴 문장은 승인 없이 나간다 — 소개문이 생성되고도 영영 빈칸이던 것 2026-09-10 17:03:44 +09:00
test_gemini_text.py 구조: 사장님(solution)과 내부 운영(admin)을 두 앱으로 가른다 2026-08-31 15:12:09 +09:00
test_gemini.py 구조: 사장님(solution)과 내부 운영(admin)을 두 앱으로 가른다 2026-08-31 15:12:09 +09:00
test_google_identity.py [feat] solution/backend,frontend: id/pw 가입 · 구글 로그인 — 계정을 만들 길이 없던 걸 연다 2026-09-02 09:33:59 +09:00
test_healthz.py 구조: 사장님(solution)과 내부 운영(admin)을 두 앱으로 가른다 2026-08-31 15:12:09 +09:00
test_indexnow.py 구조: 사장님(solution)과 내부 운영(admin)을 두 앱으로 가른다 2026-08-31 15:12:09 +09:00
test_itinerary_grounding.py [feat] solution/backend: 업소를 여행 일정의 출발·복귀 정거장으로 넣는다 2026-09-11 16:56:05 +09:00
test_itinerary_llm_service.py [feat] solution/backend: 업소를 여행 일정의 출발·복귀 정거장으로 넣는다 2026-09-11 16:56:05 +09:00
test_itinerary_payload.py [feat] solution/backend: 일정을 스냅샷에서 읽는다 — 즉석 계산을 잠시 쉬게 한다 2026-09-11 11:49:51 +09:00
test_itinerary_prompt.py [feat] solution/backend: 일정 컨셉당 2개씩, 총 10개로 확대 — 컨셉 축은 그대로 5개 2026-09-11 13:31:05 +09:00
test_itinerary_trigger.py [feat] solution/backend: 일정 생성을 잡에 붙인다 — 에디터 요청을 붙잡지 않는다 2026-09-11 11:51:39 +09:00
test_job_queue.py 구조: 사장님(solution)과 내부 운영(admin)을 두 앱으로 가른다 2026-08-31 15:12:09 +09:00
test_kakao.py [feat] solution/backend: 상호명 공개 검색 + 업종 자동 판별 — 랜딩이 로그인 앞에서 부른다 2026-09-03 09:41:34 +09:00
test_local_guide_itinerary.py [feat] solution: 캔버스도 서버가 만든 일정을 본다 — 발행본과 같은 목록 2026-09-11 11:58:09 +09:00
test_my_sites.py [feat] solution/backend,frontend: 내 사이트 목록을 카드로 — 썸네일·주소·시각 · 발행마다 그림 갱신 2026-09-08 13:01:47 +09:00
test_naver_place_lookup.py 구조: 사장님(solution)과 내부 운영(admin)을 두 앱으로 가른다 2026-08-31 15:12:09 +09:00
test_naver.py 구조: 사장님(solution)과 내부 운영(admin)을 두 앱으로 가른다 2026-08-31 15:12:09 +09:00
test_perplexity.py 구조: 사장님(solution)과 내부 운영(admin)을 두 앱으로 가른다 2026-08-31 15:12:09 +09:00
test_place_itinerary_crud.py [feat] solution/backend: 일정 읽기·덮어쓰기 — 업장×기간 한 행 2026-09-11 11:21:46 +09:00
test_place_search.py [refactor] solution/backend,frontend,postgres-init: 회사(테넌트) 제거 — 사장님 계정이 곧 스코프 2026-09-08 13:01:14 +09:00
test_place.py [refactor] solution/backend,frontend,postgres-init: 회사(테넌트) 제거 — 사장님 계정이 곧 스코프 2026-09-08 13:01:14 +09:00
test_publish_gate.py [fix] solution/backend,shared,site: 에디터에 쓴 소개문이 발행에서 사라지던 구멍 — 계약에 body 추가 2026-09-02 12:00:16 +09:00
test_render_report.py 구조: 사장님(solution)과 내부 운영(admin)을 두 앱으로 가른다 2026-08-31 15:12:09 +09:00
test_schema_ddl.py [feat] solution: 지역 이야기 생성 · 발행본 섹션 손질 · 마이그레이션 주석 축약 2026-09-10 14:36:00 +09:00
test_seo_audit.py [refactor] solution/backend,frontend,postgres-init: 회사(테넌트) 제거 — 사장님 계정이 곧 스코프 2026-09-08 13:01:14 +09:00
test_seo_keywords.py [feat] solution/backend,site: 발행 사이트 제목·keywords 메타에 SiteOntology 키워드 — 이 가게 자료로 거른 것만 2026-09-14 14:38:24 +09:00
test_showcase_api.py [refactor] solution/backend,frontend,postgres-init: 회사(테넌트) 제거 — 사장님 계정이 곧 스코프 2026-09-08 13:01:14 +09:00
test_site_slug.py [refactor] solution/backend,frontend,postgres-init: 회사(테넌트) 제거 — 사장님 계정이 곧 스코프 2026-09-08 13:01:14 +09:00
test_site_template.py [refactor] solution/backend,frontend,postgres-init: 회사(테넌트) 제거 — 사장님 계정이 곧 스코프 2026-09-08 13:01:14 +09:00
test_site_theme.py [refactor] solution/backend,frontend,postgres-init: 회사(테넌트) 제거 — 사장님 계정이 곧 스코프 2026-09-08 13:01:14 +09:00
test_site_thumbnail.py [feat] solution/backend,frontend: 내 사이트 목록을 카드로 — 썸네일·주소·시각 · 발행마다 그림 갱신 2026-09-08 13:01:47 +09:00
test_snapshot.py [fix] solution/backend: 스냅샷 지역정보에 itineraries 키가 추가된 것을 테스트에 반영 2026-09-11 13:03:41 +09:00
test_story_generation.py [feat] solution: 지역 이야기 생성 · 발행본 섹션 손질 · 마이그레이션 주석 축약 2026-09-10 14:36:00 +09:00
test_tour_api_adapter.py [feat] solution: 지역 이야기 생성 · 발행본 섹션 손질 · 마이그레이션 주석 축약 2026-09-10 14:36:00 +09:00
test_verify_candidates.py [refactor] solution/backend,frontend,postgres-init: 회사(테넌트) 제거 — 사장님 계정이 곧 스코프 2026-09-08 13:01:14 +09:00
test_vision_api.py [feat] solution: 지역 이야기 생성 · 발행본 섹션 손질 · 마이그레이션 주석 축약 2026-09-10 14:36:00 +09:00
test_weather_api.py [feat] solution: 지역 이야기 생성 · 발행본 섹션 손질 · 마이그레이션 주석 축약 2026-09-10 14:36:00 +09:00