feat(realestate): 5축 점수 breakdown + 대시보드 pass_count

- matcher: _compute_score()에 score_breakdown {region/type/area/price/eligibility} 반환
- matcher: run_matching() DB INSERT에 score_breakdown JSON 저장
- db: match_results에 score_breakdown 컬럼 마이그레이션
- db: _enrich_items / get_matches에서 score_breakdown 파싱 포함
- db: get_matches에 a.district 컬럼 추가
- db: get_dashboard()에 pass_count (min_match_score 임계값 통과 건수) 추가

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-01 08:56:27 +09:00
parent f80683ce82
commit caacb072a2
2 changed files with 36 additions and 5 deletions

View File

@@ -113,27 +113,33 @@ def _compute_score(
preferred_types = profile.get("preferred_types") or []
house_secd = ann.get("house_secd") or ""
type_name = _HOUSE_TYPE_MAP.get(house_secd, house_secd)
type_score = 0
if type_name and type_name in preferred_types:
type_score = 10
score += 10
reasons.append(f"선호 유형 일치: {type_name}")
# 3. 면적 (15점) — binary, 범위 안 모델 1개라도 있으면 통과
min_area = profile.get("min_area")
max_area = profile.get("max_area")
area_score = 0
if min_area is not None and max_area is not None and models:
for m in models:
supply_area = m.get("supply_area")
if supply_area is not None and min_area <= supply_area <= max_area:
area_score = 15
score += 15
reasons.append(f"희망 면적 범위 내 모델 존재 ({supply_area}㎡)")
break
# 4. 가격 (15점) — binary, 예산 이하 모델 1개라도 있으면 통과
max_price = profile.get("max_price")
price_score = 0
if max_price is not None and models:
for m in models:
top_amount = m.get("top_amount")
if top_amount is not None and top_amount <= max_price:
price_score = 15
score += 15
reasons.append(f"예산 범위 내 모델 존재 (최고가 {top_amount:,}만원)")
break
@@ -149,6 +155,13 @@ def _compute_score(
"match_score": score,
"match_reasons": reasons,
"eligible_types": eligible_types,
"score_breakdown": {
"region": region_score,
"type": type_score,
"area": area_score,
"price": price_score,
"eligibility": elig_score,
},
}
@@ -178,18 +191,20 @@ def run_matching():
result = _compute_score(profile, ann, model_list)
if result["match_score"] > 0:
conn.execute("""
INSERT INTO match_results (announcement_id, model_id, match_score, match_reasons, eligible_types, is_new)
VALUES (?, ?, ?, ?, ?, 1)
INSERT INTO match_results (announcement_id, model_id, match_score, match_reasons, eligible_types, score_breakdown, is_new)
VALUES (?, ?, ?, ?, ?, ?, 1)
ON CONFLICT(announcement_id, model_id) DO UPDATE SET
match_score=excluded.match_score,
match_reasons=excluded.match_reasons,
eligible_types=excluded.eligible_types
eligible_types=excluded.eligible_types,
score_breakdown=excluded.score_breakdown
""", (
ann["id"],
None,
result["match_score"],
json.dumps(result["match_reasons"]),
json.dumps(result["eligible_types"]),
json.dumps(result["score_breakdown"]),
))
# Clean up stale match results for completed announcements