가게별 첫 화면 문구를 만드는 곳이 없어 모든 숙박이 공통 문구 10개를 돌려 썼다. - COPY 잡에 catchphrase 단계(prepare→generate→save→catchphrase→faq_fill), 숙박이 아니면 건너뜀 - prompts/catchphrase.py·gemini_text.generate_catchphrases: 캐치프레이즈 1 + 일반 20·계절 3×4·월 12·날씨 2×4. 문구마다 길이·상호명·ground_check(근거 없는 시설·수치) 검사 - fact tagline·catchphrases(lodging 스키마, allow_llm). 순환 문구는 사장님이 줄 단위로 고치게 글로 저장 (catchphrase_text: `봄 | ` · `9월 | ` · `비 | ` 머리) - site_payload: narrative.tagline·catchphrases 로 넘기고 이용정보 표(facts)에서는 뺀다 - 빌더 생성 화면 단계 문구, DATA_MODEL·GENERATION_FLOW 테스트 6건 추가. 관련 20개 파일 386 passed · 실패 102건은 main 과 목록이 같다(기존 실패) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
85 lines
3.5 KiB
Python
85 lines
3.5 KiB
Python
"""숙소 첫 화면 문구 — 고정 캐치프레이즈 하나와 그 아래 줄에서 도는 문구 모음."""
|
|
|
|
from typing import Optional, Sequence
|
|
|
|
from services.prompts.copy import FactLike, _fact_lines
|
|
|
|
SEASONS = ("봄", "여름", "가을", "겨울")
|
|
WEATHERS = ("맑음", "흐림", "비", "눈")
|
|
COUNT_GENERAL = 20
|
|
COUNT_PER_SEASON = 3
|
|
COUNT_PER_WEATHER = 2
|
|
MIN_LEN = 8
|
|
MAX_LEN = 30
|
|
|
|
RESPONSE_SCHEMA = {
|
|
"type": "object",
|
|
"properties": {
|
|
"tagline": {"type": "string"},
|
|
"general": {"type": "array", "items": {"type": "string"}},
|
|
"season": {
|
|
"type": "array",
|
|
"items": {
|
|
"type": "object",
|
|
"properties": {"season": {"type": "string", "enum": list(SEASONS)}, "text": {"type": "string"}},
|
|
"required": ["season", "text"],
|
|
},
|
|
},
|
|
"month": {
|
|
"type": "array",
|
|
"items": {
|
|
"type": "object",
|
|
"properties": {"month": {"type": "integer"}, "text": {"type": "string"}},
|
|
"required": ["month", "text"],
|
|
},
|
|
},
|
|
"weather": {
|
|
"type": "array",
|
|
"items": {
|
|
"type": "object",
|
|
"properties": {"weather": {"type": "string", "enum": list(WEATHERS)}, "text": {"type": "string"}},
|
|
"required": ["weather", "text"],
|
|
},
|
|
},
|
|
},
|
|
"required": ["tagline", "general", "season", "month", "weather"],
|
|
}
|
|
|
|
|
|
def build_prompt(
|
|
place_name: str,
|
|
facts: Sequence[FactLike],
|
|
records: Optional[Sequence[str]] = None,
|
|
) -> str:
|
|
sections = [
|
|
f"숙소 '{place_name}' 홈페이지 첫 화면 문구를 쓴다.",
|
|
"첫 화면은 숙소 이름 아래 **고정 캐치프레이즈 한 줄**, 그 아래 줄에서 짧은 문구가 몇 초마다 바뀐다.",
|
|
"",
|
|
"확인된 사실:",
|
|
_fact_lines(facts) if facts else "- 없음",
|
|
]
|
|
if records:
|
|
sections.extend(["", "출처가 있는 기록:", *(f"- {line}" for line in records)])
|
|
sections.extend([
|
|
"",
|
|
"출력:",
|
|
f"- tagline: 이 숙소만의 캐치프레이즈 1개, {MIN_LEN}~{MAX_LEN}자. 이 집을 한 줄로 알아보게 하는 문장",
|
|
f"- general: 계절·날씨와 상관없는 문구 {COUNT_GENERAL}개",
|
|
f"- season: 봄·여름·가을·겨울 각 {COUNT_PER_SEASON}개",
|
|
"- month: 1월~12월 각 1개",
|
|
f"- weather: 맑음·흐림·비·눈 각 {COUNT_PER_WEATHER}개. 그 날씨에 이 집에서 보이고 들리는 것",
|
|
f"- 모든 문구는 공백 포함 {MIN_LEN}~{MAX_LEN}자, 한 문장",
|
|
"",
|
|
"좋은 예(다른 숙소): '백 년 된 집에서 하룻밤' · '담 너머는 히로쓰 가옥입니다' ·"
|
|
" '마당 평상에 앉으면 항구 소리가 들립니다' · '이불에서 볕 냄새가 납니다'",
|
|
"",
|
|
"규칙:",
|
|
"- 위 사실과 기록에 있는 것만 쓴다. 없는 시설·경관·숫자·거리·역사를 지어내지 않는다.",
|
|
"- 사실이 적으면 머무는 시간·쉼처럼 확인이 필요 없는 말로 쓴다.",
|
|
"- 담백하게 쓴다. '최고', '완벽한', '특별한', '힐링' 같은 홍보 수식어와 느낌표를 쓰지 않는다.",
|
|
"- 문구끼리 표현이 겹치지 않게 한다. 상호명은 tagline 에도 넣지 않는다.",
|
|
"- false·불가·없음 값을 있는 것처럼 쓰지 않는다.",
|
|
"- 존댓말 평서문 또는 명사형으로 끝낸다.",
|
|
])
|
|
return "\n".join(sections)
|