From 2aa45e893287f35ae7e9bf3abe17795d67781164 Mon Sep 17 00:00:00 2001 From: viktot14-ai Date: Wed, 9 Sep 2026 19:51:05 +0300 Subject: [PATCH] =?UTF-8?q?B1:=20=D0=A1=D0=9F(=D0=BB=D0=B5=D1=81)=3D1.0=20?= =?UTF-8?q?=E2=80=94=20=D0=9D=D0=BE=D1=80=D0=BC=D0=A1=20=D1=82=D1=80=D0=B0?= =?UTF-8?q?=D0=BA=D1=82=D1=83=D0=B5=D1=82=D1=81=D1=8F=20=D0=BA=D0=B0=D0=BA?= =?UTF-8?q?=20=D1=81=D0=BA=D0=BE=D1=80=D0=BE=D1=81=D1=82=D1=8C=20=D1=81?= =?UTF-8?q?=D0=BC=D0=B5=D1=89=D0=B5=D0=BD=D0=B8=D1=8F=20=D0=BF=D0=BE=20?= =?UTF-8?q?=D1=81=D1=80=D0=B5=D0=B4=D0=BD=D0=B5=D0=BC=D1=83=20=D0=BB=D0=B5?= =?UTF-8?q?=D1=81=D1=83?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Решение Виктора: СП(лес) 0.5→1.0. Прежняя таблица учитывала лес дважды (в НормС и в СП) — подросток 14 лет за 3 часа получал 2.55 км вместо реалистичных 5.1. Согласуется с априором ПСО (94% находок в 3 км): радиусы стали верхне-перцентилями, не медианой. - terrain_map пересчитан относительно среднего леса: лес/простой лес/поле/ луг/город = 1.0; сложный/густой лес 0.5; болото 0.4; горы/овраг 0.6; дорога/тропа 0.8; дефолт 1.0. - Контрольные точки тестов пересчитаны (6 шт: 1.92→3.84, 0.33→0.665, 2.43→2.7, 5.4→10.8, 13.5→15.0, stats 0.5→1.0) + операционный тест подростка 5.1 км. - settlement_score пока НЕ тронут — на обдумывание (Виктор). 193 passed. --- backend/tests/test_distance_service.py | 47 +++++++++++++------------- backend/tests/test_search_engine.py | 20 ++++++++--- services/distance_service.py | 28 ++++++++------- 3 files changed, 56 insertions(+), 39 deletions(-) diff --git a/backend/tests/test_distance_service.py b/backend/tests/test_distance_service.py index 518168c..a184162 100644 --- a/backend/tests/test_distance_service.py +++ b/backend/tests/test_distance_service.py @@ -22,14 +22,14 @@ class TestCalculateMaxDistance: Ожидания соответствуют каноничным displacement-скоростям B5 (3e2692f): get_base_speed 0.3–2.5 км/ч по априорам ПСО «Экстремум» (94% в 3 км). - Контрольная точка B5: bike 8yo 2h лес = 5.4 км. + Контрольная точка B1 2026-09-09: bike 8yo 2h лес = 10.8 км (СП(лес)=1.0). """ def test_boy_10_years_4_hours_forest_rain(self): """ Мальчик 10 лет, 4 часа, лес, дождь. Пересчитано под канон B5: НормС(10)=1.5 - Distance = 4 * 1.5 * 0.5 * 0.8 * 1.0 * 0.8 = 1.92 km + B1 2026-09-09: СП(лес)=1.0 (было 0.5): 4 * 1.5 * 1.0 * 0.8 * 1.0 * 0.8 = 3.84 km """ case_data = { 'age': 10, @@ -41,7 +41,7 @@ class TestCalculateMaxDistance: distance = calculate_max_distance(case_data) - assert distance == pytest.approx(1.92, rel=0.01) + assert distance == pytest.approx(3.84, rel=0.01) def test_young_child_short_time(self): """Test for young child (3 years) with short elapsed time.""" @@ -55,8 +55,8 @@ class TestCalculateMaxDistance: distance = calculate_max_distance(case_data) - # 1 * 0.7 * 0.5 * 0.95 * 1.0 * 1.0 = 0.33 km - assert distance == pytest.approx(0.33, rel=0.01) + # B1 2026-09-09 СП(лес)=1.0: 1 * 0.7 * 1.0 * 0.95 * 1.0 * 1.0 = 0.665 km + assert distance == pytest.approx(0.665, rel=0.01) def test_teenager_long_time_road(self): """Test for teenager on road with longer elapsed time.""" @@ -101,11 +101,11 @@ class TestCalculateMaxDistance: distance = calculate_max_distance(case_data) - # 2 * 2.5 * 0.9 * 0.9 * 1.0 * 0.6 = 2.43 km - assert distance == pytest.approx(2.43, rel=0.01) + # B1 2026-09-09 СП(поле)=1.0: 2 * 2.5 * 1.0 * 0.9 * 1.0 * 0.6 = 2.7 km + assert distance == pytest.approx(2.7, rel=0.01) def test_bike_8yo_canonical_b5(self): - """Контрольная точка B5: bike 8yo 2h лес = 5.4 км (СУ bike ×5).""" + """Контрольная точка B1 2026-09-09: bike 8yo 2h лес = 10.8 км (СП(лес)=1.0, СУ bike ×5).""" case_data = { 'age': 8, 'elapsed_hours': 2.0, @@ -118,7 +118,7 @@ class TestCalculateMaxDistance: distance = calculate_max_distance(case_data) - assert distance == pytest.approx(5.4, rel=0.01) + assert distance == pytest.approx(10.8, rel=0.01) class TestBaseSpeed: @@ -157,7 +157,8 @@ class TestBaseSpeed: class TestTerrainCoefficient: - """Test СП (terrain coefficient).""" + """Test СП (terrain coefficient). Семантика: СП=1.0 — средний лес + (НормС — скорость смещения по среднему лесу; B1-ревью 2026-09-09).""" def test_road_terrain(self): assert get_terrain_coefficient('дорога') == 0.8 @@ -165,26 +166,26 @@ class TestTerrainCoefficient: assert get_terrain_coefficient('тропа') == 0.8 def test_forest_terrain(self): - assert get_terrain_coefficient('лес') == 0.5 - assert get_terrain_coefficient('простой лес') == 0.5 - assert get_terrain_coefficient('сложный лес') == 0.25 - assert get_terrain_coefficient('густой лес') == 0.25 + assert get_terrain_coefficient('лес') == 1.0 + assert get_terrain_coefficient('простой лес') == 1.0 + assert get_terrain_coefficient('сложный лес') == 0.5 + assert get_terrain_coefficient('густой лес') == 0.5 def test_open_terrain(self): - assert get_terrain_coefficient('поле') == 0.9 - assert get_terrain_coefficient('луг') == 0.9 + assert get_terrain_coefficient('поле') == 1.0 + assert get_terrain_coefficient('луг') == 1.0 def test_difficult_terrain(self): - assert get_terrain_coefficient('болото') == 0.2 - assert get_terrain_coefficient('горы') == 0.3 - assert get_terrain_coefficient('овраг') == 0.3 + assert get_terrain_coefficient('болото') == 0.4 + assert get_terrain_coefficient('горы') == 0.6 + assert get_terrain_coefficient('овраг') == 0.6 def test_urban_terrain(self): assert get_terrain_coefficient('город') == 1.0 assert get_terrain_coefficient('населённый пункт') == 1.0 def test_unknown_terrain(self): - assert get_terrain_coefficient('неизвестно') == 0.5 + assert get_terrain_coefficient('неизвестно') == 1.0 class TestTimeOfDayCoefficient: @@ -310,7 +311,7 @@ class TestDistanceStatistics: ) assert stats['base_speed_kmh'] == 1.5 - assert stats['terrain_coefficient'] == 0.5 + assert stats['terrain_coefficient'] == 1.0 assert stats['max_distance_km'] > 0 assert 0 <= stats['zone_probability'] <= 1.0 @@ -352,5 +353,5 @@ class TestFatigueCoefficient: distance = calculate_max_distance(case_data) - # 20 * 2.5 * 0.9 * 0.3 * 1.0 * 1.0 = 13.5 - assert distance == pytest.approx(13.5, rel=0.01) + # B1 2026-09-09 СП(поле)=1.0: 20 * 2.5 * 1.0 * 0.3 * 1.0 * 1.0 = 15.0 + assert distance == pytest.approx(15.0, rel=0.01) diff --git a/backend/tests/test_search_engine.py b/backend/tests/test_search_engine.py index 2996b3b..63fd21e 100644 --- a/backend/tests/test_search_engine.py +++ b/backend/tests/test_search_engine.py @@ -53,15 +53,27 @@ def _base_input(**overrides: Any) -> SearchInput: async def test_build_search_model_bike_8yo_matches_old_contract(): - """Регресс из B14: bike 8yo 2h лес день → max_distance 5.4 км - (Time 2 × НормС 1.2 × СП 0.5 × СУТ 0.9 × СУ-bike 5.0).""" + """bike 8yo 2h лес день → max_distance. + B1-ревью 2026-09-09 (Виктор): СП(лес) 0.5→1.0 (НормС — уже скорость + смещения по среднему лесу; 0.5 учитывал лес дважды). + Было 5.4 (2 × 1.2 × 0.5 × 0.9 × 5.0), стало 10.8 (2 × 1.2 × 1.0 × 0.9 × 5.0).""" model = await build_search_model(_base_input(), analyzer=_fake_analyzer()) - assert model.max_distance_km == pytest.approx(5.4, abs=1e-6) + assert model.max_distance_km == pytest.approx(10.8, abs=1e-6) assert model.coefficients['base_speed'] == 1.2 - assert model.coefficients['terrain'] == 0.5 + assert model.coefficients['terrain'] == 1.0 assert model.coefficients['urgency'] == 5.0 # bike +async def test_teen_3h_forest_realistic_distance(): + """Операционная проверка Виктора: здоровый подросток за 3 часа проходит + заметно больше 2.5 км. При СП(лес)=1.0: 3 × 2.0 × 1.0 × 0.85 = 5.1 км.""" + model = await build_search_model( + _base_input(age=14, has_transport=None, elapsed_hours=3.0), + analyzer=_fake_analyzer(), + ) + assert model.max_distance_km == pytest.approx(5.1, abs=1e-6) + + async def test_profiles_derived_from_diagnosis_and_transport(): analyzer = _fake_analyzer() model = await build_search_model( diff --git a/services/distance_service.py b/services/distance_service.py index 8c01cde..6426de6 100644 --- a/services/distance_service.py +++ b/services/distance_service.py @@ -126,6 +126,10 @@ def get_terrain_coefficient(terrain: str) -> float: """ Get terrain movement coefficient (СП). + Семантика (согласовано с Виктором, B1-ревью 2026-09-09): НормС — скорость + СМЕЩЕНИЯ потерявшегося по СРЕДНЕМУ лесу, поэтому СП=1.0 — средний лес. + Отклонения от среднего леса режут/поднимают скорость относительно неё. + Args: terrain: Terrain type @@ -133,32 +137,32 @@ def get_terrain_coefficient(terrain: str) -> float: Terrain coefficient (0.0 - 1.0) """ if not terrain: - return 0.5 + return 1.0 terrain_lower = terrain.lower() terrain_map = { 'лесная дорога': 0.8, - 'сложный лес': 0.25, - 'густой лес': 0.25, - 'простой лес': 0.5, - 'лес': 0.5, + 'сложный лес': 0.5, + 'густой лес': 0.5, + 'простой лес': 1.0, + 'лес': 1.0, 'дорога': 0.8, 'тропа': 0.8, - 'болото': 0.2, - 'поле': 0.9, - 'луг': 0.9, + 'болото': 0.4, + 'поле': 1.0, + 'луг': 1.0, 'город': 1.0, 'населённый пункт': 1.0, - 'горы': 0.3, - 'овраг': 0.3 + 'горы': 0.6, + 'овраг': 0.6 } for key, value in terrain_map.items(): if key in terrain_lower: return value - # Default for unknown terrain - return 0.5 + # Default for unknown terrain: средний лес (как 'лес') + return 1.0 def get_diagnosis_coefficient(diagnosis_types) -> float: