Files
Sadmin 50a261e05e 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/аудит/локальное чтение
2026-09-25 10:34:53 +03:00

37 lines
1.3 KiB
Python

"""B17/E5: ингестия полевых данных КОНТУРа (колонки areas_checked).
Revises: 010_b23_policy
"""
from __future__ import annotations
import sqlalchemy as sa
from alembic import op
from sqlalchemy.dialects import postgresql as pg
revision = '011_b17_field_data'
down_revision = '010_b23_policy'
branch_labels = None
depends_on = None
def upgrade() -> None:
op.add_column('areas_checked',
sa.Column('contour_area_id', sa.String(64), nullable=True))
op.add_column('areas_checked',
sa.Column('team_label', sa.String(100), nullable=True))
op.add_column('areas_checked',
sa.Column('source', sa.String(20),
server_default='manual', nullable=False))
op.create_index('idx_areas_checked_contour_area',
'areas_checked', ['contour_area_id'])
op.create_check_constraint(
'ck_areas_checked_source', 'areas_checked',
"source IN ('manual','contour')")
def downgrade() -> None:
op.drop_constraint('ck_areas_checked_source', 'areas_checked', type_='check')
op.drop_index('idx_areas_checked_contour_area', table_name='areas_checked')
op.drop_column('areas_checked', 'source')
op.drop_column('areas_checked', 'team_label')
op.drop_column('areas_checked', 'contour_area_id')