Московская область считается по своему ряду Сбериндекса, а не по общероссийскому #3507
4 changed files with 59 additions and 20 deletions
|
|
@ -328,6 +328,13 @@ SBER_TIME_ADJUST_REGION = "Свердловская область" # ряд р
|
||||||
_SBER_REGION_SERIES: dict[int, str] = {
|
_SBER_REGION_SERIES: dict[int, str] = {
|
||||||
66: SBER_TIME_ADJUST_REGION,
|
66: SBER_TIME_ADJUST_REGION,
|
||||||
77: "Москва",
|
77: "Москва",
|
||||||
|
# Московская область — свой ряд, а не фолбэк на «Россию». Загрузчик научился
|
||||||
|
# тянуть REF_AREA=50 (services/sber_index), и 12.09.2026 ряд приехал на прод:
|
||||||
|
# real_estate_deals/«Вторичный», 116 месяцев 2017-01..2026-08 — та же глубина,
|
||||||
|
# что у Москвы и обл.66. До этой строки область считалась по общероссийскому
|
||||||
|
# ряду, то есть коридор ошибался на расхождение области со страной; теперь
|
||||||
|
# ошибка только внутрирегиональная.
|
||||||
|
50: "Московская область",
|
||||||
}
|
}
|
||||||
|
|
||||||
# Ряд для региона ВНЕ карты. Выбран общероссийский, а не отказ от поправки (1.0):
|
# Ряд для региона ВНЕ карты. Выбран общероссийский, а не отказ от поправки (1.0):
|
||||||
|
|
|
||||||
|
|
@ -233,9 +233,11 @@ REGIONS: dict[int, Region] = {
|
||||||
),
|
),
|
||||||
# Тиров обогащения у области пока НЕТ ни одного: IMV/квартальный
|
# Тиров обогащения у области пока НЕТ ни одного: IMV/квартальный
|
||||||
# индекс/кадастр/POI не заведены (проверено — frozenset() пуст
|
# индекс/кадастр/POI не заведены (проверено — frozenset() пуст
|
||||||
# намеренно, не заглушка). Ряд Сбериндекса по области загружен в
|
# намеренно, не заглушка). Ряд Сбериндекса по области с 12.09.2026 в
|
||||||
# источник (#3498), но в карте _SBER_REGION_SERIES эстиматора кода 50
|
# карте _SBER_REGION_SERIES эстиматора есть, но тиром он от этого не
|
||||||
# ЕЩЁ НЕТ — тир пока недоступен, добавляется отдельным решением.
|
# становится: у Москвы набор тиров тоже пуст, а свой ряд она читает —
|
||||||
|
# поправка по времени идёт мимо enrichment_tiers (TIER_SBER_INDEX нигде
|
||||||
|
# за пределами этого реестра не спрашивают).
|
||||||
enrichment_tiers=frozenset(),
|
enrichment_tiers=frozenset(),
|
||||||
# Источники по области несут настоящий city (Химки, Балашиха — не
|
# Источники по области несут настоящий city (Химки, Балашиха — не
|
||||||
# муниципальный округ/поселение, в отличие от Москвы) — перезаписывать
|
# муниципальный округ/поселение, в отличие от Москвы) — перезаписывать
|
||||||
|
|
|
||||||
|
|
@ -539,16 +539,24 @@ async def test_pull_sber_indices_asking_benchmark_logged(caplog: pytest.LogCaptu
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def test_moscow_oblast_ref_area_is_loaded_but_not_yet_used_by_estimator() -> None:
|
def test_moscow_oblast_ref_area_feeds_the_estimator_under_the_same_name() -> None:
|
||||||
"""Область грузится заранее: ряд наполняется месяцами, задним числом не берётся.
|
"""Имя ряда у загрузчика и у эстиматора обязано совпадать буква в букву.
|
||||||
|
|
||||||
Одновременно проверяем, что заведение ряда НЕ включает область в оценку и НЕ
|
Загрузчик завёл REF_AREA=50 раньше (#3498), когда карта эстиматора кода 50
|
||||||
поднимает тревогу монитора свежести: обе величины считаются от карты
|
ещё не знала и область считалась по «России». 12.09.2026 ряд приехал на прод
|
||||||
эстиматора (_SBER_REGION_SERIES), а не от карты загрузчика.
|
(real_estate_deals/«Вторичный», 116 месяцев 2017-01..2026-08), и код 50 попал
|
||||||
|
в `_SBER_REGION_SERIES`. С этого момента расхождение имён перестаёт быть
|
||||||
|
безобидным: эстиматор молча читал бы пустой ряд, а монитор свежести требовал
|
||||||
|
бы ряд, которого загрузчик не пишет. Тест сцепляет обе стороны по имени.
|
||||||
"""
|
"""
|
||||||
from app.services.estimator import SBER_MONITORED_REGIONS, SBER_REQUIRED_REGIONS
|
from app.services.estimator import (
|
||||||
|
SBER_MONITORED_REGIONS,
|
||||||
|
SBER_REQUIRED_REGIONS,
|
||||||
|
sber_region_series_name,
|
||||||
|
)
|
||||||
from app.services.sber_index import SBER_REF_AREAS
|
from app.services.sber_index import SBER_REF_AREAS
|
||||||
|
|
||||||
assert SBER_REF_AREAS["50"] == "Московская область"
|
assert SBER_REF_AREAS["50"] == "Московская область"
|
||||||
assert "Московская область" not in SBER_REQUIRED_REGIONS
|
assert sber_region_series_name(50) == SBER_REF_AREAS["50"]
|
||||||
assert "Московская область" not in SBER_MONITORED_REGIONS
|
assert "Московская область" in SBER_REQUIRED_REGIONS
|
||||||
|
assert "Московская область" in SBER_MONITORED_REGIONS
|
||||||
|
|
|
||||||
|
|
@ -88,6 +88,21 @@ def test_series_names_agree_with_region_registry() -> None:
|
||||||
assert regions_mod.REGIONS[code].name == name
|
assert regions_mod.REGIONS[code].name == name
|
||||||
|
|
||||||
|
|
||||||
|
def test_resolver_maps_50_to_moscow_oblast_not_russia() -> None:
|
||||||
|
"""Область получает СВОЙ ряд, а не общероссийский фолбэк.
|
||||||
|
|
||||||
|
Загрузчик научился тянуть REF_AREA=50 (#3498), и 12.09.2026 ряд приехал на
|
||||||
|
прод: real_estate_deals/«Вторичный», 116 месяцев 2017-01..2026-08 — та же
|
||||||
|
глубина, что у Москвы и обл.66. Пока кода 50 в карте не было, коридор
|
||||||
|
области считался по «России», то есть ошибался на всё расхождение области
|
||||||
|
со страной. Тест красный на прежней карте ПО ЗНАЧЕНИЮ: там вернулась бы
|
||||||
|
«Россия».
|
||||||
|
"""
|
||||||
|
assert est.sber_region_series_name(50) == "Московская область"
|
||||||
|
assert est.sber_region_series_name(50) != est.SBER_TIME_ADJUST_FALLBACK_REGION
|
||||||
|
assert "Московская область" in est.SBER_REQUIRED_REGIONS
|
||||||
|
|
||||||
|
|
||||||
def test_monitored_regions_cover_every_series_the_estimator_can_read() -> None:
|
def test_monitored_regions_cover_every_series_the_estimator_can_read() -> None:
|
||||||
assert set(est.SBER_MONITORED_REGIONS) == set(est._SBER_REGION_SERIES.values()) | {
|
assert set(est.SBER_MONITORED_REGIONS) == set(est._SBER_REGION_SERIES.values()) | {
|
||||||
est.SBER_TIME_ADJUST_FALLBACK_REGION
|
est.SBER_TIME_ADJUST_FALLBACK_REGION
|
||||||
|
|
@ -275,8 +290,15 @@ def test_monitor_verdict_for_sverdlovsk_unchanged(monkeypatch: pytest.MonkeyPatc
|
||||||
|
|
||||||
|
|
||||||
def _without_moscow() -> _MonDB:
|
def _without_moscow() -> _MonDB:
|
||||||
"""Источник переименовал «Москва» → ряда с прежним именем больше нет."""
|
"""Источник переименовал «Москва» → ряда с прежним именем больше нет.
|
||||||
return _MonDB({"Свердловская область": _JUN_2026, "Россия": _JUN_2026})
|
|
||||||
|
Остальные обязательные ряды на месте — иначе тесты ниже считали бы пропажу
|
||||||
|
Москвы вместе с чужими пропажами. Строятся от карты эстиматора, чтобы новый
|
||||||
|
регион в ней не ронял эти тесты по счётчику (ровно так они и покраснели,
|
||||||
|
когда в карту приехала область).
|
||||||
|
"""
|
||||||
|
present = {r: _JUN_2026 for r in est.SBER_MONITORED_REGIONS if r != "Москва"}
|
||||||
|
return _MonDB(present)
|
||||||
|
|
||||||
|
|
||||||
def test_missing_moscow_does_not_suppress_the_sverdlovsk_verdict(
|
def test_missing_moscow_does_not_suppress_the_sverdlovsk_verdict(
|
||||||
|
|
@ -352,7 +374,7 @@ def test_missing_fallback_series_does_not_fail_the_monitor(
|
||||||
) -> None:
|
) -> None:
|
||||||
"""«Россия» — фолбэк: сегодня по нему не считается ни одна сделка → не сбой."""
|
"""«Россия» — фолбэк: сегодня по нему не считается ни одна сделка → не сбой."""
|
||||||
calls = _patch_runs(monkeypatch)
|
calls = _patch_runs(monkeypatch)
|
||||||
db = _MonDB({"Свердловская область": _JUN_2026, "Москва": _JUN_2026})
|
db = _MonDB({r: _JUN_2026 for r in est.SBER_REQUIRED_REGIONS})
|
||||||
out = mon.check_sber_freshness(db, run_id=4, params={}, now=_NOW) # type: ignore[arg-type]
|
out = mon.check_sber_freshness(db, run_id=4, params={}, now=_NOW) # type: ignore[arg-type]
|
||||||
assert calls["failed"] is None
|
assert calls["failed"] is None
|
||||||
assert out["regions_missing"] == 1
|
assert out["regions_missing"] == 1
|
||||||
|
|
@ -457,15 +479,17 @@ def test_duplicate_series_name_does_not_pin_regions_missing(
|
||||||
def test_early_exit_fills_the_new_counters(monkeypatch: pytest.MonkeyPatch) -> None:
|
def test_early_exit_fills_the_new_counters(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||||
"""КРАСНЫЙ ДО ПРАВКИ: в раннем выходе новые ключи отдавали нули.
|
"""КРАСНЫЙ ДО ПРАВКИ: в раннем выходе новые ключи отдавали нули.
|
||||||
|
|
||||||
Пропажа ДВУХ обязательных рядов была неотличима от пропажи одного свердловского,
|
Пропажа ВСЕХ обязательных рядов была неотличима от пропажи одного
|
||||||
а найденный ряд не попадал в age_days_max вовсе.
|
свердловского, а найденный ряд не попадал в age_days_max вовсе. Счётчик
|
||||||
|
сверяем с длиной карты, а не с литералом: новый регион в ней — не повод
|
||||||
|
этому тесту краснеть.
|
||||||
"""
|
"""
|
||||||
calls = _patch_runs(monkeypatch)
|
calls = _patch_runs(monkeypatch)
|
||||||
db = _MonDB({est.SBER_TIME_ADJUST_FALLBACK_REGION: _JUN_2026})
|
db = _MonDB({est.SBER_TIME_ADJUST_FALLBACK_REGION: _JUN_2026})
|
||||||
out = mon.check_sber_freshness(db, run_id=11, params={}, now=_NOW) # type: ignore[arg-type]
|
out = mon.check_sber_freshness(db, run_id=11, params={}, now=_NOW) # type: ignore[arg-type]
|
||||||
assert calls["done"] is None
|
assert calls["done"] is None
|
||||||
assert calls["failed"] is not None
|
assert calls["failed"] is not None
|
||||||
assert out["regions_missing"] == 2
|
assert out["regions_missing"] == len(est.SBER_REQUIRED_REGIONS)
|
||||||
assert out["alert_regions_missing"] == 1 # ДО ПРАВКИ: 0
|
assert out["alert_regions_missing"] == 1 # ДО ПРАВКИ: 0
|
||||||
assert out["age_days_max"] == 72 # ДО ПРАВКИ: 0 — найденный ряд был не виден
|
assert out["age_days_max"] == 72 # ДО ПРАВКИ: 0 — найденный ряд был не виден
|
||||||
# прежняя семантика раннего выхода не тронута
|
# прежняя семантика раннего выхода не тронута
|
||||||
|
|
@ -492,9 +516,7 @@ class _MonDBDriverFault(_MonDB):
|
||||||
|
|
||||||
def execute(self, stmt: Any, params: dict[str, Any] | None = None) -> _Result:
|
def execute(self, stmt: Any, params: dict[str, Any] | None = None) -> _Result:
|
||||||
if self._deactivated:
|
if self._deactivated:
|
||||||
raise PendingRollbackError(
|
raise PendingRollbackError("Can't reconnect until invalid transaction is rolled back")
|
||||||
"Can't reconnect until invalid transaction is rolled back"
|
|
||||||
)
|
|
||||||
params = params or {}
|
params = params or {}
|
||||||
if params.get("city") == self._broken:
|
if params.get("city") == self._broken:
|
||||||
self._deactivated = True
|
self._deactivated = True
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue