From c8cf704217fd0c0ad7db3f417f2d827270f834a1 Mon Sep 17 00:00:00 2001 From: hbyang Date: Thu, 10 Sep 2026 15:29:42 +0900 Subject: [PATCH] =?UTF-8?q?[fix]=20solution/backend:=20=ED=9E=88=EC=96=B4?= =?UTF-8?q?=EB=A1=9C=20=EC=95=84=EB=9E=98=20=ED=95=9C=20=EC=A4=84=EC=9D=B4?= =?UTF-8?q?=20=EC=86=8C=EA=B0=9C=EB=AC=B8=20=EC=84=B8=20=EB=AC=B8=EC=9E=A5?= =?UTF-8?q?=EC=9D=84=20=EC=9D=B4=EA=B3=A0=20=EC=9E=88=EC=97=88=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `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건 실패는 이 변경 전 기준선과 같다). --- solution/backend/services/site_payload.py | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/solution/backend/services/site_payload.py b/solution/backend/services/site_payload.py index e51d959..367bbdf 100644 --- a/solution/backend/services/site_payload.py +++ b/solution/backend/services/site_payload.py @@ -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]