B5: перенести расчёт анализа в бэкенд (единый источник правды)
AnalysisResult.jsx теперь тонкий клиент над POST /api/v1/analyze — локальные
формулы (calculateFullDistance / generateZonesWithPsychotype / getPsychotypeModifiers)
удалены. Бэкенд становится единственным источником правды.
distance_service:
- get_base_speed: перенесены каноничные displacement-скорости фронта (0.3–2.5)
вместо расходящихся значений бэкенда (1.0–5.5) — согласовано с априорами ПСО
- реализованы заглушки СКД (get_diagnosis_coefficient: эпилепсия 0.6, ЗПР 0.8,
РАС 1.0, СДВГ 1.1, здоров 1.0) и СУ (get_transport_coefficient: bike ×5,
car ×8, иное ×3, none ×1) — критичная безопасность bike×5 теперь в бэкендe
- get_time_of_day_coefficient: сумерки/вечер 0.7 (было 0.5)
analyze.py:
- _derive_profiles: профили (РАС/эпилепсия/СДВГ/ЗПР/велосипед/самокат) формируются
из diagnosis_type + has_transport, закрывая рассинхрон bike/велосипед на границе
- _derive_time_of_day: время суток из loss_time (порт getTimeOfDay фронта)
- психотип детектится ДО claude_analyze, модификаторы прокидываются в score_zone
- _as_case_data: фильтр пустых контейнеров ([],{}) — фикс затирания данных карточки
дефолтами payload (psychotype_answers={} затирал реальные ответы)
- в ответ добавлены coefficients, time_of_day, active_profiles, critical_warnings
scoring_service.score_zone: применяется множитель полосы психотипа по дистанции
(порт getPsychotypeModifiers; таблица уже есть в psychotype_service).
AnalysisResult.jsx: рендер бэкенд-ответа, адаптер primary_zones→SearchMap
(score из приоритета, reasoning←reason), локализация пола m/f→М/Ж, бейдж
fallback/ИИ, блоки активных профилей + критических предупреждений + рекомендаций
по психотипу, исправлены ключи отображения (loss_time/behavior_description/
temperature_c). Поведение сохранено (bike 8yo 2h лес = 5.4 км, как во фронте).
Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -48,11 +48,11 @@ def calculate_max_distance(case_data: dict) -> float:
|
||||
# СП - Terrain coefficient
|
||||
terrain_coef = get_terrain_coefficient(terrain)
|
||||
|
||||
# СКД - Diagnosis coefficient (placeholder, can be expanded)
|
||||
diagnosis_coef = 1.0
|
||||
# СКД - Коэффициент диагноза (влияние на способность двигаться)
|
||||
diagnosis_coef = get_diagnosis_coefficient(case_data.get('diagnosis_type'))
|
||||
|
||||
# СУ - Urgency coefficient (placeholder, can be expanded)
|
||||
urgency_coef = 1.0
|
||||
# СУ - Коэффициент транспорта (велосипед/машина расширяют зону)
|
||||
urgency_coef = get_transport_coefficient(case_data.get('has_transport'))
|
||||
|
||||
# СУТ - Fatigue coefficient (5% reduction per hour)
|
||||
fatigue_coef = max(0.3, 1.0 - (0.05 * elapsed_hours))
|
||||
@@ -90,22 +90,26 @@ def get_base_speed(age: int) -> float:
|
||||
"""
|
||||
# Скорость смещения потерявшегося ребёнка (не скорость ходьбы)
|
||||
# ПСО ЭКСТРЕМУМ: 94% найдены в пределах 3 км
|
||||
# Скорость смещения потерявшегося ребёнка (не скорость ходьбы).
|
||||
# Реальное смещение: испуг, кружение, остановки. Основа — ПСО ЭКСТРЕМУМ
|
||||
# (400 случаев, 94% найдены в пределах 3 км). Значения перенесены из
|
||||
# фронтендового расчёта как каноничные (согласовано с методологией).
|
||||
if age <= 2:
|
||||
return 1.0
|
||||
return 0.3
|
||||
elif age <= 5:
|
||||
return 2.0
|
||||
return 0.7
|
||||
elif age <= 8:
|
||||
return 3.0
|
||||
return 1.2
|
||||
elif age <= 12:
|
||||
return 4.0
|
||||
return 1.5
|
||||
elif age <= 15:
|
||||
return 5.0
|
||||
return 2.0
|
||||
elif age <= 17:
|
||||
return 5.5
|
||||
return 2.5
|
||||
elif age <= 64:
|
||||
return 5.0
|
||||
return 2.5
|
||||
else: # 65+
|
||||
return 3.0
|
||||
return 1.5
|
||||
|
||||
|
||||
def get_terrain_coefficient(terrain: str) -> float:
|
||||
@@ -147,6 +151,56 @@ def get_terrain_coefficient(terrain: str) -> float:
|
||||
return 0.5
|
||||
|
||||
|
||||
def get_diagnosis_coefficient(diagnosis_types) -> float:
|
||||
"""
|
||||
Коэффициент диагноза (СКД) — влияние на физическую способность двигаться.
|
||||
Порт фронтендовой логики (§6 контекста ВЕКТОР).
|
||||
|
||||
Args:
|
||||
diagnosis_types: список диагнозов (diagnosis_type) или строка/None
|
||||
|
||||
Returns:
|
||||
Коэффициент: эпилепсия 0.6, ЗПР 0.8, РАС 1.0, СДВГ 1.1, иной 0.9, нет 1.0
|
||||
"""
|
||||
if not diagnosis_types:
|
||||
return 1.0
|
||||
if isinstance(diagnosis_types, str):
|
||||
diagnosis_types = [diagnosis_types]
|
||||
diags = [str(d).lower() for d in diagnosis_types if d and str(d).lower() not in ('нет', 'none', 'здоров', 'здоровый')]
|
||||
if not diags:
|
||||
return 1.0
|
||||
if any('эпилепси' in d for d in diags):
|
||||
return 0.6
|
||||
if any('зпр' in d for d in diags):
|
||||
return 0.8
|
||||
if any(('рас' in d) or ('аутизм' in d) for d in diags):
|
||||
return 1.0
|
||||
if any('сдвг' in d for d in diags):
|
||||
return 1.1
|
||||
return 0.9
|
||||
|
||||
|
||||
def get_transport_coefficient(has_transport) -> float:
|
||||
"""
|
||||
Коэффициент транспорта (СУ) — расширяет зону поиска.
|
||||
Порт фронтендовой логики. bike ×5 — критично: «расширить зону до 10-15 км».
|
||||
|
||||
Args:
|
||||
has_transport: 'bike' | 'scooter' | 'car' | 'other' | 'none' | None
|
||||
|
||||
Returns:
|
||||
Коэффициент: bike 5.0, car 8.0, иное 3.0, none/пусто 1.0
|
||||
"""
|
||||
if not has_transport or str(has_transport).lower() == 'none':
|
||||
return 1.0
|
||||
t = str(has_transport).lower()
|
||||
if t == 'bike':
|
||||
return 5.0
|
||||
if t == 'car':
|
||||
return 8.0
|
||||
return 3.0
|
||||
|
||||
|
||||
def get_time_of_day_coefficient(time_of_day: str) -> float:
|
||||
"""
|
||||
Get time of day movement coefficient (ВВС).
|
||||
@@ -164,7 +218,7 @@ def get_time_of_day_coefficient(time_of_day: str) -> float:
|
||||
if 'ночь' in time_lower:
|
||||
return 0.5
|
||||
elif 'сумерки' in time_lower or 'вечер' in time_lower:
|
||||
return 0.5
|
||||
return 0.7
|
||||
else: # день
|
||||
return 1.0
|
||||
|
||||
|
||||
@@ -322,6 +322,23 @@ class WeightedScorer:
|
||||
distance_factor = max(0.3, min(1.0, distance_factor))
|
||||
|
||||
score *= distance_factor
|
||||
|
||||
# Психотип: множитель полосы дистанции (§6 контекста) — порт
|
||||
# фронтендовой getPsychotypeModifiers. Таблицу формирует бэкенд
|
||||
# (psychotype_service.get_psychotype_modifiers) и прокидывает через
|
||||
# case['psychotype_modifiers']. Без психотипа — множитель 1.0.
|
||||
mods = case.get('psychotype_modifiers')
|
||||
if mods:
|
||||
zdist = zone.get('distance_km', 1.0)
|
||||
if zdist < 0.5:
|
||||
band = mods.get('zone_0_500', 1.0)
|
||||
elif zdist < 1.5:
|
||||
band = mods.get('zone_500_1500', 1.0)
|
||||
elif zdist < 2.5:
|
||||
band = mods.get('zone_1500_2500', 1.0)
|
||||
else:
|
||||
band = mods.get('zone_2500plus', 1.0)
|
||||
score *= band
|
||||
|
||||
# Конвертируем в шкалу 0-100
|
||||
return round(score * 100, 2)
|
||||
|
||||
Reference in New Issue
Block a user