""" Statistical search-priority recommendation. Single home for the recommendation scoring rules (B3). Previously duplicated between `backend/routers/stats.py` (inline, exact-match on terrain/weather, plus a health_flags rule) and `backend/services/stats_service.py` (substring match on tolerant key aliases, no health_flags rule). The unified rules below are the union of the two: identical weights and threshold, substring matching (a superset of the old exact match), tolerant input keys, and the health_flags rule kept. This is NOT the 7-factor zonal scoring of `scoring_service` — it only labels a case high/normal priority. Deliberately free of any database import so both the router and the service layer can use it without side effects. """ from __future__ import annotations from typing import Any # Scoring weights and threshold — unchanged from both previous implementations. WEIGHT_YOUNG_CHILD = 20 WEIGHT_LONG_ELAPSED = 20 WEIGHT_RISKY_TERRAIN = 15 WEIGHT_ADVERSE_WEATHER = 15 WEIGHT_MULTIPLE_HEALTH_FLAGS = 15 HIGH_PRIORITY_THRESHOLD = 40 YOUNG_CHILD_AGE = 12 LONG_ELAPSED_HOURS = 12 MULTIPLE_HEALTH_FLAGS = 2 RISKY_TERRAIN_TOKENS = ('лес', 'болото', 'вода') ADVERSE_WEATHER_TOKENS = ('дождь', 'туман', 'снег', 'ночь') HIGH_PRIORITY_TEXT = 'Высокий приоритет на прочёс и дрон' NORMAL_PRIORITY_TEXT = 'Стандартный приоритет поиска' def score_recommendation( age: int | None = None, elapsed_hours: int | None = None, terrain: str | None = None, weather: str | None = None, health_flags: list[str] | None = None, ) -> dict[str, Any]: """Score a case and return {'score', 'priority', 'recommendation'}.""" score = 0 if age is not None and age < YOUNG_CHILD_AGE: score += WEIGHT_YOUNG_CHILD if elapsed_hours is not None and elapsed_hours >= LONG_ELAPSED_HOURS: score += WEIGHT_LONG_ELAPSED if any(token in str(terrain or '').lower() for token in RISKY_TERRAIN_TOKENS): score += WEIGHT_RISKY_TERRAIN if any(token in str(weather or '').lower() for token in ADVERSE_WEATHER_TOKENS): score += WEIGHT_ADVERSE_WEATHER if len(health_flags or []) >= MULTIPLE_HEALTH_FLAGS: score += WEIGHT_MULTIPLE_HEALTH_FLAGS is_high = score >= HIGH_PRIORITY_THRESHOLD return { 'score': score, 'priority': 'high' if is_high else 'normal', 'recommendation': HIGH_PRIORITY_TEXT if is_high else NORMAL_PRIORITY_TEXT, } def get_statistical_recommendation(case_data: dict[str, Any]) -> dict[str, Any]: """Dict-based entry point, tolerant of the field aliases used across the desktop / mobile / admin payloads.""" return score_recommendation( age=case_data.get('age') if case_data.get('age') is not None else case_data.get('age_years'), elapsed_hours=case_data.get('elapsed_hours'), terrain=case_data.get('terrain') or case_data.get('terrain_primary'), weather=case_data.get('weather') or case_data.get('precipitation'), health_flags=case_data.get('health_flags'), )