Files
MapMil/centers/analytics/ARCHITECTURE.md
T
gitrusprusandCursor 71f8cf169e Add Profile/Channel entities with CRUD and remove legacy parser builder.
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>
2026-08-16 14:51:03 +03:00

7.5 KiB
Raw Blame History

Архитектура ЦА (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).

Профили, каналы и пары

  1. Канал (CRUD /admin/parse-channels) — handle Telegram + метаданные.
  2. Профиль (CRUD /admin/parser-profiles) — Generate (DeepSeek один раз по образцу) → Preview → Save heuristic_profile.
  3. Пара (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

  1. POST /admin/jobs / retry → запись ParseJob + enqueue_parse_job (flatten + RPUSH).
  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
/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)