Introduce reusable ParserProfile and ParseChannel, pair jobs with enqueue flatten, Events admin CRUD, and drop inline/legacy parser-builder UI and aliases. Co-authored-by: Cursor <cursoragent@cursor.com>
7.5 KiB
Архитектура ЦА (Analytics Center)
Центр аналитики — ядро MapMil: PostgreSQL, FastAPI, Vue admin/карта, distribution API для ПИ.
Общий контекст: docs/architecture-overview.md.
ПИ отдельно: DISTRIBUTION.md.
Общими чертами
ЦА:
- хранит события, jobs, объекты карты, consumers;
- отдаёт единственный UI (
ca-frontend); - ставит задания в Redis для ЦП;
- принимает ingest от ЦП;
- отдаёт срезы внешним системам через
/api/v1/events.
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).
Подробнее
Структура
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/* (jobs, events, consumers)
│ │ ├── parser_profiles.py # /admin/parser-profiles/*
│ │ ├── parse_channels.py # /admin/parse-channels/*
│ │ ├── internal.py # /internal/* (только ЦП)
│ │ └── v1.py # /api/v1/* (ПИ)
│ └── services/
│ ├── jobs.py # Redis RPUSH + flatten Profile/Channel
│ ├── scheduler.py # периодический re-queue
│ ├── ingest.py # дедуп + map sync
│ ├── parser_builder.py # DeepSeek → HeuristicProfile (один раз)
│ ├── 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, Channels, Profiles, Parsers, …
├── components/ # карта, CRUD объектов
├── api/ # HTTP-клиенты
└── router/index.ts
Модели данных
| Модель | Таблица | Назначение |
|---|---|---|
Event |
events |
Нормализованное событие; UK source_url |
ParserProfile |
parser_profiles |
Статичный heuristic-профиль (JSON-правила) |
ParseChannel |
parse_channels |
Канал (Telegram handle) |
ParseJob |
parse_jobs |
Связка канал+профиль, интервал, статус; FK profile_id + channel_id |
MapObject |
map_objects |
Точка на карте (event или ручная) |
ObjectMedia |
object_media |
Файлы к объектам |
Consumer |
consumers |
Подписчик ПИ (hash ключа) |
ConsumerFilter |
consumer_filters |
Фильтры среза ПИ |
HTTP-поверхности
| Prefix | Кто вызывает | Содержание |
|---|---|---|
/api/* |
UI, публичный health | Карта, объекты, медиа |
/admin/* |
UI admin | Jobs, channels, profiles, events, analytics, consumers |
/internal/* |
Только ЦП | ingest, job status, listener subscriptions |
/api/v1/* |
Внешние клиенты | Events с Bearer-ключом |
Internal защищён заголовком X-Internal-Token (INTERNAL_TOKEN).
Профили, каналы и пары
- Канал (
CRUD /admin/parse-channels) — handle Telegram + метаданные. - Профиль (
CRUD /admin/parser-profiles) — Generate (DeepSeek один раз по образцу) → Preview → Saveheuristic_profile. - Пара (
POST /admin/jobsсchannel_id+profile_id) →ParseJob; при enqueue ЦА разворачивает пару в плоскийsource_config:
{
"channel": "<ParseChannel.channel>",
"limit": 100,
"extract_mode": "profile",
"heuristic_profile": { "...из ParserProfile..." }
}
ЦП по-прежнему получает только Redis payload — без доступа к таблицам Profile/Channel.
Roadmap: кастомные пользовательские таблицы подменяют список target-fields при генерации; профиль будет ссылаться на schema_id.
Jobs и scheduler
POST /admin/jobs/ retry → записьParseJob+enqueue_parse_job(flatten + RPUSH).- Очередь:
cp:jobs:{family}изcontracts/queues.py. 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 |
/channels |
ChannelsView.vue |
/parser-profiles |
ParserProfilesView.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) |
DEEPSEEK_API_KEY |
Generate профиля на ca-api; не нужен для preview/runtime |
DEEPSEEK_BASE_URL / DEEPSEEK_MODEL |
Опционально |
Типовые точки входа в код
| Задача | Файл |
|---|---|
| Новый 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) |