Files
MapMil/README.md
T
gitrusprusandCursor 6dff3c1c3d Document platform architecture and Telegram proxy local setup.
Add developer docs for CA/CP/PI flows and wire cp-workers to the host SOCKS proxy so local Telegram auth and parsing work reliably.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-15 22:57:07 +03:00

210 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# MapMil Platform (ЦП → ЦА → ПИ)
Единая платформа: ЦА (аналитика и карта) + ЦП (адаптеры парсинга: Telegram, Crawl4AI, VIINA).
**Документация для разработчиков:** [`docs/`](docs/README.md) — [обзор архитектуры](docs/architecture-overview.md), [поток данных](docs/data-flow.md), [локальный запуск](docs/local-dev.md).
## Архитектура
```mermaid
flowchart LR
CA[ЦА Analytics Center]
CP[ЦП Parsing Center]
PI[ПИ External Consumers]
CA -->|jobs via Redis families| CP
CP -->|POST /internal/ingest| CA
CA -->|GET /api/v1/events| PI
```
| Центр | Контейнеры | Назначение | Документ |
|-------|------------|------------|----------|
| **ЦА** | `ca-db`, `ca-api`, `ca-frontend` | PostgreSQL, ingest API, карта, distribution API | [`centers/analytics/ARCHITECTURE.md`](centers/analytics/ARCHITECTURE.md) |
| **ЦП** | `cp-workers`, `cp-workers-web`, `cp-workers-nlp` | Адаптеры: Telegram / Crawl4AI / VIINA | [`centers/parsing/ARCHITECTURE.md`](centers/parsing/ARCHITECTURE.md) |
| **ПИ** | (маршрут в ЦА) | `GET /api/v1/events` | [`centers/analytics/DISTRIBUTION.md`](centers/analytics/DISTRIBUTION.md) |
| **Общее** | `redis` | Очереди `cp:jobs:{telegram\|web\|nlp}` | [`docs/contracts.md`](docs/contracts.md) |
Подробный обзор платформы: [`docs/architecture-overview.md`](docs/architecture-overview.md).
## Структура monorepo
```
MapMil/
├── centers/
│ ├── analytics/
│ │ ├── api/ # CA backend (FastAPI + PostgreSQL)
│ │ ├── frontend/ # CA admin UI (Vue + Leaflet)
│ │ ├── ARCHITECTURE.md
│ │ └── DISTRIBUTION.md # ПИ
│ └── parsing/
│ ├── ARCHITECTURE.md
│ └── workers/ # CP workers + adapters
├── contracts/ # Shared schemas (ingest, jobs, sources, queues)
├── docs/ # Документация для разработчиков
├── data/ # telegram.session (локально, не в git)
├── docker-compose.yml
└── .env
```
## Быстрый старт
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` / `crawl4ai` / `viina`, интервал, CRUD; дедуп по `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`.