3 Commits

Author SHA1 Message Date
8411e2c73e feat(realestate): 결과발표 공고 매칭 점수 보존
run_matching() 대상을 '청약예정/청약중/결과발표'로 확장.
삭제는 '완료' 상태만 — 결과발표 단계의 매칭 기록은 유지.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-01 10:32:58 +09:00
86a6b75124 feat(realestate): API 수집 retry 로직 추가 (최대 2회, 지수 백오프)
페이지 요청 실패 시 즉시 중단 대신 1초·2초 대기 후 재시도.
2회 모두 실패 시에만 해당 엔드포인트 수집 종료, 나머지 엔드포인트 계속 진행.
JSON 파싱 오류는 재시도 없이 즉시 skip.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-01 10:32:26 +09:00
08a32e4357 feat(realestate): 신혼부부 특공 혼인 기간 검증 추가 (84개월 이내)
marriage_months 미입력 시 기존대로 통과, 85개월 이상이면 신혼부부 특공 자격 제외.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-01 10:30:39 +09:00
2 changed files with 29 additions and 15 deletions

View File

@@ -1,5 +1,6 @@
import os
import re
import time
import logging
from datetime import date, timedelta
import requests
@@ -53,17 +54,28 @@ def _api_call(endpoint: str, params: Dict[str, Any] = None) -> List[Dict]:
all_data: List[Dict] = []
page = 1
_MAX_RETRIES = 2
while True:
base_params["page"] = page
try:
resp = requests.get(url, params=base_params, timeout=30)
resp.raise_for_status()
body = resp.json()
except requests.RequestException as e:
logger.error("API 호출 실패 [%s page=%d]: %s", endpoint, page, e)
break
except ValueError as e:
logger.error("JSON 파싱 실패 [%s page=%d]: %s", endpoint, page, e)
body = None
for attempt in range(_MAX_RETRIES + 1):
try:
resp = requests.get(url, params=base_params, timeout=30)
resp.raise_for_status()
body = resp.json()
break
except requests.RequestException as e:
if attempt < _MAX_RETRIES:
logger.warning("API 호출 실패 [%s page=%d] — %d초 후 재시도 (%d/%d): %s",
endpoint, page, 2 ** attempt, attempt + 1, _MAX_RETRIES, e)
time.sleep(2 ** attempt)
else:
logger.error("API 호출 최종 실패 [%s page=%d]: %s", endpoint, page, e)
except ValueError as e:
logger.error("JSON 파싱 실패 [%s page=%d]: %s", endpoint, page, e)
break # 파싱 오류는 재시도 무의미
if body is None:
break
data = body.get("data", [])

View File

@@ -86,8 +86,10 @@ def _check_eligible_types(profile: Dict[str, Any], ann: Dict[str, Any]) -> List[
elif is_homeless:
eligible.append("일반2순위")
# 특별공급 — 신혼부부 (소득 160% 이하)
if profile.get("is_newlywed") and is_homeless:
# 특별공급 — 신혼부부 (소득 160% 이하, 혼인 84개월 이내)
marriage_months = profile.get("marriage_months")
newlywed_ok = (marriage_months is None) or (marriage_months <= 84)
if profile.get("is_newlywed") and is_homeless and newlywed_ok:
if income_pct is None or income_pct <= 160:
eligible.append("특별-신혼부부")
@@ -203,7 +205,7 @@ def run_matching():
profile = _profile_row_to_dict(profile_row)
anns = conn.execute(
"SELECT * FROM announcements WHERE status IN ('청약예정', '청약중')"
"SELECT * FROM announcements WHERE status IN ('청약예정', '청약중', '결과발표')"
).fetchall()
for ann_row in anns:
@@ -233,10 +235,10 @@ def run_matching():
json.dumps(result["score_breakdown"]),
))
# Clean up stale match results for completed announcements
# 완료 상태 공고의 매칭 결과만 삭제 (결과발표는 보존)
conn.execute(
"DELETE FROM match_results WHERE announcement_id NOT IN "
"(SELECT id FROM announcements WHERE status IN ('청약예정', '청약중'))"
"DELETE FROM match_results WHERE announcement_id IN "
"(SELECT id FROM announcements WHERE status = '완료')"
)
logger.info("매칭 완료")