Compare commits
No commits in common. "262adbf7628a4da1c5e887b0398b262524b2028a" and "1fffb7a0a7c4d5ff4a066a9d1482a224fdbda6b5" have entirely different histories.
262adbf762
...
1fffb7a0a7
2 changed files with 8 additions and 25 deletions
|
|
@ -1240,16 +1240,11 @@ def _fetch_dkp_corridor(
|
||||||
max(factors_applied),
|
max(factors_applied),
|
||||||
SBER_TIME_ADJUST_REGION,
|
SBER_TIME_ADJUST_REGION,
|
||||||
)
|
)
|
||||||
# #1520: используем P10/P90 вместо абсолютных min/max, чтобы коридор был
|
|
||||||
# устойчив к выбросам (один нерыночный ДКП не сдвигает границу).
|
|
||||||
# При маленьких выборках (n < 10) _percentile с линейной интерполяцией
|
|
||||||
# плавно сжимается к первому/последнему элементу — специального fallback
|
|
||||||
# не нужно (n=3: P10 ≈ индекс 0.2, P90 ≈ индекс 2.8 → оба внутри диапазона).
|
|
||||||
return {
|
return {
|
||||||
"count": len(ppm2_values),
|
"count": len(ppm2_values),
|
||||||
"low_ppm2": int(_percentile(ppm2_values, 0.10)),
|
"low_ppm2": int(ppm2_values[0]),
|
||||||
"median_ppm2": int(_percentile(ppm2_values, 0.5)),
|
"median_ppm2": int(_percentile(ppm2_values, 0.5)),
|
||||||
"high_ppm2": int(_percentile(ppm2_values, 0.90)),
|
"high_ppm2": int(ppm2_values[-1]),
|
||||||
"period_months": period_months,
|
"period_months": period_months,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -177,14 +177,7 @@ def test_dkp_corridor_none_without_street() -> None:
|
||||||
|
|
||||||
|
|
||||||
def test_dkp_corridor_aggregates_ppm2() -> None:
|
def test_dkp_corridor_aggregates_ppm2() -> None:
|
||||||
"""Считает low/median/high ₽/м² по сделкам.
|
"""Считает low/median/high ₽/м² по сделкам."""
|
||||||
|
|
||||||
#1520: low/high — P10/P90 (не min/max), устойчивы к выбросам.
|
|
||||||
Для 3 значений [100k, 120k, 140k]:
|
|
||||||
P10 = 100k + (120k-100k)*0.2 = 104k
|
|
||||||
P50 = 120k
|
|
||||||
P90 = 120k + (140k-120k)*0.8 = 136k
|
|
||||||
"""
|
|
||||||
mock_db = MagicMock()
|
mock_db = MagicMock()
|
||||||
mock_db.execute.return_value.mappings.return_value.all.return_value = [
|
mock_db.execute.return_value.mappings.return_value.all.return_value = [
|
||||||
{"price_per_m2": 100_000},
|
{"price_per_m2": 100_000},
|
||||||
|
|
@ -195,9 +188,9 @@ def test_dkp_corridor_aggregates_ppm2() -> None:
|
||||||
res = _fetch_dkp_corridor(mock_db, address="улица Ленина, 5", rooms=2, area=50.0)
|
res = _fetch_dkp_corridor(mock_db, address="улица Ленина, 5", rooms=2, area=50.0)
|
||||||
assert res is not None
|
assert res is not None
|
||||||
assert res["count"] == 3
|
assert res["count"] == 3
|
||||||
assert res["low_ppm2"] == 104_000 # P10
|
assert res["low_ppm2"] == 100_000
|
||||||
assert res["median_ppm2"] == 120_000
|
assert res["median_ppm2"] == 120_000
|
||||||
assert res["high_ppm2"] == 136_000 # P90
|
assert res["high_ppm2"] == 140_000
|
||||||
|
|
||||||
|
|
||||||
# ── #651: anchor band-guard (WHERE clause is exercised via bind params) ─────────
|
# ── #651: anchor band-guard (WHERE clause is exercised via bind params) ─────────
|
||||||
|
|
@ -275,12 +268,7 @@ def test_blend_weight_clamped_below_zero() -> None:
|
||||||
|
|
||||||
|
|
||||||
def test_dkp_corridor_count_below_three_still_aggregates() -> None:
|
def test_dkp_corridor_count_below_three_still_aggregates() -> None:
|
||||||
"""count<3 → corridor всё равно вычисляется (sanity-bound branch уже у caller'а).
|
"""count<3 → corridor всё равно вычисляется (sanity-bound branch уже у caller'а)."""
|
||||||
|
|
||||||
#1520: P10/P90 при n=2 [100k, 130k] (интерполяция по диапазону 30k):
|
|
||||||
P10 = 100k + 30k*0.1 = 103k
|
|
||||||
P90 = 100k + 30k*0.9 = 127k
|
|
||||||
"""
|
|
||||||
mock_db = MagicMock()
|
mock_db = MagicMock()
|
||||||
mock_db.execute.return_value.mappings.return_value.all.return_value = [
|
mock_db.execute.return_value.mappings.return_value.all.return_value = [
|
||||||
{"price_per_m2": 100_000},
|
{"price_per_m2": 100_000},
|
||||||
|
|
@ -290,8 +278,8 @@ def test_dkp_corridor_count_below_three_still_aggregates() -> None:
|
||||||
res = _fetch_dkp_corridor(mock_db, address="улица Ленина, 5", rooms=2, area=50.0)
|
res = _fetch_dkp_corridor(mock_db, address="улица Ленина, 5", rooms=2, area=50.0)
|
||||||
assert res is not None
|
assert res is not None
|
||||||
assert res["count"] == 2
|
assert res["count"] == 2
|
||||||
assert res["low_ppm2"] == 103_000 # P10
|
assert res["low_ppm2"] == 100_000
|
||||||
assert res["high_ppm2"] == 127_000 # P90
|
assert res["high_ppm2"] == 130_000
|
||||||
|
|
||||||
|
|
||||||
# ── #651: expected_sold ↔ blended-median consistency (full estimate path) ───────
|
# ── #651: expected_sold ↔ blended-median consistency (full estimate path) ───────
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue