feat: ж/д слой + cant_swim в скоринг + профили вне модели
Определение специфических рекомендаций (матрица профилей §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).
This commit is contained in:
@@ -87,6 +87,26 @@ def _as_case_data(payload: AnalysisRequest) -> dict[str, Any]:
|
||||
}
|
||||
|
||||
|
||||
# Диагнозы, которые форма собирает, но поведенческая модель не покрывает
|
||||
# (vector_tasks.md B12: профили без аналога/данных — честно помечать «вне модели»).
|
||||
UNMODELED_DIAGNOSES: dict[str, str] = {
|
||||
'ДЦП': 'Двигательные нарушения — модель не учитывает; учесть вручную при планировании (доступность местности, темп).',
|
||||
'слабое_зрение': 'Слабое зрение — модель не учитывает; вероятность следования по открытым/звуковым ориентирам выше.',
|
||||
'слабый_слух': 'Слабый слух — модель не учитывает; голосовой поиск менее эффективен, приоритет визуальным сигналам.',
|
||||
}
|
||||
|
||||
|
||||
def _unmodeled_profiles(case_data: dict[str, Any]) -> list[dict[str, str]]:
|
||||
"""Диагнозы вне поведенческой модели (не влияют на скоринг) + пояснение."""
|
||||
out: list[dict[str, str]] = []
|
||||
for diag in (case_data.get('diagnosis_type') or []):
|
||||
d = str(diag).lower()
|
||||
for key, note in UNMODELED_DIAGNOSES.items():
|
||||
if d == key.lower() and not any(o['profile'] == key for o in out):
|
||||
out.append({'profile': key, 'note': note})
|
||||
return out
|
||||
|
||||
|
||||
def _derive_profiles(case_data: dict[str, Any]) -> list[str]:
|
||||
"""
|
||||
Формирует список поведенческих профилей из диагнозов и транспорта, если они
|
||||
@@ -112,6 +132,8 @@ def _derive_profiles(case_data: dict[str, Any]) -> list[str]:
|
||||
derived.append('велосипед')
|
||||
elif transport == 'scooter':
|
||||
derived.append('самокат')
|
||||
if case_data.get('cant_swim'):
|
||||
derived.append('не_умеет_плавать')
|
||||
seen: set[str] = set()
|
||||
out: list[str] = []
|
||||
for p in derived:
|
||||
@@ -213,6 +235,7 @@ async def analyze_case(payload: AnalysisRequest) -> dict[str, Any]:
|
||||
'distance_multiplier': scorer.distance_multiplier,
|
||||
'active_profiles': scorer.get_active_profiles_info(),
|
||||
'critical_warnings': scorer.critical_warnings,
|
||||
'unmodeled_profiles': _unmodeled_profiles(case_data),
|
||||
'urgency': claude_result.urgency,
|
||||
'primary_zones': [zone.model_dump() for zone in claude_result.primary_zones],
|
||||
'search_radius_km': claude_result.search_radius_km,
|
||||
|
||||
Reference in New Issue
Block a user