Files
web-page-backend/realestate-lab/app/internal_router.py
gahusb 1fff511752 fix(realestate): compose INTERNAL_API_KEY(배포 401 방지) + 매칭·알림 공유락(중복 텔레그램 방지)
- docker-compose.yml realestate-lab environment에 INTERNAL_API_KEY/NAVER_PAGE_LIMIT 누락 추가
  (verify_internal_key 항상 401 → 워커 계약 전체 마비 방지, sibling image/video/music/insta-lab과 동형)
- pipeline_lock.py 신설: cron _run_listing_pipeline과 워커 listings_ingest가
  run_listing_matching()+notify_new_listings() 임계구역을 공유 threading.Lock으로 직렬화
  (동시 실행 시 동일 매물 중복 텔레그램 발송 방지). 느린 collect는 락 밖 유지.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EqCYBhvTcdeCTUDX3RhWx9
2026-07-10 00:50:55 +09:00

70 lines
2.3 KiB
Python

"""naver-fetch 워커 내부 계약: targets 조회 + listings ingest."""
import os
import logging
from typing import List, Dict, Any
from fastapi import APIRouter, Depends
from pydantic import BaseModel
from .auth import verify_internal_key
from .db import get_listing_criteria, upsert_listing
from .lawd_codes import naver_cortar
from .listing_collector import _parse_naver_article
from .listing_matcher import run_listing_matching
from .notifier import notify_new_listings
from .pipeline_lock import match_notify_lock
logger = logging.getLogger("realestate-lab")
router = APIRouter()
NAVER_PAGE_LIMIT = int(os.getenv("NAVER_PAGE_LIMIT", "2"))
@router.get("/api/internal/realestate/targets", dependencies=[Depends(verify_internal_key)])
def listing_targets():
crit = get_listing_criteria()
dongs = []
for d in crit.get("dongs", []):
code = naver_cortar(d)
if not code:
logger.warning("naver cortarNo 매핑 없음 — 대상 제외: %s", d)
continue
dongs.append({"dong": d, "cortar_no": code})
return {"dongs": dongs, "deal_types": crit.get("deal_types", []),
"page_limit": NAVER_PAGE_LIMIT}
class _Batch(BaseModel):
dong: str
articles: List[Dict[str, Any]] = []
class ListingsIngest(BaseModel):
fetched_at: str | None = None
batches: List[_Batch] = []
@router.post("/api/internal/realestate/listings-ingest",
dependencies=[Depends(verify_internal_key)])
def listings_ingest(body: ListingsIngest):
received = new = 0
for batch in body.batches:
for raw in batch.articles:
try:
d = _parse_naver_article(raw)
if not d.get("article_no"):
continue
d["dong"] = batch.dong
_, is_new = upsert_listing(d)
received += 1
if is_new:
new += 1
except Exception as e:
logger.warning("네이버 ingest 파싱 실패: %s", e)
matched = 0
if received:
with match_notify_lock:
run_listing_matching()
noti = notify_new_listings()
matched = int(noti.get("sent", 0)) if isinstance(noti, dict) else 0
return {"received": received, "new": new, "matched": matched}