Compare commits
4 Commits
a1de5794e2
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| ef18f8e28f | |||
| cdfbf30232 | |||
| 31757f18b3 | |||
| 05d6b3ce68 |
@@ -334,6 +334,27 @@ SPA на react-scripts (CRA). Роутинг:
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## 11а. Полевые данные КОНТУРа (B17/E5)
|
||||||
|
|
||||||
|
Двусторонний обмен с КОНТУРом по операции:
|
||||||
|
|
||||||
|
- **Outbound** (E4): `POST /operations/{id}/send-to-contour` — зоны расчёта.
|
||||||
|
- **Inbound** (B17): `POST /operations/{id}/field-data/sync` — тянет из КОНТУРа
|
||||||
|
`GET {CONTOUR_API_URL}/api/v1/vector/areas-checked?operation_id=<contour_operation_id>`
|
||||||
|
и делает идемпотентный upsert в `areas_checked` (dedupe по `contour_area_id`,
|
||||||
|
хранит geom GeoJSON / checked_at / result clear|partial / coverage_pct / callsign цепи,
|
||||||
|
`source=contour`). Каждая синхронизация аудируется (`field_data_sync`).
|
||||||
|
- `GET /operations/{id}/areas-checked` — после sync читает локальную БД (`source: db`),
|
||||||
|
до первой синхронизации прозрачно проксирует КОНТУР (`source: contour`).
|
||||||
|
- Фронт: кнопка «⟳ Синхронизировать полевые данные» в КОНТУР-панели страницы анализа;
|
||||||
|
проверенные квадраты рисуются на карте **поверх зон** — зелёный = прочесано (≥99.5%),
|
||||||
|
жёлтый пунктир = частично; в попапе покрытие %, время проверки, callsign.
|
||||||
|
|
||||||
|
Статусы КОНТУРа: `clear` (покрытие ≥99.5%) / `partial`. Нахождение субъекта на стороне
|
||||||
|
КОНТУРа статусом не передаётся — находка фиксируется завершением поиска в ВЕКТОРе.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## 12. Интеграция с КОНТУР (отправка зон)
|
## 12. Интеграция с КОНТУР (отправка зон)
|
||||||
|
|
||||||
КОНТУР — система полевой координации (ATAK + Meshtastic). ВЕКТОР отдаёт приоритетные зоны (outbound):
|
КОНТУР — система полевой координации (ATAK + Meshtastic). ВЕКТОР отдаёт приоритетные зоны (outbound):
|
||||||
@@ -403,6 +424,7 @@ SPA на react-scripts (CRA). Роутинг:
|
|||||||
- Карта: концентрические круги + подсветка направлений (дизайн согласованный)
|
- Карта: концентрические круги + подсветка направлений (дизайн согласованный)
|
||||||
- РБАК: иерархия РЦУ РЧС → ОУМЧС → Г(Р)ОЧС (142 юнита), 4 роли / 9 прав, блокировки, сессии
|
- РБАК: иерархия РЦУ РЧС → ОУМЧС → Г(Р)ОЧС (142 юнита), 4 роли / 9 прав, блокировки, сессии
|
||||||
- Мультипоиск: операции, статусная машина, дашборд «Активные поиски»
|
- Мультипоиск: операции, статусная машина, дашборд «Активные поиски»
|
||||||
|
- B17/E5: ингестия полевых данных КОНТУРа (проверенные квадраты на карте операции)
|
||||||
- Завершение поиска с внесением исхода, архив завершённых с «прогноз vs факт»
|
- Завершение поиска с внесением исхода, архив завершённых с «прогноз vs факт»
|
||||||
- Аудит 90 дней с field-level деталями
|
- Аудит 90 дней с field-level деталями
|
||||||
- Прикладное логирование `vector.*`: события анализа, входов, КОНТУРа, аудита (README §13а)
|
- Прикладное логирование `vector.*`: события анализа, входов, КОНТУРа, аудита (README §13а)
|
||||||
@@ -413,8 +435,8 @@ SPA на react-scripts (CRA). Роутинг:
|
|||||||
|
|
||||||
### В планах
|
### В планах
|
||||||
|
|
||||||
- **E5 (B17)** — приём полевых данных от КОНТУРа: треки групп, проверенные квадраты (areas-checked) на карте операции
|
- **E5/B17 расширение** — треки групп (tracks/live) как field_observations; полевой статус «найден»
|
||||||
- **Живая интеграция с КОНТУР** (CT130) — настройка токена и реальная отправка зон
|
- **Авто-sync полевых данных** — периодический (сейчас кнопка вручную)
|
||||||
- **Локальный tile-сервер** — для полностью offline-фронта (сейчас тайлы OSM с CDN в браузере)
|
- **Локальный tile-сервер** — для полностью offline-фронта (сейчас тайлы OSM с CDN в браузере)
|
||||||
- **Калибровка settlement_score** — вкладка населённых пунктов в скоринге секторов (открытый методический вопрос)
|
- **Калибровка settlement_score** — вкладка населённых пунктов в скоринге секторов (открытый методический вопрос)
|
||||||
- **Калибровка зонального скоринга** по накопленному архиву «прогноз vs факт»
|
- **Калибровка зонального скоринга** по накопленному архиву «прогноз vs факт»
|
||||||
|
|||||||
+3
-1
@@ -34,9 +34,11 @@ services:
|
|||||||
ports:
|
ports:
|
||||||
- "8000:8000"
|
- "8000:8000"
|
||||||
environment:
|
environment:
|
||||||
- DATABASE_URL=${DATABASE_URL:-postgresql://postgres:***@postgres:5432/vector_mchs}
|
- DATABASE_URL=${DATABASE_URL:-postgresql://postgres:postgres@postgres:5432/vector_mchs}
|
||||||
- JWT_SECRET=${JWT_SECRET:-change-me-in-production}
|
- JWT_SECRET=${JWT_SECRET:-change-me-in-production}
|
||||||
- CORS_ORIGINS=${CORS_ORIGINS:-http://localhost:3000}
|
- CORS_ORIGINS=${CORS_ORIGINS:-http://localhost:3000}
|
||||||
|
- CONTOUR_API_URL=${CONTOUR_API_URL:-}
|
||||||
|
- CONTOUR_TOKEN=${CONTOUR_TOKEN:-}
|
||||||
- PYTHONPATH=/app
|
- PYTHONPATH=/app
|
||||||
depends_on:
|
depends_on:
|
||||||
postgres:
|
postgres:
|
||||||
|
|||||||
@@ -215,23 +215,6 @@ const SearchMap = ({ tnpLat, tnpLon, maxDistance, zones, waterFeatures, fieldAre
|
|||||||
</Polyline>
|
</Polyline>
|
||||||
))}
|
))}
|
||||||
|
|
||||||
{/* B17: проверенные квадраты КОНТУРа (поверх гео, под зонами) */}
|
|
||||||
{fieldGeoJSON.map(({ area, positions }, idx) => (
|
|
||||||
<Polygon
|
|
||||||
key={`field-${idx}`}
|
|
||||||
positions={positions}
|
|
||||||
pathOptions={fieldStyle(area)}
|
|
||||||
>
|
|
||||||
<Popup>
|
|
||||||
<strong>Проверенный участок {area.team_id ? `— ${area.team_id}` : ''}</strong><br/>
|
|
||||||
Статус: {area.result === 'clear' ? '✅ прочесано (≥99.5%)' : '🟡 частично'}<br/>
|
|
||||||
{area.coverage_pct != null && <>Покрытие: {Math.round(area.coverage_pct)}%<br/></>}
|
|
||||||
{area.checked_at && <>Проверено: {new Date(area.checked_at).toLocaleString('ru-RU')}<br/></>}
|
|
||||||
{area.synced && <span style={{ opacity: 0.7 }}>данные синхронизированы из КОНТУРа</span>}
|
|
||||||
</Popup>
|
|
||||||
</Polygon>
|
|
||||||
))}
|
|
||||||
|
|
||||||
{/* Базовый слой: концентрические круги возможной зоны поиска */}
|
{/* Базовый слой: концентрические круги возможной зоны поиска */}
|
||||||
{defaultZones.map((zone, idx) => (
|
{defaultZones.map((zone, idx) => (
|
||||||
<Polygon
|
<Polygon
|
||||||
@@ -252,6 +235,23 @@ const SearchMap = ({ tnpLat, tnpLon, maxDistance, zones, waterFeatures, fieldAre
|
|||||||
</Polygon>
|
</Polygon>
|
||||||
))}
|
))}
|
||||||
|
|
||||||
|
{/* B17: проверенные квадраты КОНТУРа — ПОВЕРХ зон (оперативные данные важнее расчётных) */}
|
||||||
|
{fieldGeoJSON.map(({ area, positions }, idx) => (
|
||||||
|
<Polygon
|
||||||
|
key={`field-${idx}`}
|
||||||
|
positions={positions}
|
||||||
|
pathOptions={fieldStyle(area)}
|
||||||
|
>
|
||||||
|
<Popup>
|
||||||
|
<strong>Проверенный участок {area.team_id ? `— ${area.team_id}` : ''}</strong><br/>
|
||||||
|
Статус: {area.result === 'clear' ? '✅ прочесано (≥99.5%)' : '🟡 частично'}<br/>
|
||||||
|
{area.coverage_pct != null && <>Покрытие: {Math.round(area.coverage_pct)}%<br/></>}
|
||||||
|
{area.checked_at && <>Проверено: {new Date(area.checked_at).toLocaleString('ru-RU')}<br/></>}
|
||||||
|
{area.synced && <span style={{ opacity: 0.7 }}>данные синхронизированы из КОНТУРа</span>}
|
||||||
|
</Popup>
|
||||||
|
</Polygon>
|
||||||
|
))}
|
||||||
|
|
||||||
{/* Подсветка приоритетных направлений (поверх кругов) */}
|
{/* Подсветка приоритетных направлений (поверх кругов) */}
|
||||||
{highlightSectors.map((sector, idx) => (
|
{highlightSectors.map((sector, idx) => (
|
||||||
<Polygon
|
<Polygon
|
||||||
|
|||||||
@@ -1074,3 +1074,66 @@ mchs_units); admin (РЦУ РЧС) — без фильтра. Тот же ско
|
|||||||
ретенция 90 дней.
|
ретенция 90 дней.
|
||||||
- Зоны операции уезжают в КОНТУР и видны в штаб-панели CT130:8080 под своим
|
- Зоны операции уезжают в КОНТУР и видны в штаб-панели CT130:8080 под своим
|
||||||
operation_id; вторая операция не смешивается.
|
operation_id; вторая операция не смешивается.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## B24 — Мёртвые веса скоринга: анализ зафиксирован, данные решают (2026-09-25)
|
||||||
|
**Тип:** анализ завершён, РЕШЕНИЕ ЗАФИКСИРОВАНО. НЕ чистить веса «по чутью» — оживлять данными.
|
||||||
|
|
||||||
|
### Диагностика (проверено прогонами, A/B-тест)
|
||||||
|
|
||||||
|
`BASE_WEIGHTS` (services/scoring_service.py, методика §9) содержит 7 факторов.
|
||||||
|
Реально на ранжирование зон влияют только 5:
|
||||||
|
|
||||||
|
| Фактор | Базовый вес | Подставляется в zone_dict? | Реальный вклад |
|
||||||
|
|--------|-------------|---------------------------|----------------|
|
||||||
|
| forest | 0.25 | да (forest_pct из OSM) | 29.4% |
|
||||||
|
| water | 0.20 | да (water_distance_km из OSM) | 23.5% |
|
||||||
|
| roads | 0.18 | да (road_density из OSM) | 21.2% |
|
||||||
|
| settlement | 0.15 | да (settlement_distance_km) | 17.6% |
|
||||||
|
| historical | 0.12 | **НЕТ** — всегда дефолт 0.5 | 0% (константа) |
|
||||||
|
| direction | 0.07 | да (direction_match при last_seen_direction) | 8.2% |
|
||||||
|
| shelter | 0.03 | **НЕТ** — всегда дефолт 0.3 | 0% (константа) |
|
||||||
|
|
||||||
|
Третья разновидность мёртвого — **railway**: вес добавляется динамически
|
||||||
|
(профиль РАС, ×2.5, `apply_profile` → `weights['railway'] = 0.05`), но
|
||||||
|
`railway_distance_km` никто не вычисляет → вклад в score всегда 0.
|
||||||
|
При этом critical_warning РАС требует «немедленно проверить ВСЕ ж/д пути».
|
||||||
|
|
||||||
|
**A/B-прогон подтверждает**: приоритеты 8 секторов с dead-весами и без них
|
||||||
|
попарно идентичны (константа historical 0.5 / shelter 0.3 добавляет всем
|
||||||
|
зонам одинаковую добавку, на разницу оценок не влияет).
|
||||||
|
|
||||||
|
### РЕШЕНИЕ (фиксируем)
|
||||||
|
|
||||||
|
1. **НЕ удалять веса из BASE_WEIGHTS.** Методика §9 согласована с руководством;
|
||||||
|
историческая частота — осмысленный фактор, у него нет ДАННЫХ, а не смысла.
|
||||||
|
Правка весов руками = калибровка «по чутью», запрещена политикой
|
||||||
|
(«коэффициенты меняются только по явному решению руководства»).
|
||||||
|
|
||||||
|
2. **Оживать по мере появления данных:**
|
||||||
|
- **railway_distance_km** — можно сразу: railway=rail уже есть в локальном
|
||||||
|
OSM (planet_osm_line, water-эндпоинт выгружает ж/д объекты). Нужна функция
|
||||||
|
«расстояние до ближайшего ж/д пути в секторе» в osm_local (аналог
|
||||||
|
settlement/water-кандидатов) + подстановка в zones_dict в rules_analysis.
|
||||||
|
Тогда railway-фактор для РАС заработает, как обещает critical_warning.
|
||||||
|
- **shelter_pct** — из локального OSM (доля укрытий в секторе: лес + отдельные
|
||||||
|
объекты natural=scrub/tree/wetland). Требует выбора методики «укрытие» —
|
||||||
|
согласовать с руководством, потом код.
|
||||||
|
- **historical_freq** — ТОЛЬКО после набора архива «прогноз vs факт»
|
||||||
|
(сейчас 2 записи — данных нет, выдумывать нельзя). Когда закрытых поисков
|
||||||
|
станет десятки: посчитать места находок вокруг ТНП по секторам и подавать
|
||||||
|
в скоринг. Это ЗАКРЫВАЕТ калибровку settlement_score из «в планах».
|
||||||
|
|
||||||
|
3. **Порядок работы (предложение):**
|
||||||
|
a) railway из OSM (данные есть, ~1 день) — оживает critical_warning РАС;
|
||||||
|
b) копить архив закрытых кейсов (каждый поиск — точка);
|
||||||
|
c) historical_freq по архиву + калибровка весов по фактам (решение руководства);
|
||||||
|
d) shelter — по согласованной методике.
|
||||||
|
|
||||||
|
4. **Тест-страж:** при любых правках скоринга сверять контрольные точки
|
||||||
|
матрасчёта (README §7) и прогонять A/B (ранжирование до/после) —
|
||||||
|
приоритеты не должны меняться «молча».
|
||||||
|
|
||||||
|
**Не делать:** подстановку «правдоподобных» констант, включение weights без
|
||||||
|
источника данных, изменение BASE_WEIGHTS без решения руководства.
|
||||||
|
|||||||
Reference in New Issue
Block a user