o2o-site-AEO/solution/backend/services/external/restaurant_discovery.py
김성경 a093921b62 feat(backend,site): NOL 수집 및 숙박 안내 구조화와 지역 콘텐츠 연동
수집한 객실·이용 정보가 발행 화면에 연결되지 않던 경로를 보완하고, 숙소 소개와 지역 맛집 표시를 개선한다.

- NOL 브라우저 수집 어댑터와 수집·반영 스크립트 추가
- 크롤링 fact 즉시 노출 및 직접 입력·정정값 보호
- 이용안내 항목별 구조화와 기존 표 연결, 원문 UI 비표시
- 군산 한일옥 고정 등록과 지역 맛집 탐색·보강 경로 추가
- 숙소 소개 요약, 히어로 문구, 지역 콘텐츠·목업 표시 개선

검증: 작업 트리 기준 site 타입·린트·빌드 및 안내 렌더링 테스트 통과, PC·모바일 화면 확인. 스테이징 diff 공백 검사 통과. 사용자 요청에 따라 현재 스테이징된 55개 파일만 포함하며 미스테이징 문서·테스트 등은 제외.
2026-09-14 19:40:04 +09:00

58 lines
2.1 KiB
Python

"""지역명으로 맛집 후보 이름을 찾는다.
★ services/external/perplexity.py(채널 발견: 단일 업소 → URL)와는 다른 용도다 —
여기는 "이 지역에 뭐가 있나"를 묻는 지역 목록 검색이다. 원칙은 같다:
Perplexity 응답을 사실로 쓰지 않는다. 이름만 후보로 받고, 실제 값은 이후
네이버 크롤링(NaverPlaceAdapter)이 확정한다.
"""
import json
from common.logger import LOG
from services.llm.perplexity import DEFAULT_MAX_TOKENS, DEFAULT_MODEL, PerplexityError, call
from services.prompts.restaurant_search import RESPONSE_SCHEMA, SYSTEM_PROMPT, build_prompt
MAX_RESULTS = 10
def _parse_names(payload: dict) -> list[str]:
choices = payload.get("choices") or []
if not choices or not isinstance(choices[0], dict):
return []
content = ((choices[0].get("message") or {}).get("content")) or ""
try:
doc = json.loads(content)
except (json.JSONDecodeError, TypeError):
LOG.w("[restaurant_discovery] 구조화 출력 파싱 실패")
return []
names = doc.get("restaurants")
if not isinstance(names, list):
return []
return [str(n).strip() for n in names if str(n or "").strip()][:MAX_RESULTS]
async def search_region_restaurants(
region_label: str, *, model: str = DEFAULT_MODEL, client=None,
) -> list[str]:
"""지역명으로 맛집 상위 10곳의 이름만 받는다.
실패(미설정·타임아웃·5xx)하면 빈 목록 — 호출측이 TourAPI 결과만으로 계속 진행한다.
"""
if not (region_label or "").strip():
return []
body = {
"model": model,
"messages": [
{"role": "system", "content": SYSTEM_PROMPT},
{"role": "user", "content": build_prompt(region_label)},
],
"max_tokens": DEFAULT_MAX_TOKENS,
"temperature": 0,
"response_format": RESPONSE_SCHEMA,
}
try:
payload = await call(body, client=client)
except PerplexityError as ex:
LOG.w(f"[restaurant_discovery] '{region_label}' 검색 실패: {ex}")
return []
return _parse_names(payload)