perf(realestate): run_listing_matching 단일 conn+배치 + POST /listings/rematch(MOLIT없이 즉시 재판정)
- get_market_deals_for에 optional conn 파라미터 추가(재사용 시 open/close 안 함, 기존 단건 호출부는 불변) - bulk_upsert_listing_matches 추가(listing_matches 다건 upsert, 단일 connection) - run_listing_matching이 listings 조회+매물별 market_deals 조회+최종 upsert를 단일 _conn()으로 처리 (기존: 269건 매물마다 개별 _conn() 3회 → 병목) - POST /api/realestate/listings/rematch: MOLIT 재수집 없이 기존 listings+market_deals로 즉시 재판정 (알림 미발송, 즉시 피드백용) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EqCYBhvTcdeCTUDX3RhWx9
This commit is contained in:
@@ -2,8 +2,8 @@
|
||||
import logging
|
||||
import statistics
|
||||
|
||||
from .db import (get_listing_criteria, get_market_deals_for, upsert_listing_match,
|
||||
get_listings)
|
||||
from .db import (get_listing_criteria, get_market_deals_for, bulk_upsert_listing_matches,
|
||||
_conn)
|
||||
from . import finance_rules
|
||||
|
||||
logger = logging.getLogger("realestate-lab")
|
||||
@@ -74,37 +74,47 @@ def match_listing(criteria, listing, budget) -> dict:
|
||||
|
||||
|
||||
def run_listing_matching() -> None:
|
||||
"""미평가 포함 전체 매물 재평가 → listing_matches upsert (청약 run_matching 대칭)."""
|
||||
"""미평가 포함 전체 매물 재평가 → listing_matches upsert (청약 run_matching 대칭).
|
||||
listings 조회 + 매물별 market_deals 조회 + 최종 upsert를 단일 connection으로 처리
|
||||
(Task 2 성능개선: 이전엔 매물마다 get_market_deals_for/upsert_listing_match가 각각
|
||||
자체 _conn()을 열어 269건×~3conn이 발생했다)."""
|
||||
criteria = get_listing_criteria()
|
||||
equity = criteria.get("equity") or 0
|
||||
income = criteria.get("annual_income") or 0
|
||||
jeonse_budget = finance_rules.estimate_jeonse_budget(equity, income)
|
||||
for l in get_listings(limit=1000):
|
||||
is_rent = l["deal_type"] != "매매"
|
||||
reg = finance_rules.region_regulation(l.get("dong"))
|
||||
if is_rent:
|
||||
budget = jeonse_budget
|
||||
else:
|
||||
budget = finance_rules.estimate_purchase_budget(
|
||||
equity, income, reg, criteria.get("is_first_home"), criteria.get("is_homeless"))
|
||||
m = match_listing(criteria, l, budget)
|
||||
deals = get_market_deals_for(l.get("dong_code"), l.get("complex_name"),
|
||||
l.get("area_exclusive"), "전세" if is_rent else "매매")
|
||||
rec = {"listing_id": l["id"], "category": "임차" if is_rent else "매매",
|
||||
"passed": 1 if m["passed"] else 0, "match_score": m["score"],
|
||||
"sample_size": 0, "reasons": m["reasons"], "is_new": 1,
|
||||
"regulation_flags": reg_flags(reg)}
|
||||
if is_rent:
|
||||
s = compute_safety(l.get("deposit"), l["deal_type"], deals)
|
||||
rec.update({"jeonse_ratio": s["jeonse_ratio"], "safety_tier": s["tier"],
|
||||
"market_median": s["median"], "sample_size": s["sample"]})
|
||||
else:
|
||||
v = compute_valuation(l.get("sale_price"), deals)
|
||||
rec.update({"price_ratio": v["price_ratio"], "valuation_tier": v["tier"],
|
||||
"market_median": v["median"], "sample_size": v["sample"],
|
||||
"budget_ok": 1 if (l.get("sale_price") or 0) <= (budget.get("max_price") or 0) else 0})
|
||||
upsert_listing_match(rec)
|
||||
logger.info("매물 매칭 완료")
|
||||
with _conn() as conn:
|
||||
listings = [dict(r) for r in conn.execute(
|
||||
"SELECT * FROM listings ORDER BY created_at DESC LIMIT 1000"
|
||||
).fetchall()]
|
||||
recs = []
|
||||
for l in listings:
|
||||
is_rent = l["deal_type"] != "매매"
|
||||
reg = finance_rules.region_regulation(l.get("dong"))
|
||||
if is_rent:
|
||||
budget = jeonse_budget
|
||||
else:
|
||||
budget = finance_rules.estimate_purchase_budget(
|
||||
equity, income, reg, criteria.get("is_first_home"), criteria.get("is_homeless"))
|
||||
m = match_listing(criteria, l, budget)
|
||||
deals = get_market_deals_for(l.get("dong_code"), l.get("complex_name"),
|
||||
l.get("area_exclusive"), "전세" if is_rent else "매매",
|
||||
conn=conn)
|
||||
rec = {"listing_id": l["id"], "category": "임차" if is_rent else "매매",
|
||||
"passed": 1 if m["passed"] else 0, "match_score": m["score"],
|
||||
"sample_size": 0, "reasons": m["reasons"], "is_new": 1,
|
||||
"regulation_flags": reg_flags(reg)}
|
||||
if is_rent:
|
||||
s = compute_safety(l.get("deposit"), l["deal_type"], deals)
|
||||
rec.update({"jeonse_ratio": s["jeonse_ratio"], "safety_tier": s["tier"],
|
||||
"market_median": s["median"], "sample_size": s["sample"]})
|
||||
else:
|
||||
v = compute_valuation(l.get("sale_price"), deals)
|
||||
rec.update({"price_ratio": v["price_ratio"], "valuation_tier": v["tier"],
|
||||
"market_median": v["median"], "sample_size": v["sample"],
|
||||
"budget_ok": 1 if (l.get("sale_price") or 0) <= (budget.get("max_price") or 0) else 0})
|
||||
recs.append(rec)
|
||||
bulk_upsert_listing_matches(recs, conn=conn)
|
||||
logger.info("매물 매칭 완료(%d건)", len(recs))
|
||||
|
||||
|
||||
def reg_flags(reg: dict) -> list:
|
||||
|
||||
Reference in New Issue
Block a user