# 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/`.