Согласование радиуса: summary = max_distance × средний психотип-множитель

- search_engine: max_distance_km передаётся в analyzer (case_data).
- rules_analysis: search_radius = max_distance × _psychotype_radius_multiplier
  (среднее по полосам 0-500/0.5-1.5/1.5-2.5/2.5+). Убран двойной учёт
  age/season: score_zone.distance_multiplier их уже содержит, а max_distance
  их тоже учёл — раньше radius = multiplier×2.0 (хардкод) давал 1.6 км при
  радиусе 2.4 км.
- UI: подсказка у «×0.8» — коэффициент скоринга, не радиуса.
- Тест: согласованность summary↔формула.
This commit is contained in:
2026-09-09 23:25:06 +03:00
parent 1fe3b37d1a
commit 1513bad538
4 changed files with 41 additions and 4 deletions
+18 -2
View File
@@ -178,6 +178,17 @@ def _psychotype_phrase(case_data: dict, distance_km: float) -> str | None:
return None
def _psychotype_radius_multiplier(case_data: dict) -> float:
"""Средний множитель психотипа по полосам дистанции (без двойного учёта
возраст/сезон — они уже в max_distance)."""
mods = case_data.get('psychotype_modifiers')
if not mods:
return 1.0
vals = [mods.get(k, 1.0) for k in ('zone_0_500', 'zone_500_1500',
'zone_1500_2500', 'zone_2500plus')]
return sum(vals) / len(vals)
def _build_zone_reason(zone: dict, case_data: dict) -> str:
"""Словесное пояснение приоритета зоны — по тем же факторам, что считает
WeightedScorer (детерминированно, без выдумок): рельеф, вода, дороги, НП,
@@ -238,7 +249,8 @@ async def rules_analyze(case_data: dict[str, Any]) -> AnalysisResult:
urgency = _age_urgency(age, profiles)
primary_zones: list[PrimaryZone] = []
search_radius_km = 5.0
base_km = float(case_data.get('max_distance_km') or 5.0)
search_radius_km = round(base_km * 1.0, 1)
if case_data.get('lat') and case_data.get('lon'):
try:
@@ -273,7 +285,11 @@ async def rules_analyze(case_data: dict[str, Any]) -> AnalysisResult:
distance=zone['distance_km'],
reason=_build_zone_reason(zone, case_data),
))
search_radius_km = scorer.distance_multiplier * 2.0
# Радиус = max_distance (НормС×коэффициенты) × средний психотип-множитель.
# score_zone.distance_multiplier НЕ используется: он накопил age/season,
# уже учтённые в max_distance — двойной учёт.
base_km = float(case_data.get('max_distance_km') or 2.0)
search_radius_km = round(base_km * _psychotype_radius_multiplier(case_data), 1)
except Exception as e:
logger.warning(f'Geo/scoring service error: {e}')
+1
View File
@@ -269,6 +269,7 @@ async def build_search_model(
case_data['psychotype_modifiers'] = psychotype_modifiers
max_distance_km = calculate_max_distance(case_data)
case_data['max_distance_km'] = max_distance_km
analysis = await analyzer(case_data)
scorer = WeightedScorer()