Commit Graph

29 Commits

Author SHA1 Message Date
Sadmin 6e9dfe8a6f UI: справочники типа места находки / кто нашёл / направление — единые селекты
- constants.js: LOCATION_TYPES (дорога/тропа/лес/дом-стройка/вода-берег/поле/
  не указано), WHO_FOUND_TYPES, FOUND_DIRECTIONS (8 румбов).
- Модал завершения поиска: тип места, кто нашёл, направление — селекты вместо
  свободного ввода (данные пригодны для матобработки архива без нормализации).
- Архив /admin (ClosedCases): «Кто нашёл» → тот же селект; LOCATION_TYPES
  вынесен в общий модуль.
2026-09-09 22:08:25 +03:00
Sadmin 91fe8358ac fix: ClosedCases load — apiJson вместо apiFetch (главный фикс списка архива) 2026-09-09 22:00:50 +03:00
Sadmin 611ca95d3a fix: ClosedCases — apiFetch возвращает Response, а не JSON (архив всегда показывал 0)
- Загрузка списка, создание и удаление переведены на apiJson
  (apiFetch → Response.items === undefined → items=[]).
2026-09-09 21:58:39 +03:00
Sadmin 5a86755e90 Завершение поиска: исход → архив + матмодель + операция completed
- POST /operations/{id}/complete: found_alive/дистанция/направление/кто нашёл/
  длительность → кейс status=closed + фактические данные + прогон детерминированной
  матмодели (model_prediction, accuracy_note в analysis_log) → операция
  status=completed + closed_at. Аудит: operation_complete + case_closed.
- ФИКС list_closed_cases: фильтр по to_summary().analysis_log всегда давал None
  (analysis_log только в to_detail) → список завершённых был пуст; теперь
  фильтр по status='closed' (совместимо с TestDB).
- UI: кнопка «✓ Завершить поиск» на странице анализа (для активных операций),
  модал с исходом, после завершения — плашка «Поиск завершён», в архиве
  (/admin завершённые) появляется запись с прогнозом модели и точностью.
- Тесты (4): полный флоу, повторное завершение 400, аудит, появление в
  /closed-cases. 230 passed.
2026-09-09 21:55:12 +03:00
Sadmin af98ec94e2 E2 UI: журнал аудита — вкладка «Аудит» в админке пользователей
- AuditTab: события (вход/неудачный вход/блокировка/user_create/user_update/
  password_reset/operation_*/contour_send_*), фильтр по типу, пагинация 50,
  retention из security_settings, field-level old→new в раскрываемых деталях.
- Цветовая маркировка: вход/успех зелёным, отказы/ошибки красным,
  сброс пароля жёлтым.
2026-09-09 21:49:06 +03:00
Sadmin 39b2e296d1 E3 UI: админка пользователей — создание, роли, подразделения, блокировка, сброс пароля
- UsersAdmin.jsx (/users): список с фильтрами (подразделение/поиск),
  форма создания (логин/email/временный пароль/ФИО/должность/юнит/роль),
  inline-смена роли и подразделения, отключение/включение, сброс пароля
  (все сессии отзываются), статусы цветом. must_change_password
  подсвечивается пользователю при первом входе.
- Доступ: бэкенд require_permission('manage_users') — РЦУ РЧС и ОУМЧС;
  скоуп координатора фильтрует список по подразделению (кросс-областной
  PATCH отбивается 403 на бэкенде).
- Кнопка «Пользователи» из дашборда.
2026-09-09 21:42:30 +03:00
Sadmin 90213e0491 E4 (B21): отправка зон в КОНТУР по операции (контракт B20)
- contour_operation_id в PATCH /operations/{id} (mapping на операцию КОНТУРа).
- backend/routers/contour.py: POST /{id}/send-to-contour — последний анализ
  (search_models → legacy analysis_log) → SearchZones[] GeoJSON-сектора
  направлений (±22.5°, probability по приоритету) → POST {CONTOUR_API_URL}/
  api/v1/vector/zones?operation_id=... (Bearer CONTOUR_TOKEN). Все шаги
  аудируются (contour_send_ok/failed). GET /{id}/areas-checked — прокси
  проверенных квадратов (inbound к B17).
- env: CONTOUR_API_URL (дефолт CT130:8000), CONTOUR_TOKEN (в secrets).
- case_id-фильтр в GET /operations.
- UI: панель «КОНТУР» на странице анализа: ввод/сохранение UUID операции
  КОНТУРа, кнопка «Отправить зоны в КОНТУР» со статусом результата.
