Определение специфических рекомендаций (матрица профилей §8):
1. Ж/д слой (закрыт мёртвый railway ×2.5 у РАС):
- /api/v1/water/{case_id} отдаёт railway=rail как LineString
(без service/industrial/military веток), кэш общий v2;
- railway_warning «перекрыть/проверить немедленно» по профилям;
- SearchMap: Polyline слой ж/д (тёмно-красный), счётчики 💧/🚂.
2. cant_swim → профиль не_умеет_плавать (water ×3.0, без изменения
радиуса, critical_warning «обследовать водоёмы НЕМЕДЛЕННО»):
- раньше чекбокс влиял только на текст, в скоринге был пробел;
- derive в analyze._derive_profiles — работает и для closed_cases.
3. unmodeled_profiles: ДЦП/слабое зрение/слух — честная пометка
«вне поведенческой модели» с пояснением (vector_tasks B12:
профили без аналога не выдавать за учтённые); блок на фронте
в карточке здоровья.
Площадь воды: сферический эксцесс, проверен на квадрате 53° (744017 м²
vs 743272 точного). Тесты: 202 passed (новый test_cant_swim_profile).
Сверка с методикой ПСР ред. 12.17 (Евдокимов, Лейтес):
- ночь: 0.5 → 0.0 — методичка §12.3.5 прямо даёт «ночь = 0 км»:
без фонаря в лесу ночью пострадавший не перемещается.
Ранее 0.5 ЗАВЫШАЛО радиус ночного поиска.
- сумерки/вечер: 0.7 → 0.5 — «за час до заката скорость 50% от обычной».
B5 переносил 0.7 из старого фронта вопреки методичке.
- get_base_speed: документирована семантика «скорость смещения»
(не асфальтовая НормС из §12.3.2) — оператору не нужно вводить
асфальтовую скорость, она уже свёрнута; СП = поправка конкретной
местности поверх базового среднего леса.
Тесты обновлены под методичку, добавлен тест ночи = 0.
201 passed, 0 failed.
Тесты проверяли старые бэкендовские скорости (1.0–5.5 км/ч), тогда как
B5 перенёс в живую копию services/distance_service.py каноничные
displacement-скорости фронта (0.3–2.5 км/ч, априоры ПСО «Экстремум»,
94% в 3 км) и сумерки 0.7.
Обновлены ожидания (пересчитаны все кейсы):
- TestBaseSpeed: 0.3/0.7/1.2/1.5/2.0/2.5/2.5/1.5 по возрастам
- 5 кейсов calculate_max_distance пересчитаны (5.12→1.92 и т.д.)
- test_twilight: 0.5 → 0.7
- test_statistics_values: base_speed 4.0 → 1.5
- test_fatigue_minimum: 27.0 → 13.5
- Добавлена контрольная точка B5: bike 8yo 2h лес = 5.4 км
Коэффициенты в services/ НЕ тронуты (B1-safe) — только тесты.
201 passed, 0 failed.
Бэкенд:
- schemas.py: ClosedCaseCreate/Response — форма ~25 полей (субъект, среда,
ресурсы, исход) для внесения завершённых поисков вручную
- routers/closed_cases.py: CRUD + POST {id}/reanalyze. _run_model() —
тот же скоринг, что /analyze (WeightedScorer + distance_service +
психотип), НО БЕЗ claude_service: urgency/behavior/actions по правилам
- _accuracy_note(): прогнозный радиус vs факт (в радиусе / мимо)
- main.py: роутер подключён
Фронтенд:
- pages/ClosedCases.jsx + .css: форма внесения (VCL-density, чипы диагнозов/
рельефа), блок 'Прогноз vs факт', таблица архива (в радиусе/мимо)
- AdminDashboard.jsx: вкладки Кейсы | Завершённые поиски
Тесты: test_closed_cases.py — 6 passed. Существующие 15 failed
(test_distance_service) не связаны с изменениями — падали до них.
_extract_json_payload handles a json/JSON/bare fence, raw JSON and JSON
embedded in prose; any unparseable response or contract violation now
degrades to the deterministic scoring_service instead of raising.
Also guards the response envelope itself (content[0].text).
B3: single home for recommendation scoring
services/recommendation_service.py holds the rules; routers/stats.py and
backend/services/stats_service.py both delegate to it. Unified rules are the
union of the two old copies: same weights/threshold, substring matching
(superset of the old exact match), tolerant key aliases, health_flags rule
kept. Endpoint response contract unchanged.
Plus: Overpass circuit breaker and concurrent zone queries in geo_service -
128 sequential calls per analysis no longer each burn a connect timeout when
the host has no outbound network.
Tests: 152 -> 194 passed.