Files
vector/backend/tests/test_zone_reason.py
Sadmin 1513bad538 Согласование радиуса: 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↔формула.
2026-09-09 23:25:06 +03:00

90 lines
4.4 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""Тесты словесных пояснений приоритетных направлений (_build_zone_reason)."""
from __future__ import annotations
import sys
from pathlib import Path
REPO_ROOT = Path(__file__).resolve().parents[2]
if str(REPO_ROOT) not in sys.path:
sys.path.insert(0, str(REPO_ROOT))
from services.rules_analysis import ( # noqa: E402
_build_zone_reason,
_direction_phrase,
_psychotype_phrase,
_water_phrase,
)
class TestZoneReason:
def test_reason_mentions_key_factors(self):
zone = {'direction': 'NE', 'distance_km': 2.0, 'forest_pct': 0.68,
'road_density': 0.5, 'water_distance_km': 0.4,
'settlement_distance_km': 1.2, 'score': 78.4}
case = {'profiles': ['не_умеет_плавать'], 'last_seen_direction': 'ne'}
reason = _build_zone_reason(zone, case)
assert 'лесистость умеренная (68%)' in reason
assert '0.4 км — проверить берега' in reason
assert 'не умеет плавать' in reason
assert 'совпадает с направлением' in reason
assert reason.endswith('Оценка 78/100.')
def test_reason_open_field_with_bike(self):
zone = {'direction': 'SE', 'distance_km': 1.0, 'forest_pct': 0.12,
'road_density': 2.6, 'water_distance_km': 6.0,
'settlement_distance_km': None, 'score': 41.0}
reason = _build_zone_reason(zone, {'profiles': ['велосипед']})
assert 'преобладает открытое поле' in reason
assert 'вода далеко' in reason
assert 'велосипед' in reason
def test_reason_direction_neighbor(self):
zone = {'direction': 'NE', 'distance_km': 1.0, 'score': 60.0}
case = {'last_seen_direction': 'N'}
reason = _build_zone_reason(zone, case)
assert 'близко к направлению' in reason
def test_reason_without_factors_falls_back_to_score(self):
reason = _build_zone_reason({'direction': 'N', 'score': 50.0, 'distance_km': 1.0}, {})
assert reason == 'Оценка 50/100 по весам модели'
def test_water_phrase_no_swimmer_far_water_no_warning(self):
# Не умеет плавать, но вода далеко — акцент не добавляется
phrase = _water_phrase(8.0, ['не_умеет_плавать'])
assert phrase is not None and 'не умеет плавать' not in phrase
def test_psychotype_phrase_bands(self):
mods = {'zone_0_500': 1.0, 'zone_500_1500': 1.25,
'zone_1500_2500': 1.0, 'zone_2500plus': 0.8}
up = _psychotype_phrase({'psychotype_modifiers': mods}, 1.0)
assert 'повышает' in up and '0.5-1.5 км' in up
down = _psychotype_phrase({'psychotype_modifiers': mods}, 3.0)
assert 'снижает' in down
def test_reason_is_deterministic(self):
zone = {'direction': 'E', 'distance_km': 1.8, 'forest_pct': 0.5,
'road_density': 1.0, 'water_distance_km': 2.0,
'settlement_distance_km': 3.0, 'score': 55.5}
case = {'profiles': [], 'last_seen_direction': 'E'}
r1 = _build_zone_reason(zone, case)
r2 = _build_zone_reason(dict(zone), dict(case))
assert r1 == r2
assert 'совпадает с направлением' in r1
class TestRadiusConsistency:
def test_summary_radius_matches_max_times_multiplier(self):
"""Радиус в summary = max_distance × психотип-множитель (согласован с хедером)."""
import asyncio
from services.rules_analysis import rules_analyze
case = {
'age': 9, 'gender': 'м', 'season': 'лето', 'elapsed_hours': 4.0,
'profiles': ['гармоничный'], 'psychotype': 'harmonic',
'psychotype_modifiers': {'zone_0_500': 1.0, 'zone_500_1500': 1.0,
'zone_1500_2500': 1.1, 'zone_2500plus': 1.0},
'max_distance_km': 2.4,
'lat': 54.5, 'lon': 28.5,
}
result = asyncio.run(rules_analyze(case))
# Средний психотип-множитель полос (1.0+1.0+1.1+1.0)/4 = 1.025 → 2.4×1.025=2.46→2.5
assert result.search_radius_km == 2.5, result.search_radius_km
assert f'{result.search_radius_km} км' in result.summary