"""naver-fetch 워커 내부 계약: targets 조회 + listings ingest.""" import os import time import logging from typing import List, Dict, Any from fastapi import APIRouter, Depends, BackgroundTasks 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, background_tasks: BackgroundTasks): 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) if received: background_tasks.add_task(_match_and_notify) return {"received": received, "new": new, "queued": bool(received)} def _match_and_notify(): """ingest 후처리(백그라운드): 매칭+알림을 공유 락으로 직렬화. 타이밍 로그로 병목 관측.""" with match_notify_lock: t0 = time.time() run_listing_matching() t1 = time.time() noti = notify_new_listings() t2 = time.time() sent = (noti or {}).get("sent") if isinstance(noti, dict) else noti logger.info("ingest 후처리: match=%.1fs notify=%.1fs sent=%s", t1 - t0, t2 - t1, sent)