Introduce background scheduling and migrations for analytics ingest, expand parser management and map toolbar UX, and ignore local data/session files from version control. Co-authored-by: Cursor <cursoragent@cursor.com>
205 lines
10 KiB
Markdown
205 lines
10 KiB
Markdown
# MapMil Platform (ЦП → ЦА → ПИ)
|
||
|
||
Единая платформа: ЦА (аналитика и карта) + ЦП (парсинг Telegram).
|
||
|
||
## Архитектура
|
||
|
||
```mermaid
|
||
flowchart LR
|
||
CA[ЦА Analytics Center]
|
||
CP[ЦП Parsing Center]
|
||
PI[ПИ External Consumers]
|
||
|
||
CA -->|jobs via Redis| CP
|
||
CP -->|POST /internal/ingest| CA
|
||
CA -->|GET /api/v1/events| PI
|
||
```
|
||
|
||
| Центр | Контейнеры | Назначение |
|
||
|-------|------------|------------|
|
||
| **ЦА** | `ca-db`, `ca-api`, `ca-frontend` | PostgreSQL, ingest API, карта, distribution API |
|
||
| **ЦП** | `cp-workers` | Парсинг Telegram: real-time listener (Telethon) + batch-задания из Redis |
|
||
| **Общее** | `redis` | Очередь заданий ЦА → ЦП |
|
||
|
||
## Структура monorepo
|
||
|
||
```
|
||
MapMil/
|
||
├── centers/
|
||
│ ├── analytics/
|
||
│ │ ├── api/ # CA backend (FastAPI + PostgreSQL)
|
||
│ │ └── frontend/ # CA admin UI (Vue + Leaflet)
|
||
│ └── parsing/
|
||
│ └── workers/ # CP workers (Telethon)
|
||
├── contracts/ # Shared schemas (ingest, jobs)
|
||
├── data/ # telegram.session (локально, не в git)
|
||
├── docker-compose.yml
|
||
└── .env # TELEGRAM_API_ID, TELEGRAM_API_HASH, …
|
||
```
|
||
|
||
## Быстрый старт
|
||
|
||
1. Создайте `.env` из примера и укажите ключи Telegram:
|
||
|
||
```bash
|
||
cp .env.example .env
|
||
# отредактируйте TELEGRAM_API_ID и TELEGRAM_API_HASH
|
||
```
|
||
|
||
2. Положите сессию Telegram в `data/telegram.session` (файл Telethon SQLite). Если мигрируете со старого SocialParser:
|
||
|
||
```bash
|
||
cp ../SocialParser/data/telegram.session data/
|
||
```
|
||
|
||
3. Запуск:
|
||
|
||
```bash
|
||
docker compose up --build
|
||
```
|
||
|
||
4. Откройте UI: [http://localhost:8080](http://localhost:8080)
|
||
|
||
## Admin UI
|
||
|
||
Веб-интерфейс ЦА доступен по тем же адресу. Навигация в шапке:
|
||
|
||
| Раздел | Путь | Описание |
|
||
|--------|------|----------|
|
||
| **Карта** | `/` | Интерактивная карта событий: навигация по датам (flatpickr), пресеты периода, фильтры региона/темы/источника, подложки Яндекс/OSM/Topo/ESRI, линейка, полноэкранный режим, центрирование по координатам и городам, поиск населённых пунктов (Nominatim). CRUD для ручных объектов (ПКМ). Поддерживает `?eventId=` |
|
||
| **Парсеры** | `/parsers` | Telegram-парсеры с периодическим запуском, настройка интервала, редактирование и удаление; дубликаты по `source_url` не записываются |
|
||
| **События** | `/events` | Фильтрация, пагинация, просмотр деталей, ссылка «На карте» для событий с координатами |
|
||
| **Аналитика** | `/analytics` | KPI-карточки, график динамики ingest за 30 дней, топ населённых пунктов и регионов |
|
||
| **ПИ** | `/consumers` | CRUD подписчиков distribution API, ротация ключей, тест среза через `/api/v1/events` |
|
||
|
||
## Сохранение Telegram-сессии
|
||
|
||
**Важно:** существующий файл сессии **не удаляется и не пересоздаётся**.
|
||
|
||
- Локально: `data/telegram.session` (SQLite Telethon, в `.gitignore`)
|
||
- В Docker `cp-workers`: каталог `./data` монтируется как `/data` (read-write)
|
||
- Путь в контейнере: `/data/telegram.session`
|
||
- Переменные из `.env`: `TELEGRAM_API_ID`, `TELEGRAM_API_HASH`, `TELEGRAM_SESSION_PATH=/data/telegram.session`
|
||
|
||
Файл сессии и `.env` не коммитятся.
|
||
|
||
## API
|
||
|
||
### Карта
|
||
|
||
| Метод | Путь | Описание |
|
||
|-------|------|----------|
|
||
| GET | `/api/health` | Health check |
|
||
| GET | `/api/map/objects` | Объекты на карте с данными события; фильтры: `date_from`, `date_to`, `on_date`, `region`, `topic`, `source_type`, `search`, `event_id` |
|
||
| GET | `/api/map/filters` | Справочники для фильтров: `regions`, `topics`, `source_types`, `available_dates`, `date_bounds` |
|
||
| GET | `/api/objects` | Объекты (legacy, без фильтров события) |
|
||
| POST | `/api/objects` | Создать объект вручную |
|
||
|
||
#### Инструменты карты (UI)
|
||
|
||
- **Даты:** кнопки ❮❮/❮/❯/❯❯, календарь flatpickr, пресеты периода (1 неделя — 1 год / день)
|
||
- **Фильтры:** регион, тема, источник (из `/api/map/filters`)
|
||
- **Подложки:** Яндекс Карты / Спутник (ru_RU), OpenStreetMap, OpenTopoMap, ESRI Satellite
|
||
- **Инструменты:** линейка (PolylineMeasure), полноэкранный режим, координаты/города, поиск Nominatim
|
||
- **Точки:** маркеры из ingest + popup; ручные объекты — CRUD и drag
|
||
- **Обновление:** polling `/api/map/objects` каждые 30 с
|
||
|
||
### ЦА Admin
|
||
|
||
| Метод | Путь | Описание |
|
||
|-------|------|----------|
|
||
| POST | `/admin/jobs` | Создать парсер (сразу в очередь + периодический запуск) |
|
||
| GET | `/admin/jobs` | Список парсеров |
|
||
| PATCH | `/admin/jobs/{id}` | Изменить канал/лимит, `interval_seconds`, `is_active` |
|
||
| DELETE | `/admin/jobs/{id}` | Удалить парсер |
|
||
| POST | `/admin/jobs/{id}/retry` | Повторить failed-задание |
|
||
| GET | `/admin/events` | События с фильтрами (`{ items, total }`) |
|
||
| GET | `/admin/analytics/summary` | KPI-сводка |
|
||
| GET | `/admin/analytics/timeline?days=` | Динамика ingest по дням |
|
||
| GET | `/admin/analytics/top-localities?limit=` | Топ населённых пунктов |
|
||
| GET | `/admin/analytics/top-regions?limit=` | Топ регионов |
|
||
| GET | `/admin/consumers` | Список подписчиков ПИ |
|
||
| POST | `/admin/consumers` | Создать подписчика ПИ |
|
||
| PATCH | `/admin/consumers/{id}` | Обновить подписчика |
|
||
| POST | `/admin/consumers/{id}/rotate-key` | Сменить API-ключ |
|
||
|
||
Пример задания Telegram:
|
||
|
||
```bash
|
||
curl -X POST http://localhost:8080/admin/jobs \
|
||
-H 'Content-Type: application/json' \
|
||
-d '{"source_type":"telegram","source_config":{"channel":"creamy_caprice","limit":50}}'
|
||
```
|
||
|
||
### ЦП → ЦА (internal)
|
||
|
||
| Метод | Путь | Описание |
|
||
|-------|------|----------|
|
||
| POST | `/internal/ingest` | Приём batch событий (заголовок `X-Internal-Token`) |
|
||
|
||
### ПИ Distribution API
|
||
|
||
| Метод | Путь | Описание |
|
||
|-------|------|----------|
|
||
| GET | `/api/v1/events` | События с фильтром по API-ключу |
|
||
|
||
Тестовый consumer `test-pi` создаётся при старте с ключом из `TEST_PI_API_KEY` (по умолчанию `test-pi-api-key-change-me`):
|
||
|
||
```bash
|
||
curl http://localhost:8080/api/v1/events \
|
||
-H 'Authorization: Bearer test-pi-api-key-change-me'
|
||
```
|
||
|
||
## Парсинг Telegram
|
||
|
||
`cp-workers` объединяет два режима в **одном процессе** (общая сессия `telegram.session`):
|
||
|
||
| Режим | Как работает |
|
||
|-------|----------------|
|
||
| **Listener (real-time)** | Telethon `NewMessage` / `Album` на активных парсерах (`is_active=true`); новый пост сразу уходит в ingest |
|
||
| **Batch (по расписанию)** | Планировщик в `ca-api` ставит задание в Redis; воркер забирает последние N постов (`iter_messages`) |
|
||
|
||
Дубликаты по `source_url` при ingest пропускаются. Listener не меняет `status` парсера (флаг `listener: true` в ingest).
|
||
|
||
Переменные `cp-workers`:
|
||
|
||
| Переменная | По умолчанию | Описание |
|
||
|------------|--------------|----------|
|
||
| `TELEGRAM_LISTENER_ENABLED` | `true` | Включить real-time listener |
|
||
| `TELEGRAM_LISTENER_REFRESH_SECONDS` | `60` | Как часто обновлять список каналов из БД |
|
||
|
||
Internal API: `GET /internal/listener/subscriptions` — список активных каналов для listener.
|
||
|
||
## Поток данных
|
||
|
||
1. Аналитик создаёт парсер: `POST /admin/jobs` (канал, лимит, интервал в секундах)
|
||
2. **Listener** сразу подписывается на канал и ingest-ит новые посты
|
||
3. **Планировщик** `ca-api` периодически ставит batch-задание в Redis (`cp:jobs`)
|
||
4. `cp-workers` забирает batch-задание, парсит последние N постов
|
||
5. Результаты → `POST /internal/ingest` (дубликаты по `source_url` пропускаются)
|
||
6. Новые события с координатами появляются на карте как `MapObject`
|
||
7. Внешние ПИ получают срез через `/api/v1/events`
|
||
|
||
## Миграция EventRecord → Event
|
||
|
||
| SocialParser | CA Event |
|
||
|--------------|----------|
|
||
| `event` | `description` / `title` |
|
||
| `date` (dd.mm.yy) | `event_date` |
|
||
| `geolocation` | `latitude`, `longitude` |
|
||
| `locality` | `locality`, `region` |
|
||
| `source_url` | `source_url` |
|
||
| — | `source_type = "telegram"` |
|
||
|
||
## Остановка
|
||
|
||
```bash
|
||
docker compose down
|
||
```
|
||
|
||
Данные PostgreSQL сохраняются в volume `pgdata`.
|
||
|
||
## Legacy
|
||
|
||
Старые каталоги `backend/` и `frontend/` в корне оставлены для справки; активная разработка — в `centers/`.
|