Compare commits
No commits in common. "4a728b3a68336dc36ccbc8c2e51aea8c4aac42c8" and "9b72e50d188ee42b62fd9d7e1ca7d4d2886bb468" have entirely different histories.
4a728b3a68
...
9b72e50d18
2 changed files with 13 additions and 125 deletions
|
|
@ -3156,23 +3156,11 @@ def analyze_parcel(
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.warning("district_price_block query failed for %s: %s", cad_num, e)
|
logger.warning("district_price_block query failed for %s: %s", cad_num, e)
|
||||||
|
|
||||||
# B5-6) Risk indicators — flood_zone + noise_score (SF-B5).
|
# B5-6) Risk indicators — flood_zone из cad_risk_zones + noise_score + geology proxy (SF-B5)
|
||||||
#
|
|
||||||
# `geology_risk_label` УБРАН (#2934). Он назывался геологическим риском, а
|
|
||||||
# вычислялся из подтопления и ШУМА: «high» при подтоплении, «medium» при шуме
|
|
||||||
# ≥65 дБ, иначе «low». Геологии в нём не было ни одного бита. При этом
|
|
||||||
# `cad_risk_zones` пуста (0 строк, писателя нет — см. #2934 п.6), поэтому
|
|
||||||
# подтопление приходило только из OSM-прокси «река ближе 200 м», и на
|
|
||||||
# тихом участке без реки поле всегда говорило «low» — зелёный вердикт,
|
|
||||||
# ни разу не подкреплённый проверкой геологии.
|
|
||||||
#
|
|
||||||
# Замена не нужна: соседний блок `geology` честно отдаёт
|
|
||||||
# `data_available: false`, когда данных нет. Потребителей у поля не было —
|
|
||||||
# ни фронт, ни экспортёры, ни §19-allowlist чата его не читали, а в схеме
|
|
||||||
# `risks: dict[str, Any]`, поэтому OpenAPI не меняется.
|
|
||||||
risks_block: dict[str, Any] = {
|
risks_block: dict[str, Any] = {
|
||||||
"flood_zone": False,
|
"flood_zone": False,
|
||||||
"noise_score": round(noise_score, 2),
|
"noise_score": round(noise_score, 2),
|
||||||
|
"geology_risk_label": None,
|
||||||
}
|
}
|
||||||
try:
|
try:
|
||||||
with db.begin_nested():
|
with db.begin_nested():
|
||||||
|
|
@ -3194,13 +3182,20 @@ def analyze_parcel(
|
||||||
.first()
|
.first()
|
||||||
)
|
)
|
||||||
_flood = bool(flood_row and int(flood_row["cnt"]) > 0)
|
_flood = bool(flood_row and int(flood_row["cnt"]) > 0)
|
||||||
# OSM-прокси «река или канал ближе 200 м» (посчитан выше в hydrology).
|
# Geology proxy через hydrology flood_risk_flag (уже посчитан выше)
|
||||||
# На сегодня это ЕДИНСТВЕННЫЙ работающий источник этого признака:
|
|
||||||
# cad_risk_zones пуста, поэтому _flood всегда False (#2934 п.6).
|
|
||||||
_geo_flood = hydrology.get("flood_risk_flag", False) if hydrology else False
|
_geo_flood = hydrology.get("flood_risk_flag", False) if hydrology else False
|
||||||
|
_has_flood = _flood or _geo_flood
|
||||||
|
# geology_risk_label: high если flooding, medium если шум > 65дБ, иначе low
|
||||||
|
if _has_flood:
|
||||||
|
_geo_label: str | None = "high"
|
||||||
|
elif noise_db_max >= 65.0:
|
||||||
|
_geo_label = "medium"
|
||||||
|
else:
|
||||||
|
_geo_label = "low"
|
||||||
risks_block = {
|
risks_block = {
|
||||||
"flood_zone": _flood or _geo_flood,
|
"flood_zone": _has_flood,
|
||||||
"noise_score": round(noise_score, 2),
|
"noise_score": round(noise_score, 2),
|
||||||
|
"geology_risk_label": _geo_label,
|
||||||
}
|
}
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.warning("risks_block query failed for %s: %s", cad_num, e)
|
logger.warning("risks_block query failed for %s: %s", cad_num, e)
|
||||||
|
|
|
||||||
|
|
@ -1,107 +0,0 @@
|
||||||
"""«Геологический риск» не выводится из шума (#2934).
|
|
||||||
|
|
||||||
`risks.geology_risk_label` назывался геологическим риском, а вычислялся так:
|
|
||||||
|
|
||||||
high — если подтопление
|
|
||||||
medium — если шум ≥ 65 дБ
|
|
||||||
low — иначе
|
|
||||||
|
|
||||||
Геологии в нём не было ни одного бита. При этом `cad_risk_zones` пуста (0 строк,
|
|
||||||
писателя нет), поэтому подтопление приходило только из OSM-прокси «река ближе
|
|
||||||
200 м» — и на тихом участке без реки поле ВСЕГДА говорило «low». Зелёный вердикт,
|
|
||||||
ни разу не подкреплённый проверкой геологии.
|
|
||||||
|
|
||||||
Поле убрано, а не переименовано: соседний блок `geology` честно отдаёт
|
|
||||||
`data_available: false`, когда данных нет, и потребителей у метки не было — ни
|
|
||||||
фронт, ни экспортёры, ни §19-allowlist чата её не читали.
|
|
||||||
|
|
||||||
Тесты смотрят на исходник эндпоинта: собрать полный ответ `/analyze` в юните
|
|
||||||
нельзя (нужны БД, OSM, НСПД), а утверждение проверяемо и так — метка не должна
|
|
||||||
существовать, и шум не должен участвовать в риск-блоке.
|
|
||||||
"""
|
|
||||||
|
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
import inspect
|
|
||||||
import re
|
|
||||||
|
|
||||||
|
|
||||||
def _risks_block_source() -> str:
|
|
||||||
"""Кусок analyze-эндпоинта, собирающий risks_block."""
|
|
||||||
from app.api.v1 import parcels
|
|
||||||
|
|
||||||
src = inspect.getsource(parcels)
|
|
||||||
начало = src.index("risks_block: dict[str, Any]")
|
|
||||||
конец = src.index("# 10) Market trend", начало)
|
|
||||||
return src[начало:конец]
|
|
||||||
|
|
||||||
|
|
||||||
def test_geology_risk_label_is_gone() -> None:
|
|
||||||
"""Головной: метки нет в ответе.
|
|
||||||
|
|
||||||
На origin/main она есть и на тихом участке без реки всегда равна 'low'.
|
|
||||||
"""
|
|
||||||
блок = _risks_block_source()
|
|
||||||
assert '"geology_risk_label"' not in блок, f"метка всё ещё выдаётся:\n{блок[:400]}"
|
|
||||||
|
|
||||||
|
|
||||||
def test_noise_no_longer_feeds_the_risk_label() -> None:
|
|
||||||
"""Контроль подмены: шум не должен участвовать в риск-метке.
|
|
||||||
|
|
||||||
Ловит «починку» через переименование — если метку назвать иначе, но по-прежнему
|
|
||||||
считать из noise_db_max, обман останется тем же.
|
|
||||||
"""
|
|
||||||
блок = _risks_block_source()
|
|
||||||
assert "noise_db_max" not in блок, f"шум по-прежнему участвует в риск-блоке:\n{блок[:400]}"
|
|
||||||
assert not re.search(
|
|
||||||
r'"(high|medium|low)"', блок
|
|
||||||
), f"в риск-блоке остались словесные градации риска:\n{блок[:400]}"
|
|
||||||
|
|
||||||
|
|
||||||
def test_noise_score_itself_is_preserved() -> None:
|
|
||||||
"""Контроль от переусердствования: сам noise_score остаётся — он измерен честно."""
|
|
||||||
блок = _risks_block_source()
|
|
||||||
assert '"noise_score"' in блок, "убран и измеренный noise_score — это перебор"
|
|
||||||
|
|
||||||
|
|
||||||
def test_flood_zone_still_reported() -> None:
|
|
||||||
"""Контроль: flood_zone остаётся в ответе (его судьба — отдельный пункт #2934)."""
|
|
||||||
блок = _risks_block_source()
|
|
||||||
assert '"flood_zone"' in блок
|
|
||||||
|
|
||||||
|
|
||||||
def test_no_consumer_reads_the_removed_field() -> None:
|
|
||||||
"""Контроль обоснования: поле действительно никем не читалось.
|
|
||||||
|
|
||||||
Утверждение «потребителей нет» проверяется, а не декларируется — иначе
|
|
||||||
удаление поля из публичного ответа держалось бы на слове.
|
|
||||||
"""
|
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
from app.api.v1 import parcels
|
|
||||||
|
|
||||||
корень = Path(inspect.getsourcefile(parcels)).resolve().parents[4]
|
|
||||||
попадания: list[str] = []
|
|
||||||
for каталог, маски in (
|
|
||||||
(корень / "backend" / "app", ("*.py",)),
|
|
||||||
(корень / "frontend" / "src", ("*.ts", "*.tsx")),
|
|
||||||
):
|
|
||||||
for маска in маски:
|
|
||||||
for f in каталог.rglob(маска):
|
|
||||||
if "__fixtures__" in str(f) or "api-types" in f.name:
|
|
||||||
continue
|
|
||||||
текст = f.read_text(encoding="utf-8", errors="ignore")
|
|
||||||
for n, строка in enumerate(текст.splitlines(), 1):
|
|
||||||
if "geology_risk_label" not in строка:
|
|
||||||
continue
|
|
||||||
# Комментарий, объясняющий удаление, — не потребитель. Проверка
|
|
||||||
# обязана отличать упоминание от использования, иначе краснеет
|
|
||||||
# на собственном объяснении правки.
|
|
||||||
голая = строка.lstrip()
|
|
||||||
комментарий = (
|
|
||||||
голая.startswith("#") or голая.startswith("//") or голая.startswith("*")
|
|
||||||
)
|
|
||||||
if комментарий:
|
|
||||||
continue
|
|
||||||
попадания.append(f"{f.relative_to(корень)}:{n}")
|
|
||||||
assert попадания == [], f"поле где-то ещё ЧИТАЕТСЯ (не в комментарии): {попадания}"
|
|
||||||
Loading…
Add table
Reference in a new issue