- Тесты E4 (4): build B20-payload (полигон замкнут, probability 0..1,
  operation_id в query, Bearer), недостающий contour_id 400, отказ КОНТУРа
  → 502 + аудит, геометрия сектора.
226 passed, 5 skipped.
2026-09-09 21:39:18 +03:00
Sadmin ab3eebe274 E3 UI: дашборд «Активные поиски» — главная страница после логина
- Dashboard.jsx: счётчики (активных / всего / завершено за 24ч из
  /operations/summary), фильтры по статусу, сетка карточек операций
  (title, статус-бейдж, подразделение, время обновления), клик →
  рабочее пространство (анализ по карточке операции).
- Роут /dashboard, «/» → дашборд (было /desktop).
- CaseForm: после создания карточки автоматически создаётся поисковая
  операция (title «Поиск: имя, N лет») — операция не блокирует анализ.
- VCL-density, палитра ЦОУ (#ff4d00 акценты, статусы цветом).
2026-09-09 21:33:01 +03:00
Sadmin 5794cb74a9 UI: «перекрыть» → «проверить» (рекомендации МЧС); геоформат «пункт, район, адрес»
1. Рекомендации не предписывают перекрывать водоёмы/ж/д — оператор ПРОВЕРЯЕТ
   их (правки в rules_analysis, scoring_service ×2 копии, water.py + доки).
2. Геокодирование: район нужен (дубли улиц между районами) — подсказка формата
   «пункт, район, адрес» и текст ошибки.

198 passed.
2026-09-09 20:21:18 +03:00
Sadmin b636389ee1 UI карты: приоритет — концентрические круги + подсветка направлений
Дизайн Виктора (классика ПСО): возможная зона поиска — круг радиусом
max_distance от ТНП (ядро 0.5R / средняя 0.75R / внешняя R с градацией
плотности вероятности), а приоритетные направления ПОДСВЕЧИВАЮТСЯ
45°-секторами полного радиуса поверх кругов (топ-1 #ff4d00 op0.30,
топ-2 #ff9f1a 0.22, топ-3 #ffd166 0.18, прочие серые 0.10).
Раньше сектора были «пирогами» разного радиуса по score — зона выглядела
лоскутной. Теперь круги показывают охват, подсветка — приоритет.
2026-09-09 19:58:05 +03:00
Sadmin 1b2c189ace UI: убрана кнопка «Получить текущие координаты»
Мобильная версия выпилена — браузерная геолокация оператора больше не
нужна: ТНП задаётся адресом (кнопка «Найти координаты» → geocode) или
вручную. handleGetLocation и его состояния удалены; .gps-button остался
только у геокод-кнопки.
2026-09-09 19:29:07 +03:00
Sadmin ee1265c259 UI: фикс адресной строки — width:auto перебивает .gps-button/.form-group width:100%
.gps-button { width:100% } в flex-строке съедал всю ширину, input сжимался
до огрызка. Inline width:auto/flexShrink:0 у кнопки и width:auto/minWidth:0
у инпута.
2026-09-09 19:25:47 +03:00
Sadmin 333b827ba7 UI: высота поля адреса = высоте кнопки геокодирования (44px) 2026-09-09 19:23:26 +03:00
Sadmin fa466fc5ed UI: один адрес на шаге с координатами + рабочее геокодирование + авто«прошло времени»
- Step3Circumstances: удалено дублирующее «Последнее известное место»
  (last_known_place); остаётся только tnp_address на шаге координат.
- Step4Environment: кнопка «Найти координаты» теперь РАБОЧАЯ — вызывает
  новый GET /api/v1/geocode (прокси Nominatim, countrycodes=by, короткий
  запрос «пункт, адрес» по граблям geocoding), заполняет lat/lon,
  показывает найденный display_name или ошибку. Без debounce-автоплейса
  (Pitfall 2) — только явный клик.
- Step3Circumstances: «Прошло времени» считается автоматически из
  lost_time (пересчёт каждые 30 с, округление до 0.1 ч); поле остаётся
  редактируемым для корректировки.
- БАГФИКС: форма шлёт lost_time, модель хранит loss_time — алиас в
  _normalize_case_data. Раньше время пропажи молча терялось
  (Pydantic игнорирует неизвестные поля) — elapsed_hours приходилось
  вводить руками.
- last_known_place убран из валидации шага 4.
- backend/routers/geocode.py: Nominatim-прокси с auth (operator/field/admin).

192 passed, JSX валиден.
2026-09-09 19:18:20 +03:00
Sadmin 3d96c1f630 frontend: fix — useEffect не импортирован (eslint no-undef) 2026-09-09 19:05:11 +03:00
Sadmin d4f2cb4f20 frontend: мобильная версия отключена
- Удалён /mobile-роут и MobileFormPage; " /" всегда ведёт на /desktop
  (user-agent/width-детекция выпилена — ЦОУ работает только на десктопе).
- Тема mobile в index.css оставлена (мёртвый CSS, не мешает).
- CaseForm.isMobile не тронут: DesktopSARPage передаёт false явно.
2026-09-09 19:03:19 +03:00
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
Sadmin 0320cad063 fix: GeoJSON MultiPolygon — кольца оборачивать в полигоны ([[ring]])
Баг: для relation с 2+ внешними кольцами coordinates отдавался [ring0, ring1]
(глубина 3) при type=MultiPolygon — невалидный GeoJSON, Leaflet падал
('latlngs[0] undefined') → белый экран страницы анализа на кейсах с
мультиполигонами (РАС-кейс Минск: Камсамольскае возера из 2 колец).
Фронт: toLatLngs различает Polygon/MultiPolygon по глубине coords[0][0][0].
2026-09-09 14:14:44 +03:00
Sadmin 32af8975f9 ui: переработка страницы результатов — логические блоки, та же палитра
Проблема: 10 инфо-карточек в один столбец, карта с зонами — в самом
низу, немедленные действия терялись в середине, формула занимала
пол-экрана раньше вывода.

Новая структура (сверху вниз = порядок чтения штабом):
1. Шапка-панель решения: срочность | радиус (+множитель профилей
   отдельным чипом) | критические действия списком — решение видно
   без прокрутки; summary свёрнут в одну строку.
2. Главный ряд: КАРТА (2/3) + список ЗОН (1/3) рядом — зоны и карта
   теперь одно визуальное целое; geo-чипы (💧/🚂) и предупреждения
   геослоя компактными плашками в шапке карты.
3. Ряд поведение+действия: психотип (тактика 2×2 сеткой), теги
   профилей, unmodeled-пометки; немедленные действия нумерованным
   списком, КРИТИЧНО-пункты акцентной рамкой.
4. Ряд обоснование радиуса: формула моно-шрифтом + коэффициенты
   чипами (НормС/СП/СКД/СУ/СУТ/ВВС/ВП с подсказками) + психотип-
   поправки полос чипами.
5. Низ: справка (обстоятельства/здоровье/среда) сеткой 3 колонки.

Цветовая схема не тронута: var(--accent) #ff4d00, зоны #ff4d00/
#ff9f1a/#ffd166, тёмная тема ЦОУ. Направления зон — кириллицей
(С/СВ/В/...). Все поля бэкенда рендерятся (проверено живым analyze).
2026-09-09 13:53:17 +03:00
Sadmin f20080305d feat: ж/д слой + cant_swim в скоринг + профили вне модели
Определение специфических рекомендаций (матрица профилей §8):

1. Ж/д слой (закрыт мёртвый railway ×2.5 у РАС):
   - /api/v1/water/{case_id} отдаёт railway=rail как LineString
     (без service/industrial/military веток), кэш общий v2;
   - railway_warning «перекрыть/проверить немедленно» по профилям;
   - SearchMap: Polyline слой ж/д (тёмно-красный), счётчики 💧/🚂.

2. cant_swim → профиль не_умеет_плавать (water ×3.0, без изменения
   радиуса, critical_warning «обследовать водоёмы НЕМЕДЛЕННО»):
   - раньше чекбокс влиял только на текст, в скоринге был пробел;
   - derive в analyze._derive_profiles — работает и для closed_cases.

3. unmodeled_profiles: ДЦП/слабое зрение/слух — честная пометка
   «вне поведенческой модели» с пояснением (vector_tasks B12:
   профили без аналога не выдавать за учтённые); блок на фронте
   в карточке здоровья.

Площадь воды: сферический эксцесс, проверен на квадрате 53° (744017 м²
vs 743272 точного). Тесты: 202 passed (новый test_cant_swim_profile).
2026-09-09 13:12:55 +03:00
Sadmin 98e8e58023 feat: водоёмы в зоне поиска на карте (рекомендации профилей → геослой)
- GET /api/v1/water/{case_id}: полигоны водоёмов/болот OSM вокруг ТНП
  (Overpass, natural=water+wetland, кэш 72ч, circuit breaker от geo_service);
  бейдж-предупреждение по профилям случая (РАС/эпилепсия/не умеет плавать)
- geo_service: цепочка зеркал Overpass (maps.mail.ru → overpass-api.de):
  основной сервер имеет AAAA, на хостах без IPv6-маршрута httpx падал
  ConnectError'ом до IPv4-фолбэка; на CT108 фапало стабильно
- SearchMap: слой водоёмов под зонами (синие полигоны, болота пунктиром),
  попапы с площадью/дистанцией от ТНП; AnalysisResult грузит слой,
  показывает предупреждение профиля и счётчик водоёмов
- проверено на CT108: РАС-кейс (Минск) — 43 объекта, витя-кейс
  (Каменец-борисовская обл.) — 7 объектов, включая водохранилище Загацце
2026-09-09 12:48:44 +03:00
Sadmin 3e38aae2be feat: завершённые поиски — ручной ввод + детерминированный прогон модели (без ИИ)
Бэкенд:
- schemas.py: ClosedCaseCreate/Response — форма ~25 полей (субъект, среда,
  ресурсы, исход) для внесения завершённых поисков вручную
- routers/closed_cases.py: CRUD + POST {id}/reanalyze. _run_model() —
  тот же скоринг, что /analyze (WeightedScorer + distance_service +
  психотип), НО БЕЗ claude_service: urgency/behavior/actions по правилам
- _accuracy_note(): прогнозный радиус vs факт (в радиусе / мимо)
- main.py: роутер подключён

Фронтенд:
- pages/ClosedCases.jsx + .css: форма внесения (VCL-density, чипы диагнозов/
  рельефа), блок 'Прогноз vs факт', таблица архива (в радиусе/мимо)
- AdminDashboard.jsx: вкладки Кейсы | Завершённые поиски

Тесты: test_closed_cases.py — 6 passed. Существующие 15 failed
(test_distance_service) не связаны с изменениями — падали до них.
2026-09-09 10:41:33 +03:00
root 3e2692f8a5 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>
2026-08-20 16:36:35 +00:00
root d0f309cdf6 chore(frontend): remove dead components (B4)
Недостижимые от index.js -> App компоненты (23 файла):
- MobileForm.jsx/.tsx/.css (живая мобильная форма = CaseForm)
- BehavioralProfile.tsx/.css
- AnalysisLayout/, MapView/, ResultPanel/ (MapView/ResultPanel
  импортировались только из неиспользуемого AnalysisLayout; живая
  карта = SearchMap.jsx)
- admin/ (CaseDetail, CasesList, HeatmapView, Statistics) - AdminDashboard
  использует inline-UI, не эти компоненты
- CaseForm/Step5Resources.jsx - не подключён в CaseForm.jsx

Сборка проходит. Мёртвый код с его багами (isPsycotypeSelected опечатка,
хардкод URL, NaN-гварды) уходит вместе с файлами.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-20 16:14:25 +00:00
root bef93d159c feat(auth): minimal login/logout + ProtectedRoute (B-LOGOUT)
- api/client.js: apiFetch подставляет Bearer-токен, выставляет
  Content-Type: application/json для не-FormData тел, ловит 401 -> событие
- context/AuthContext: валидация токена через /auth/me, loading-гейт,
  logout(), подписка на 401
- components/ProtectedRoute: guard с ролями и заглушкой на время проверки
- pages/Login: форма входа (OAuth2 form-urlencoded), редирект на from
- App.js: AuthProvider, /login, ProtectedRoute на /sar /desktop
  /analysis /admin (admin -> roles=[admin]) /mobile
- AdminDashboard/CaseForm/AnalysisResult: переход на apiFetch
- AdminDashboard: кнопка "Выйти" в шапке (useAuth().logout)

Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-20 16:04:18 +00:00
root 3a0d2b0c2d B6: SPA navigation after case creation (useNavigate instead of window.location.href)
B7: stop AdminDashboard refetching the case list on selection change

selectedCaseId was both read and written inside the list-loading effect and
listed in its deps, causing a second fetch on first render and after every
filter reset. Auto-select now uses a functional state update, so the effect
no longer depends on selectedCaseId; editForm stays in sync via the existing
selectedCase effect.
2026-07-25 13:04:19 +00:00
root 5a631f6553 A10-A16: datetime-local time parsing, step heading, gps_error state, unified leaflet 1.9.4 icons, drop debug log, dashboard error surfacing, isMobile on resize 2026-07-25 12:02:56 +00:00
root 3548733019 Fix admin import chain and tests 2026-06-06 20:59:23 +00:00
root fcc22063ed Import Vector lab project 2026-06-06 18:31:55 +00:00