feat(realestate): 재무·규제 규칙(전세/매수 예산·토허·LTV·DSR config)
This commit is contained in:
67
realestate-lab/app/finance_rules.py
Normal file
67
realestate-lab/app/finance_rules.py
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
"""전세/매수 예산·규제 판정 (순수 함수, 외부 호출 없음)."""
|
||||||
|
from . import finance_rules_config as cfg
|
||||||
|
|
||||||
|
|
||||||
|
def region_regulation(dong: str) -> dict:
|
||||||
|
d = (dong or "").strip()
|
||||||
|
is_toheo = d in cfg.TOHEO_DONGS
|
||||||
|
is_regulated = d in cfg.REGULATED_DONGS or is_toheo
|
||||||
|
notes = []
|
||||||
|
if is_toheo:
|
||||||
|
notes.append("토지거래허가구역 — 매수 시 실거주 의무(2년)+허가 필요, 갭투자 금지")
|
||||||
|
if is_regulated:
|
||||||
|
notes.append("규제지역 — LTV 강화")
|
||||||
|
notes.append("토허/규제 여부는 서울시 최신 고시로 확인 필요")
|
||||||
|
return {"is_toheo": is_toheo, "is_regulated": is_regulated, "notes": " · ".join(notes)}
|
||||||
|
|
||||||
|
|
||||||
|
def _dsr_max_loan(annual_income: int) -> int:
|
||||||
|
"""DSR 한도 내 대출 원금 근사(만원). 원리금균등, 가정 금리·만기."""
|
||||||
|
if not annual_income or annual_income <= 0:
|
||||||
|
return 0
|
||||||
|
annual_pay_cap = annual_income * cfg.DSR_LIMIT_PCT / 100.0 # 연 상환 가능액
|
||||||
|
r = cfg.ASSUMED_RATE_PCT / 100.0 / 12
|
||||||
|
n = cfg.ASSUMED_TERM_YEARS * 12
|
||||||
|
monthly_cap = annual_pay_cap / 12.0
|
||||||
|
if r == 0:
|
||||||
|
return int(monthly_cap * n)
|
||||||
|
# PV = PMT * (1-(1+r)^-n)/r
|
||||||
|
pv = monthly_cap * (1 - (1 + r) ** (-n)) / r
|
||||||
|
return int(pv)
|
||||||
|
|
||||||
|
|
||||||
|
def estimate_jeonse_budget(equity: int, annual_income: int, jeonse_loan_ratio: float = None) -> dict:
|
||||||
|
equity = equity or 0
|
||||||
|
ratio = jeonse_loan_ratio if jeonse_loan_ratio is not None else cfg.JEONSE_LOAN_RATIO
|
||||||
|
# 전세대출 한도 = min(보증기관 절대상한, DSR 한도). 보증금 비례는 매물별로 matcher에서 재확인.
|
||||||
|
loan_limit = min(cfg.JEONSE_LOAN_CAP, _dsr_max_loan(annual_income) or cfg.JEONSE_LOAN_CAP)
|
||||||
|
max_deposit = equity + loan_limit
|
||||||
|
return {
|
||||||
|
"loan_limit": loan_limit,
|
||||||
|
"max_deposit": max_deposit,
|
||||||
|
"notes": f"전세대출 한도≈{loan_limit}만(보증기관·DSR 근사, 보증금 대비 {int(ratio*100)}% 이내). {cfg.DISCLAIMER}",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def estimate_purchase_budget(equity: int, annual_income: int, region_flags: dict,
|
||||||
|
is_first_home: bool, is_homeless: bool) -> dict:
|
||||||
|
equity = equity or 0
|
||||||
|
reg = "regulated" if (region_flags or {}).get("is_regulated") else "normal"
|
||||||
|
first = "first" if is_first_home else "normal"
|
||||||
|
ltv_pct = cfg.LTV_TABLE[(reg, first)]
|
||||||
|
# 대출가능액 = min(수도권 한도, DSR 한도). LTV는 매매가에 따라 matcher에서 상한 재확인.
|
||||||
|
dsr_cap = _dsr_max_loan(annual_income)
|
||||||
|
loan_cap = min(cfg.SUDOGWON_MORTGAGE_CAP, dsr_cap) if dsr_cap else cfg.SUDOGWON_MORTGAGE_CAP
|
||||||
|
max_price = equity + loan_cap
|
||||||
|
flags = []
|
||||||
|
if (region_flags or {}).get("is_toheo"):
|
||||||
|
flags.append("토허")
|
||||||
|
if (region_flags or {}).get("is_regulated"):
|
||||||
|
flags.append("규제지역")
|
||||||
|
return {
|
||||||
|
"ltv_pct": ltv_pct,
|
||||||
|
"loan_cap": loan_cap,
|
||||||
|
"max_price": max_price,
|
||||||
|
"dsr_note": f"DSR {cfg.DSR_LIMIT_PCT}% 한도 근사(금리{cfg.ASSUMED_RATE_PCT}%·{cfg.ASSUMED_TERM_YEARS}년)",
|
||||||
|
"regulation_flags": flags,
|
||||||
|
}
|
||||||
35
realestate-lab/app/finance_rules_config.py
Normal file
35
realestate-lab/app/finance_rules_config.py
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
"""정책 파라미터 (외부 호출 없음). ⚠️ 정부 정책 수시 변동 — 계약·대출 전 은행/서울시 고시로 최종 확인.
|
||||||
|
기준: 2025 6·27 대책(수도권 주담대 6억 한도) 등. 실제 집행 전 최신값으로 갱신."""
|
||||||
|
|
||||||
|
# 수도권 주택담보대출 총액 한도 (만원). 6·27 대책: 6억.
|
||||||
|
SUDOGWON_MORTGAGE_CAP = 60000
|
||||||
|
|
||||||
|
# LTV(%) — (규제지역 여부, 생애최초 여부) → 한도. 무주택 기준.
|
||||||
|
LTV_TABLE = {
|
||||||
|
("regulated", "first"): 70,
|
||||||
|
("regulated", "normal"): 50,
|
||||||
|
("normal", "first"): 80,
|
||||||
|
("normal", "normal"): 70,
|
||||||
|
}
|
||||||
|
|
||||||
|
# 스트레스 DSR 한도(%) — 연소득 대비 연원리금.
|
||||||
|
DSR_LIMIT_PCT = 40
|
||||||
|
# 대출 산정용 가정 금리(연%)·만기(년) — 원리금균등 근사.
|
||||||
|
ASSUMED_RATE_PCT = 4.5
|
||||||
|
ASSUMED_TERM_YEARS = 30
|
||||||
|
|
||||||
|
# 전세대출: 보증기관 보증비율(보증금 대비) 상한 근사.
|
||||||
|
JEONSE_LOAN_RATIO = 0.80
|
||||||
|
# 전세대출 절대 상한(만원) — 보증기관 한도 근사.
|
||||||
|
JEONSE_LOAN_CAP = 44000
|
||||||
|
|
||||||
|
# 토지거래허가구역(구·동 단위, 서울시 고시 기준, 수동 갱신). 소수만.
|
||||||
|
TOHEO_DONGS = {
|
||||||
|
"대치동", "삼성동", "청담동", "잠실동", # 강남·송파 국제교류복합지구권
|
||||||
|
"압구정동", "여의도동", "목동", "성수동", # 재건축 토허
|
||||||
|
"이촌동", "한남동", # 용산
|
||||||
|
}
|
||||||
|
# 규제지역(투기과열/조정) — 매수 LTV 강화 대상.
|
||||||
|
REGULATED_DONGS = set(TOHEO_DONGS) | {"반포동", "서초동"}
|
||||||
|
|
||||||
|
DISCLAIMER = "정책(LTV·DSR·수도권 한도·토허)은 수시 변동 — 계약·대출 전 은행/중개사/서울시 고시로 최종 확인."
|
||||||
21
realestate-lab/tests/test_finance_rules.py
Normal file
21
realestate-lab/tests/test_finance_rules.py
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
def test_region_regulation_toheo_and_normal():
|
||||||
|
from app.finance_rules import region_regulation
|
||||||
|
assert region_regulation("대치동")["is_toheo"] is True # 강남 토허
|
||||||
|
r = region_regulation("신대방동")
|
||||||
|
assert r["is_toheo"] is False and "확인" in r["notes"]
|
||||||
|
|
||||||
|
def test_jeonse_budget_equity_plus_loan():
|
||||||
|
from app.finance_rules import estimate_jeonse_budget
|
||||||
|
b = estimate_jeonse_budget(equity=15000, annual_income=6000) # 자기자금 1.5억
|
||||||
|
assert b["max_deposit"] == b["loan_limit"] + 15000
|
||||||
|
assert b["loan_limit"] > 0
|
||||||
|
|
||||||
|
def test_purchase_budget_seoul_cap_applies():
|
||||||
|
from app.finance_rules import region_regulation, estimate_purchase_budget
|
||||||
|
flags = region_regulation("상도동")
|
||||||
|
b = estimate_purchase_budget(equity=30000, annual_income=8000,
|
||||||
|
region_flags=flags, is_first_home=False, is_homeless=True)
|
||||||
|
# 수도권 주담대 한도(6·27 대책 6억=60000만원) 캡이 대출가능액에 반영
|
||||||
|
assert b["loan_cap"] <= 60000
|
||||||
|
assert b["max_price"] == b["loan_cap"] + 30000
|
||||||
|
assert 0 < b["ltv_pct"] <= 100
|
||||||
Reference in New Issue
Block a user