[fix] solution/backend: 히어로 아래 한 줄이 소개문 세 문장을 이고 있었다

`narrative.summary` 는 계약이 "요약 **한 문장**"이라 적어 둔 자리인데(shared/site-payload.ts)
첫 **문단**을 통째로 넣고 있었다. 그 값은 두 곳으로 나간다 — 히어로 아래 한 줄과
meta description. 그래서 히어로가 세 문장을 이고 서고(실측 2026-09-10), 검색 결과에서는
설명이 중간에 잘렸다.

- site_payload._first_sentence: 첫 문장만 뗀다. **문장을 새로 생성하지 않는다** —
  있는 글에서 자를 뿐이다(요약을 또 생성하면 근거 검사를 한 번 더 통과해야 한다).
  숫자 사이의 점은 자르지 않는다(1.5km) — 뒤가 숫자면 문장 끝이 아니다.

★ 곁에서 확인한 것 — 생성기가 만든 meta_description 이 **버려지고 있다.**
  copy_service 는 소개문과 함께 50~120자짜리 메타 요약을 만들지만, 업종 스키마에
  `meta_description` 필드가 없어서 "allow_llm 이 아닌 필드" 로 조용히 건너뛴다
  (lodging.json 의 allow_llm 은 intro·room_intro 둘뿐). 지금은 첫 문장으로 대신하지만,
  스키마에 자리를 만들면 생성기가 쓴 요약이 그대로 meta description 이 된다 — 별건이다.

검증: 재발행 후 히어로 한 문장 · meta description 동일. test_snapshot 통과
(site_theme 의 섹션표 parity 4건 실패는 이 변경 전 기준선과 같다).
This commit is contained in:
hbyang 2026-09-10 15:29:42 +09:00
parent 868127a69a
commit c8cf704217

View File

@ -163,6 +163,21 @@ _DEFAULT_THEME = {
# ── 값 변환 헬퍼 ──────────────────────────────────────────────────────────
def _first_sentence(text: str) -> str:
"""첫 문장. 마침표가 없으면 통째로 돌려준다.
필요한가: 히어로 아래 줄과 meta description 같은 값을 쓴다. ** 문장**
자리라, 문단이 들어가면 히어로는 줄로 부풀고 검색 결과에서는 뒤가 잘린다.
마침표 뒤에 공백이 없어도 자른다("…입니다.일본식" 같은 생성물이 실제로 온다).
다만 숫자 사이의 (1.5km) 자르지 않는다 뒤가 숫자면 문장 끝이 아니다.
"""
import re as _re
match = _re.search(r"[.!?](?![0-9])", text or "")
return text[: match.end()].strip() if match else (text or "").strip()
def _get(row, key, default=None):
"""ORM 행이든 dict 든 같은 방식으로 읽는다(테스트가 가짜 행을 넣기 쉬우라고)."""
if isinstance(row, dict):
@ -805,8 +820,12 @@ def to_site_payload(place, snapshot: dict, site, version, links) -> dict:
paragraphs = [p.strip() for p in intro.split("\n") if p.strip()] if intro else []
narrative = {
"about": paragraphs,
# 요약은 첫 문단을 그대로 쓴다(요약문을 새로 생성하지 않는다).
"summary": paragraphs[0] if paragraphs else None,
# ★ 요약은 **첫 문장**이다. 문단이 아니다.
# 계약이 "요약 한 문장"이라 적어 뒀는데(shared/site-payload.ts) 첫 문단을 통째로
# 넣고 있었다. 그 값은 두 곳으로 나간다 — 히어로 아래 한 줄과 meta description.
# 문단이 들어가면 히어로가 세 문장을 이고 서고(실측 2026-09-10), meta description 은
# 검색 결과에서 잘린다. 문장을 새로 생성하지는 않는다 — 있는 글의 첫 문장을 뗄 뿐이다.
"summary": _first_sentence(paragraphs[0]) if paragraphs else None,
}
theme_spec = _DEFAULT_THEME.get(category) or _DEFAULT_THEME[PlaceCategory.LODGING.value]