fix(site-finder): non-overlapping L2/L3 ready_dt windows, stop double-count of 13-24mo supply (#1355) #1677
1 changed files with 19 additions and 3 deletions
|
|
@ -81,9 +81,14 @@ _EKB_REGION_CODE: int = 66
|
|||
# Ниже — слишком много исключённых (NULL free_flats) объектов → 'low' (см. limitations).
|
||||
_L2_MEDIUM_MIN_COVERAGE: float = 0.6
|
||||
|
||||
# L2/L3 граница (включительно в L2, исключительно в L3) — половинно-открытые интервалы:
|
||||
# L2 (hidden) : ready_dt IS NULL OR ready_dt <= now + L2_NEAR_HORIZON [now, now+12mo]
|
||||
# L3 (future) : ready_dt IS NOT NULL AND ready_dt > now + L3_FUTURE_HORIZON (now+12mo, ∞)
|
||||
# Оба используют одно и то же значение 12 мес., но по разные стороны границы — нет overlap.
|
||||
_L2_NEAR_HORIZON_MONTHS: int = 12 # L2 берёт объекты с ready_dt ≤ сейчас+12мес (или NULL)
|
||||
# L3: «будущее» = ready_dt дальше этого горизонта от сегодня. Раньше — это уже
|
||||
# почти-на-рынке предложение (ловится L1/L2), не «future».
|
||||
_L3_FUTURE_HORIZON_MONTHS: int = 12
|
||||
_L3_FUTURE_HORIZON_MONTHS: int = 12 # L3 берёт объекты с ready_dt > сейчас+12мес
|
||||
|
||||
# L3: «ещё не продаётся» = выставлено не больше этого числа квартир. >0 порог (не строго
|
||||
# 0) терпит единичные тестовые/служебные листинги в проекте, который по сути не открыт.
|
||||
|
|
@ -215,6 +220,10 @@ _L1_OPEN_SQL = text(
|
|||
# free_flats) к району. ИСКЛЮЧАЕМ free_flats IS NULL (нет sold-колонки → не вычислим;
|
||||
# documented). clamp ≥ 0 через GREATEST. Считаем coverage (всего объектов района vs с
|
||||
# free_flats) для confidence. NO join на objective_lots / complexes — намеренно.
|
||||
#
|
||||
# Граница L2/L3 (#1355): L2 захватывает ТОЛЬКО объекты с ready_dt ≤ горизонту (или NULL —
|
||||
# объект уже должен был выйти). ready_dt > горизонту → это L3 (future), НЕ L2.
|
||||
# Полу-открытые интервалы (half-open): L2 = [now, now+12mo], L3 = (now+12mo, ∞).
|
||||
_L2_HIDDEN_SQL = text(
|
||||
"""
|
||||
WITH latest AS (
|
||||
|
|
@ -222,7 +231,8 @@ _L2_HIDDEN_SQL = text(
|
|||
o.obj_id,
|
||||
o.district_name,
|
||||
o.flat_count,
|
||||
o.free_flats
|
||||
o.free_flats,
|
||||
o.ready_dt
|
||||
FROM domrf_kn_objects o
|
||||
WHERE o.region_cd = :region_cd
|
||||
AND o.district_name IS NOT NULL
|
||||
|
|
@ -240,6 +250,8 @@ _L2_HIDDEN_SQL = text(
|
|||
GREATEST(0, COALESCE(flat_count, 0) - free_flats)
|
||||
) FILTER (WHERE free_flats IS NOT NULL), 0) AS hidden_units
|
||||
FROM latest
|
||||
WHERE ready_dt IS NULL
|
||||
OR ready_dt <= CURRENT_DATE + CAST(:near_horizon AS interval)
|
||||
GROUP BY district_name
|
||||
"""
|
||||
)
|
||||
|
|
@ -368,7 +380,11 @@ def compute_layer2_hidden(
|
|||
source='domrf_declared'. obj_class=None (district-агрегат класс-agnostic).
|
||||
units_estimate всегда ≥ 0 (clamp). Пустые данные → []. Не бросает.
|
||||
"""
|
||||
params: dict[str, Any] = {"region_cd": _EKB_REGION_CODE, "district": district}
|
||||
params: dict[str, Any] = {
|
||||
"region_cd": _EKB_REGION_CODE,
|
||||
"district": district,
|
||||
"near_horizon": f"{_L2_NEAR_HORIZON_MONTHS} months",
|
||||
}
|
||||
rows = _safe_rows(db, _L2_HIDDEN_SQL, params, layer=2)
|
||||
|
||||
out: list[SupplyLayerRow] = []
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue