# Архитектура ЦА (Analytics Center) Центр аналитики — ядро MapMil: PostgreSQL, FastAPI, Vue admin/карта, distribution API для ПИ. Общий контекст: [docs/architecture-overview.md](../../docs/architecture-overview.md). ПИ отдельно: [DISTRIBUTION.md](DISTRIBUTION.md). --- ## Общими чертами ЦА: - хранит события, jobs, объекты карты, consumers; - отдаёт единственный UI (`ca-frontend`); - ставит задания в Redis для ЦП; - принимает ingest от ЦП; - отдаёт срезы внешним системам через `/api/v1/events`. ```mermaid flowchart TB UI[ca-frontend Vue] API[ca-api FastAPI] DB[(PostgreSQL)] Redis[(Redis)] CP[CP workers] UI --> API API --> DB API -->|enqueue| Redis Redis --> CP CP -->|/internal/*| API ``` Контейнеры: `ca-db`, `ca-api`, `ca-frontend` (+ общий `redis`). --- ## Подробнее ### Структура ```text centers/analytics/ ├── ARCHITECTURE.md ├── DISTRIBUTION.md ├── api/ │ ├── Dockerfile │ ├── requirements.txt │ └── app/ │ ├── main.py # lifespan: migrations, scheduler, seed │ ├── database.py │ ├── models.py │ ├── schemas.py │ ├── deps.py # X-Internal-Token │ ├── seed.py │ ├── storage.py # uploads │ ├── routers/ │ │ ├── objects.py # /api/health, /api/objects, media │ │ ├── map.py # /api/map/* │ │ ├── admin.py # /admin/* │ │ ├── internal.py # /internal/* (только ЦП) │ │ └── v1.py # /api/v1/* (ПИ) │ └── services/ │ ├── jobs.py # Redis RPUSH │ ├── scheduler.py # периодический re-queue │ ├── ingest.py # дедуп + map sync │ ├── filtering.py │ ├── map_query.py │ ├── events_query.py │ ├── analytics.py │ └── migrations.py └── frontend/ ├── Dockerfile # Vite build + nginx ├── nginx.conf # proxy /api /admin /internal → ca-api └── src/ ├── views/ # Map, Parsers, Events, Analytics, Consumers ├── components/ # карта, CRUD объектов ├── api/ # HTTP-клиенты └── router/index.ts ``` ### Модели данных | Модель | Таблица | Назначение | |--------|--------|------------| | `Event` | `events` | Нормализованное событие; UK `source_url` | | `ParseJob` | `parse_jobs` | Конфиг парсера, интервал, статус | | `MapObject` | `map_objects` | Точка на карте (event или ручная) | | `ObjectMedia` | `object_media` | Файлы к объектам | | `Consumer` | `consumers` | Подписчик ПИ (hash ключа) | | `ConsumerFilter` | `consumer_filters` | Фильтры среза ПИ | ### HTTP-поверхности | Prefix | Кто вызывает | Содержание | |--------|--------------|------------| | `/api/*` | UI, публичный health | Карта, объекты, медиа | | `/admin/*` | UI admin | Jobs, events, analytics, consumers | | `/internal/*` | Только ЦП | ingest, job status, listener subscriptions | | `/api/v1/*` | Внешние клиенты | Events с Bearer-ключом | Internal защищён заголовком `X-Internal-Token` (`INTERNAL_TOKEN`). ### Jobs и scheduler 1. `POST /admin/jobs` / retry → запись `ParseJob` + `enqueue_job` (`services/jobs.py`). 2. Очередь: `cp:jobs:{family}` из `contracts/queues.py`. 3. `services/scheduler.py` — тик ~30 с, повторная постановка активных jobs по `interval_seconds`. ### Ingest `POST /internal/ingest` → `services/ingest.py`: - дедуп по `source_url`; - создание `Event`; - при координатах — sync `MapObject`; - batch обновляет статус job; `listener: true` — нет. ### Frontend (маршруты) | Path | View | |------|------| | `/` | `MapViewPage.vue` | | `/parsers` | `ParsersView.vue` | | `/events` | `EventsView.vue` | | `/analytics` | `AnalyticsView.vue` | | `/consumers` | `ConsumersView.vue` | Карта: Leaflet, фильтры дат/региона/темы/источника, CRUD объектов (ПКМ), медиа, таймлайн появления (если включён в UI). ### Nginx `ca-frontend` слушает `:80` (с хоста `:8080`), проксирует backend-пути на `http://ca-api:8000`. Лимит тела для медиа задаётся в `nginx.conf`. ### Env (ЦА) | Переменная | Назначение | |------------|------------| | `DATABASE_URL` | PostgreSQL | | `REDIS_URL` | Очереди | | `INTERNAL_TOKEN` | Auth ЦП ↔ ЦА | | `TEST_PI_API_KEY` | Seed consumer `test-pi` | | `UPLOAD_DIR` | Медиа (по умолчанию `/data/uploads`) | ### Типовые точки входа в код | Задача | Файл | |--------|------| | Новый admin endpoint | `routers/admin.py` | | Логика ingest | `services/ingest.py` | | Фильтры карты | `services/map_query.py` + `routers/map.py` | | Новый экран UI | `frontend/src/views/` + `router/index.ts` | | Поля парсера в форме | `ParsersView.vue` (+ contracts) |