fix: forest_pct — доля 0..1 вместо процентов (вес леса падает со 100×)

Закономерность «приоритетная зона вверх-вправо»: score_zone умножает
weight × forest_pct, ожидая ДОЛЮ (тесты скоринга 0.3–0.8), а гео-сервисы
(osm_local и legacy Overpass calculate_forest_coverage) отдавали
ПРОЦЕНТЫ 0..100. Лес получил фактический вес в 100 раз больше задумки —
топ зон систематически занимали чуть более зелёные сектора.

Фикс: osm_local.calculate_forest_coverage возвращают долю 0..1.
Разброс скорингов по Минску: 1.244 → 0.032 (честно однородный город).
Остаточные различия — вода/дороги; дифференциация плотностей город/село —
ревью скоринга (B1), коэффициенты не тронуты.

+регресс-тест 0<=forest_pct<=1. 197 passed.
This commit is contained in:
2026-09-09 19:33:53 +03:00
parent 1b2c189ace
commit 59f97c850e
3 changed files with 13 additions and 4 deletions
+5 -2
View File
@@ -344,9 +344,12 @@ def calculate_forest_coverage(elements: List[Dict], radius_m: int) -> float:
search_area_km2 = math.pi * (radius_m / 1000) ** 2
estimated_forest_km2 = forest_ways * 0.1
coverage_pct = min(100.0, (estimated_forest_km2 / search_area_km2) * 100)
# Контракт: ДОЛЯ 0..1 (scoring_service читает forest_pct как долю —
# weight × forest_pct; раньше отдавались проценты 0..100, что делало
# лес доминирующим фактором со 100× весом).
coverage = min(1.0, estimated_forest_km2 / search_area_km2)
return round(coverage_pct, 1)
return round(coverage, 3)
async def get_zone_features(lat: float, lon: float, direction: str, radius_m: int) -> Dict: