B17/E5: ингестия полевых данных КОНТУРа
- POST /operations/{id}/field-data/sync: тянет GET /api/v1/vector/areas-checked
→ идемпотентный upsert в areas_checked (dedupe по contour_area_id:
geom/checked_at/result/coverage_pct/team_label, source=contour)
- GET /operations/{id}/areas-checked: теперь из локальной БД после sync
(source=db), фолбэк на живой прокси КОНТУРа если пусто (source=contour/empty)
- миграция 011_b17_field_data: areas_checked +contour_area_id/team_label/source
(+check ck_areas_checked_source)
- аудит field_data_sync; лог vector.contour field_sync_*
- фронт: кнопка «⟳ Синхронизировать полевые данные» в КОНТУР-панели,
слой проверенных квадратов на карте (зелёный=clear ≥99.5%, жёлтый=partial)
- тесты: +9 (267 passed) — sync/идемпотентность/502/аудит/локальное чтение
This commit is contained in:
@@ -699,7 +699,7 @@
|
||||
color: #e6edf3;
|
||||
font-size: 13px;
|
||||
}
|
||||
.contour-save, .contour-send {
|
||||
.contour-save, .contour-send, .contour-sync {
|
||||
padding: 8px 12px;
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
@@ -711,6 +711,8 @@
|
||||
.contour-save { background: #21262d; color: #e6edf3; }
|
||||
.contour-send { background: #238636; }
|
||||
.contour-send:disabled { background: #21262d; color: #8b949e; cursor: not-allowed; }
|
||||
.contour-sync { background: #1f6feb; }
|
||||
.contour-sync:disabled { background: #21262d; color: #8b949e; cursor: not-allowed; }
|
||||
.contour-ok { font-size: 12px; color: #3fb950; }
|
||||
.contour-err { font-size: 12px; color: #f85149; }
|
||||
|
||||
|
||||
@@ -40,6 +40,8 @@ const AnalysisResult = () => {
|
||||
const [operation, setOperation] = useState(null);
|
||||
const [contourId, setContourId] = useState('');
|
||||
const [contourStatus, setContourStatus] = useState(null); // {ok, message}
|
||||
const [fieldAreas, setFieldAreas] = useState(null); // B17: {source, items}
|
||||
const [syncing, setSyncing] = useState(false);
|
||||
const [showComplete, setShowComplete] = useState(false);
|
||||
const [completeForm, setCompleteForm] = useState({
|
||||
found_alive: 'true', found_distance_km: '', found_direction: '',
|
||||
@@ -89,6 +91,12 @@ const AnalysisResult = () => {
|
||||
/* водоёмы — опциональный слой, не блокируют анализ */
|
||||
}
|
||||
|
||||
// B17: полевые данные (если есть contour_operation_id)
|
||||
try {
|
||||
const fieldResp = await apiFetch(`/api/v1/operations/${caseId}/areas-checked`);
|
||||
if (fieldResp.ok) setFieldAreas(await fieldResp.json());
|
||||
} catch { /* слой опциональный */ }
|
||||
|
||||
} catch (err) {
|
||||
console.error("Error loading case:", err);
|
||||
setError(err.message);
|
||||
@@ -250,6 +258,7 @@ const AnalysisResult = () => {
|
||||
tnpLat={parseFloat(tnpLat)}
|
||||
tnpLon={parseFloat(tnpLon)}
|
||||
waterFeatures={waterData?.features || []}
|
||||
fieldAreas={fieldAreas?.items || []}
|
||||
/>
|
||||
) : (
|
||||
<div className="map-empty">ТНП не задана — карта недоступна</div>
|
||||
@@ -290,6 +299,25 @@ const AnalysisResult = () => {
|
||||
}}
|
||||
>Отправить зоны в КОНТУР</button>
|
||||
</div>
|
||||
<div className="contour-row">
|
||||
<button
|
||||
className="contour-sync"
|
||||
disabled={!contourId || syncing}
|
||||
onClick={async () => {
|
||||
setSyncing(true);
|
||||
const r = await apiFetch(`/api/v1/operations/${operation.id}/field-data/sync`, { method: 'POST' });
|
||||
const body = await r.json().catch(() => ({}));
|
||||
setSyncing(false);
|
||||
if (r.ok) {
|
||||
setContourStatus({ ok: true, message: `Полевые данные: квадратов ${body.synced_areas} (новых ${body.new_areas}, обновлено ${body.updated_areas})` });
|
||||
const fr = await apiFetch(`/api/v1/operations/${operation.id}/areas-checked`);
|
||||
if (fr.ok) setFieldAreas(await fr.json());
|
||||
} else {
|
||||
setContourStatus({ ok: false, message: body.detail || 'Ошибка синхронизации' });
|
||||
}
|
||||
}}
|
||||
>{syncing ? 'Синхронизация…' : '⟳ Синхронизировать полевые данные'}</button>
|
||||
</div>
|
||||
{contourStatus && (
|
||||
<div className={contourStatus.ok ? 'contour-ok' : 'contour-err'}>
|
||||
{contourStatus.message}
|
||||
|
||||
Reference in New Issue
Block a user