1. Рекомендации не предписывают перекрывать водоёмы/ж/д — оператор ПРОВЕРЯЕТ
их (правки в rules_analysis, scoring_service ×2 копии, water.py + доки).
2. Геокодирование: район нужен (дубли улиц между районами) — подсказка формата
«пункт, район, адрес» и текст ошибки.
198 passed.
Дизайн Виктора (классика ПСО): возможная зона поиска — круг радиусом
max_distance от ТНП (ядро 0.5R / средняя 0.75R / внешняя R с градацией
плотности вероятности), а приоритетные направления ПОДСВЕЧИВАЮТСЯ
45°-секторами полного радиуса поверх кругов (топ-1 #ff4d00 op0.30,
топ-2 #ff9f1a 0.22, топ-3 #ffd166 0.18, прочие серые 0.10).
Раньше сектора были «пирогами» разного радиуса по score — зона выглядела
лоскутной. Теперь круги показывают охват, подсветка — приоритет.
Мобильная версия выпилена — браузерная геолокация оператора больше не
нужна: ТНП задаётся адресом (кнопка «Найти координаты» → geocode) или
вручную. handleGetLocation и его состояния удалены; .gps-button остался
только у геокод-кнопки.
.gps-button { width:100% } в flex-строке съедал всю ширину, input сжимался
до огрызка. Inline width:auto/flexShrink:0 у кнопки и width:auto/minWidth:0
у инпута.
- 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 валиден.
- Удалён /mobile-роут и MobileFormPage; " /" всегда ведёт на /desktop
(user-agent/width-детекция выпилена — ЦОУ работает только на десктопе).
- Тема mobile в index.css оставлена (мёртвый CSS, не мешает).
- CaseForm.isMobile не тронут: DesktopSARPage передаёт false явно.
Баг: для relation с 2+ внешними кольцами coordinates отдавался [ring0, ring1]
(глубина 3) при type=MultiPolygon — невалидный GeoJSON, Leaflet падал
('latlngs[0] undefined') → белый экран страницы анализа на кейсах с
мультиполигонами (РАС-кейс Минск: Камсамольскае возера из 2 колец).
Фронт: toLatLngs различает Polygon/MultiPolygon по глубине coords[0][0][0].
Проблема: 10 инфо-карточек в один столбец, карта с зонами — в самом
низу, немедленные действия терялись в середине, формула занимала
пол-экрана раньше вывода.
Новая структура (сверху вниз = порядок чтения штабом):
1. Шапка-панель решения: срочность | радиус (+множитель профилей
отдельным чипом) | критические действия списком — решение видно
без прокрутки; summary свёрнут в одну строку.
2. Главный ряд: КАРТА (2/3) + список ЗОН (1/3) рядом — зоны и карта
теперь одно визуальное целое; geo-чипы (💧/🚂) и предупреждения
геослоя компактными плашками в шапке карты.
3. Ряд поведение+действия: психотип (тактика 2×2 сеткой), теги
профилей, unmodeled-пометки; немедленные действия нумерованным
списком, КРИТИЧНО-пункты акцентной рамкой.
4. Ряд обоснование радиуса: формула моно-шрифтом + коэффициенты
чипами (НормС/СП/СКД/СУ/СУТ/ВВС/ВП с подсказками) + психотип-
поправки полос чипами.
5. Низ: справка (обстоятельства/здоровье/среда) сеткой 3 колонки.
Цветовая схема не тронута: var(--accent) #ff4d00, зоны #ff4d00/
#ff9f1a/#ffd166, тёмная тема ЦОУ. Направления зон — кириллицей
(С/СВ/В/...). Все поля бэкенда рендерятся (проверено живым analyze).
Определение специфических рекомендаций (матрица профилей §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).
- 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 объектов, включая водохранилище Загацце
Бэкенд:
- 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) не связаны с изменениями — падали до них.
Недостижимые от 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>
- 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>
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.