Files
vector/backend/tests/test_rules_analysis.py
Sadmin d8723582c9 Полный отказ от LLM: детерминированный rules-анализ
Решение Виктора: ИИ убран из проекта — карточки детей (ПД) не должны
покиать контур. Анализ полностью локальный и детерминированный.

- 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 не изменён (фронт совместим).
2026-09-09 18:30:44 +03:00

80 lines
3.7 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""Тесты детерминированного 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()