feat(realestate-lab): 청약 가점제 계산 (84점 만점)
- calculate_subscription_points(): 무주택기간(32) + 부양가족(35) + 통장기간(17) - 프로필 GET/PUT 응답에 subscription_points 포함 - 매칭 결과 API에 my_points 포함 (가점 비교용) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -449,6 +449,81 @@ def upsert_model(data: Dict[str, Any]):
|
||||
""", data)
|
||||
|
||||
|
||||
# ── 청약 가점 계산 ───────────────────────────────────────────────────────────
|
||||
|
||||
def calculate_subscription_points(profile: Dict[str, Any]) -> Dict[str, Any]:
|
||||
"""청약 가점제 점수 계산 (총 84점 만점).
|
||||
|
||||
1. 무주택기간 (0~32점): 만 30세부터 기산, 연 2점
|
||||
2. 부양가족 수 (0~35점): 인당 5점, 6명+ 만점
|
||||
3. 청약통장 가입기간 (0~17점): 6개월 미만 1점 ~ 15년+ 17점
|
||||
"""
|
||||
result = {
|
||||
"homeless_duration": {"score": 0, "max": 32, "detail": ""},
|
||||
"dependents": {"score": 0, "max": 35, "detail": ""},
|
||||
"subscription_period": {"score": 0, "max": 17, "detail": ""},
|
||||
"total": 0,
|
||||
"max_total": 84,
|
||||
}
|
||||
|
||||
if not profile:
|
||||
return result
|
||||
|
||||
# 1. 무주택기간 (만 30세부터 기산, 연 2점, 최대 32점)
|
||||
age = profile.get("age") or 0
|
||||
is_homeless = profile.get("is_homeless", False)
|
||||
if is_homeless and age >= 30:
|
||||
homeless_years = age - 30
|
||||
score = min(homeless_years * 2, 32)
|
||||
# 1년 미만도 2점
|
||||
if homeless_years == 0:
|
||||
score = 2
|
||||
result["homeless_duration"]["score"] = score
|
||||
result["homeless_duration"]["detail"] = f"만 {age}세, 무주택 약 {homeless_years}년"
|
||||
elif is_homeless and age < 30:
|
||||
result["homeless_duration"]["score"] = 0
|
||||
result["homeless_duration"]["detail"] = f"만 {age}세 (30세 미만, 기간 미산정)"
|
||||
else:
|
||||
result["homeless_duration"]["detail"] = "유주택자"
|
||||
|
||||
# 2. 부양가족 수 (인당 5점, 최대 35점)
|
||||
family_members = profile.get("family_members") or 0
|
||||
dependents = max(family_members - 1, 0) # 본인 제외
|
||||
dep_score = min(dependents * 5, 35)
|
||||
result["dependents"]["score"] = dep_score
|
||||
result["dependents"]["detail"] = f"{dependents}명" if dependents > 0 else "0명 (본인만)"
|
||||
|
||||
# 3. 청약통장 가입기간 (6개월 미만 1점, 이후 1년마다 +1점, 최대 17점)
|
||||
months = profile.get("subscription_months") or 0
|
||||
if months <= 0:
|
||||
sub_score = 0
|
||||
sub_detail = "미가입"
|
||||
elif months < 6:
|
||||
sub_score = 1
|
||||
sub_detail = f"{months}개월 (6개월 미만)"
|
||||
else:
|
||||
years = months / 12
|
||||
# 6개월~1년 = 2점, 1~2년 = 3점, ..., 14~15년 = 16점, 15년+ = 17점
|
||||
sub_score = min(int(years) + 2, 17)
|
||||
if years < 1:
|
||||
sub_score = 2
|
||||
if years >= 1:
|
||||
y = int(years)
|
||||
sub_detail = f"{y}년 {months - y*12}개월"
|
||||
else:
|
||||
sub_detail = f"{months}개월"
|
||||
result["subscription_period"]["score"] = sub_score
|
||||
result["subscription_period"]["detail"] = sub_detail
|
||||
|
||||
result["total"] = (
|
||||
result["homeless_duration"]["score"]
|
||||
+ result["dependents"]["score"]
|
||||
+ result["subscription_period"]["score"]
|
||||
)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
# ── user_profile CRUD ────────────────────────────────────────────────────────
|
||||
|
||||
def _profile_row_to_dict(r) -> Dict[str, Any]:
|
||||
@@ -468,7 +543,11 @@ def _profile_row_to_dict(r) -> Dict[str, Any]:
|
||||
def get_profile() -> Optional[Dict[str, Any]]:
|
||||
with _conn() as conn:
|
||||
r = conn.execute("SELECT * FROM user_profile WHERE id = 1").fetchone()
|
||||
return _profile_row_to_dict(r) if r else None
|
||||
if not r:
|
||||
return None
|
||||
profile = _profile_row_to_dict(r)
|
||||
profile["subscription_points"] = calculate_subscription_points(profile)
|
||||
return profile
|
||||
|
||||
|
||||
PROFILE_COLUMNS = {
|
||||
@@ -512,7 +591,9 @@ def upsert_profile(data: Dict[str, Any]) -> Dict[str, Any]:
|
||||
vals,
|
||||
)
|
||||
row = conn.execute("SELECT * FROM user_profile WHERE id = 1").fetchone()
|
||||
return _profile_row_to_dict(row)
|
||||
profile = _profile_row_to_dict(row)
|
||||
profile["subscription_points"] = calculate_subscription_points(profile)
|
||||
return profile
|
||||
|
||||
|
||||
# ── match_results CRUD ───────────────────────────────────────────────────────
|
||||
@@ -536,10 +617,18 @@ def save_match_result(data: Dict[str, Any]):
|
||||
def get_matches(page: int = 1, size: int = 20) -> Dict[str, Any]:
|
||||
offset = (page - 1) * size
|
||||
with _conn() as conn:
|
||||
# 프로필 가점 계산
|
||||
profile_row = conn.execute("SELECT * FROM user_profile WHERE id = 1").fetchone()
|
||||
points = None
|
||||
if profile_row:
|
||||
profile = _profile_row_to_dict(profile_row)
|
||||
points = calculate_subscription_points(profile)
|
||||
|
||||
total = conn.execute("SELECT COUNT(*) FROM match_results").fetchone()[0]
|
||||
rows = conn.execute("""
|
||||
SELECT m.*, a.house_nm, a.region_name, a.address, a.status as ann_status,
|
||||
a.receipt_start, a.receipt_end, a.winner_date, a.pblanc_url
|
||||
a.receipt_start, a.receipt_end, a.winner_date, a.pblanc_url,
|
||||
a.house_secd, a.is_speculative_area
|
||||
FROM match_results m
|
||||
JOIN announcements a ON a.id = m.announcement_id
|
||||
ORDER BY m.is_new DESC, m.match_score DESC
|
||||
@@ -552,7 +641,13 @@ def get_matches(page: int = 1, size: int = 20) -> Dict[str, Any]:
|
||||
d["match_reasons"] = json.loads(d["match_reasons"]) if d["match_reasons"] else []
|
||||
d["eligible_types"] = json.loads(d["eligible_types"]) if d["eligible_types"] else []
|
||||
items.append(d)
|
||||
return {"items": items, "total": total, "page": page, "size": size}
|
||||
return {
|
||||
"items": items,
|
||||
"total": total,
|
||||
"page": page,
|
||||
"size": size,
|
||||
"my_points": points,
|
||||
}
|
||||
|
||||
|
||||
def mark_match_read(match_id: int) -> bool:
|
||||
|
||||
Reference in New Issue
Block a user