d8723582c9
Решение Виктора: ИИ убран из проекта — карточки детей (ПД) не должны покиать контур. Анализ полностью локальный и детерминированный. - services/rules_analysis.py: правила вместо Claude (бывший analyze_with_fallback + rule_based_urgency из closed_cases): urgency по возрасту/профилям, зоны через geo+scoring, behavioral_prediction/immediate_actions/key_locations — справочники. - search_engine: анализатор инъекцией (CaseAnalyzer, Protocol), импорт claude_service удалён; рантайм — rules_analyze. - УДАЛЕНЫ: services/claude_service.py, backend/services/claude_service.py (устаревшая копия), test_claude_service.py; ключевые правила-кейсы перенесены в test_rules_analysis.py (+тест отсутствия LLM-упоминаний). - docker-compose/.env.example: ANTHROPIC_API_KEY удалён. - Фронт: бейдж 'ИИ-анализ' -> 'Локальный расчёт'. - Доки: DEPLOY/SERVICES_README/ANALYZE_API переписаны, FIXES_REPORT помечен историческим, vector_tasks B20: zones/probability — из движка. pytest: 187 passed (было 214; -27 удалённых LLM-тестов, +тест rules). Контракт /analyze не изменён (фронт совместим).
80 lines
3.7 KiB
Python
80 lines
3.7 KiB
Python
"""Тесты детерминированного rules-анализа (services/rules_analysis.py).
|
||
|
||
Ключевые кейсы перенесены из test_claude_service.py (удалён вместе с LLM);
|
||
плюс проверка отсутствия внешних вызовов.
|
||
"""
|
||
from __future__ import annotations
|
||
|
||
from services.rules_analysis import (
|
||
AnalysisResult,
|
||
PrimaryZone,
|
||
rule_based_urgency,
|
||
rules_analyze,
|
||
)
|
||
|
||
|
||
class TestUrgency:
|
||
async def test_young_child_critical(self):
|
||
# _age_urgency: возраст ≤4 — критическая вне зависимости от elapsed
|
||
r = await rules_analyze({'age': 3})
|
||
assert r.urgency == 'критическая'
|
||
|
||
def test_ras_critical(self):
|
||
assert rule_based_urgency(1.0, 8, ['РАС']) == 'высокая' # возраст 8 не даёт +2
|
||
|
||
def test_long_search_raises(self):
|
||
assert rule_based_urgency(30.0, 15, []) == 'высокая'
|
||
assert rule_based_urgency(30.0, 5, ['РАС']) == 'критическая'
|
||
|
||
|
||
class TestFallbackRules:
|
||
async def test_young_child(self):
|
||
result = await rules_analyze({'age': 3, 'gender': 'мужской', 'terrain': 'лес'})
|
||
assert isinstance(result, AnalysisResult)
|
||
assert result.urgency == 'критическая'
|
||
assert result.fallback_used is True
|
||
assert len(result.primary_zones) >= 2
|
||
assert 'водоёмы' in result.key_locations
|
||
|
||
async def test_ras_profile(self):
|
||
result = await rules_analyze({'age': 8, 'profiles': ['РАС']})
|
||
assert result.urgency == 'критическая'
|
||
assert 'РАС' in result.behavioral_prediction or 'водоём' in result.behavioral_prediction
|
||
assert any('водоём' in a.lower() for a in result.immediate_actions)
|
||
|
||
async def test_bicycle_profile(self):
|
||
result = await rules_analyze({'age': 12, 'profiles': ['велосипед']})
|
||
assert result.urgency == 'высокая'
|
||
assert any('10-15 км' in a or 'камер' in a for a in result.immediate_actions)
|
||
|
||
async def test_teenager(self):
|
||
result = await rules_analyze({'age': 15, 'terrain': 'лес'})
|
||
assert result.urgency in ['средняя', 'высокая']
|
||
assert 'дороги' in result.key_locations or 'населённые пункты' in result.key_locations
|
||
|
||
async def test_behavioral_predictions_by_age(self):
|
||
r4 = await rules_analyze({'age': 4, 'profiles': []})
|
||
r9 = await rules_analyze({'age': 9, 'profiles': []})
|
||
r16 = await rules_analyze({'age': 16, 'profiles': []})
|
||
assert 'близко к точке потери' in r4.behavioral_prediction
|
||
assert 'тропам или дорогам' in r9.behavioral_prediction
|
||
assert 'населённым пунктам' in r16.behavioral_prediction
|
||
|
||
|
||
class TestNoLLM:
|
||
def test_no_anthropic_in_services(self):
|
||
"""В services/ не осталось ссылок на LLM-провайдеров."""
|
||
import pathlib
|
||
for p in pathlib.Path('services').glob('*.py'):
|
||
src = p.read_text(encoding='utf-8')
|
||
for word in ('anthropic', 'ANTHROPIC', 'api.anthropic.com', 'openai', 'OpenAI'):
|
||
assert word not in src, f'{p.name} contains {word}'
|
||
|
||
def test_no_llm_keys_in_compose(self):
|
||
src = open('docker-compose.yml', encoding='utf-8').read()
|
||
assert 'ANTHROPIC' not in src and 'OPENAI' not in src
|
||
|
||
async def test_result_is_deterministic(self):
|
||
a = await rules_analyze({'age': 10, 'profiles': []})
|
||
b = await rules_analyze({'age': 10, 'profiles': []})
|
||
assert a.model_dump() == b.model_dump() |