Files
roi24/ARCHITECTURE.md

12 KiB
Raw Permalink Blame History

ROI24 — Техническая архитектура

Общая схема

┌──────────────────────────────────────────────────────────────┐
│                       ИСТОЧНИКИ ДАННЫХ                       │
│  1С:Предприятие │ Банки │ CRM │ Реклама │ Маркетплейсы        │
└────────┬─────────┬───────┬─────┬────────┴───────────────────┘
         │         │       │     │
    ┌────▼────┐ ┌──▼────┐ │     │
    │ 1C Ad.  │ │Bank.  │ │Ad.  │ │CRM Ad. │
    └────┬────┘ └──┬────┘ │     └──┬──────┘
         │         │       │       │
    ┌────▼─────────▼───────▼───────▼─────────┐
    │          Python ETL Pipeline            │
    │    APScheduler / Celery + Redis         │
    └─────────────────┬──────────────────────┘
                      │
    ┌─────────────────▼──────────────────────┐
    │           PostgreSQL 16                 │
    │   raw_data │ aggregated │ metadata      │
    └─────────────────┬──────────────────────┘
                      │
    ┌─────────────────▼──────────────────────┐
    │            FastAPI (REST API)           │
    │   GET /api/dashboard/{client_id}        │
    │   GET /api/metrics/{client_id}/{type}   │
    │   GET /api/trend/{client_id}            │
    └─────────────────┬──────────────────────┘
                      │
    ┌─────────────────▼──────────────────────┐
    │         HTML Dashboard (ECharts)        │
    │   Single-page SPA или server-rendered   │
    └────────────────────────────────────────┘

Схема БД (PostgreSQL)

-- Клиент
CREATE TABLE clients (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    name VARCHAR(256) NOT NULL,
    niche VARCHAR(100),              -- ecommerce, services, retail, b2b
    status VARCHAR(20) DEFAULT 'active',
    plan VARCHAR(20) DEFAULT 'start', -- start, business, premium
    created_at TIMESTAMP DEFAULT NOW()
);

-- Подключённые источники данных
CREATE TABLE data_sources (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    client_id UUID REFERENCES clients(id),
    source_type VARCHAR(50),         -- 1c, bank, ad_platform, crm
    source_name VARCHAR(100),        -- СберБизнес, Яндекс.Директ, amoCRM
    config JSONB NOT NULL,           -- credentials, API keys (encrypted)
    enabled BOOLEAN DEFAULT TRUE,
    last_sync TIMESTAMP,
    created_at TIMESTAMP DEFAULT NOW()
);

-- Исходные данные (сырые)
CREATE TABLE raw_data (
    id BIGSERIAL PRIMARY KEY,
    source_id UUID REFERENCES data_sources(id),
    data JSONB NOT NULL,             -- сырые данные из API
    fetched_at TIMESTAMP NOT NULL,   -- когда скачаны
    processed BOOLEAN DEFAULT FALSE  -- обработаны ETL
);

-- Агрегированные данные (для дашборда)
CREATE TABLE metrics (
    id BIGSERIAL PRIMARY KEY,
    client_id UUID REFERENCES clients(id),
    metric_key VARCHAR(100) NOT NULL, -- revenue, profit, orders, etc.
    metric_value DECIMAL(15,2) NOT NULL,
    period_start DATE NOT NULL,
    period_end DATE,
    dimensions JSONB,                -- city, channel, manager, etc.
    source TEXT,                     -- откуда рассчитано
    created_at TIMESTAMP DEFAULT NOW(),
    UNIQUE(client_id, metric_key, period_start, dimensions::text)
);

-- Индекс для быстрого поиска по дашборду
CREATE INDEX idx_metrics_client_period ON metrics(client_id, period_start DESC);
CREATE INDEX idx_metrics_key ON metrics(metric_key);

-- История изменений дашборда (аудит)
CREATE TABLE dashboard_changes (
    id BIGSERIAL PRIMARY KEY,
    client_id UUID REFERENCES clients(id),
    metric_key VARCHAR(100),
    old_value DECIMAL(15,2),
    new_value DECIMAL(15,2),
    change_pct DECIMAL(10,2),
    changed_at TIMESTAMP DEFAULT NOW()
);

-- Настройки дашборда (какие метрики показывать клиенту)
CREATE TABLE dashboard_config (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    client_id UUID REFERENCES clients(id) UNIQUE,
    template VARCHAR(50),            -- ecommerce, services, retails
    metrics JSONB,                   -- какие метрики включены
    refresh_interval INTEGER DEFAULT 1440, -- минут
    created_at TIMESTAMP DEFAULT NOW()
);

ETL Pipeline

Структура адаптера

roi24/PIPELINE/adapters/
├── base.py              # Базовый класс адаптера
├── 1c/
│   ├── __init__.py
│   ├── xmlrpc.py        # Подключение через XML-RPC 1С
│   ├── direct_db.py     # Прямой доступ к БД 1С
│   └── exporters.py     # Экспорт данных
├── bank/
│   ├── __init__.py
│   ├── sberbank.py      # СберБизнес
│   ├── tinkoff.py       # Тинькофф
│   ├── tochka.py        # Точка Банк
│   └── csv_import.py    # Импорт из CSV
├── ad/
│   ├── __init__.py
│   ├── yandex_direct.py # Яндекс.Директ API
│   └── vk_ads.py        # VK Ads
├── crm/
│   ├── __init__.py
│   ├── bitrix24.py      # Битрикс24 REST API
│   └── amo.py           # amoCRM API
└── marketplace/
    ├── __init__.py
    └── ozon.py

Базовый адаптер

# PIPELINE/adapters/base.py
from abc import ABC, abstractmethod
from datetime import datetime
from typing import Optional

class BaseAdapter(ABC):
    """Базовый класс для всех адаптеров источников данных."""

    def __init__(self, config: dict):
        self.config = config
        self.last_sync: Optional[datetime] = None

    @abstractmethod
    def fetch(self, date_from: str, date_to: str) -> list[dict]:
        """Получить данные за период."""
        pass

    def _save_raw(self, data: list[dict], source_id: str):
        """Сохранить сырые данные в PostgreSQL."""
        pass

    def _aggregate(self, raw: list[dict]) -> list[dict]:
        """Преобразовать сырые данные в метрики."""
        pass

    def sync(self, date_from: str, date_to: str, source_id: str):
        """Полный цикл синхронизации."""
        raw = self.fetch(date_from, date_to)
        self._save_raw(raw, source_id)
        metrics = self._aggregate(raw)
        self.last_sync = datetime.now()
        return metrics

Типовой цикл ETL

1. APScheduler запускает job каждые N минут
2. Job для каждого источника данных:
   a. Вызывает adapter.fetch(date_from, date_to)
   b. Сохраняет сырые данные в raw_data
   c. Агрегирует в метрики (metrics table)
   d. Помечает raw_data как processed
3. При изменении агрегированных метрик > 10% —
   записывает в dashboard_changes

FastAPI API

Эндпоинты

GET    /api/health                          # health-check
GET    /api/dashboard/{client_id}           # все метрики клиента
GET    /api/metrics/{client_id}/{metric}    # конкретная метрика
GET    /api/trend/{client_id}/{metric}      # тренд (сравнение периодов)
GET    /api/export/{client_id}/{format}     # экспорт в CSV/PDF
GET    /api/clients                         # список клиентов
POST   /api/clients/{id}/connect            # подключить источник
DELETE /api/clients/{id}/sources/{src_id}   # удалить источник

Пример ответа

{
    "client_id": "uuid-here",
    "generated_at": "2026-06-20T10:00:00Z",
    "metrics": {
        "revenue": {
            "today": 125000,
            "yesterday": 98000,
            "week": 820000,
            "month": 2450000,
            "trend": "+12.5%",
            "trend_direction": "up"
        },
        "profit": {
            "today": 38500,
            "margin": "30.8%",
            "trend": "+8.2%"
        },
        "orders": {
            "today": 47,
            "conversion_rate": "3.2%",
            "avg_check": 2660
        },
        "balance": {
            "total": 1250000,
            "debtor": 340000,
            "creditor": 120000
        }
    }
}

Docker Compose (стартовый)

version: '3.9'

services:
  postgres:
    image: postgres:16-alpine
    environment:
      POSTGRES_DB: roi24
      POSTGRES_USER: roi24
      POSTGRES_PASSWORD: change_me
    ports:
      - "5432:5432"
    volumes:
      - postgres_data:/var/lib/postgresql/data
      - ./storage/schema.sql:/docker-entrypoint-initdb.d/01-schema.sql

  api:
    build:
      context: ./api
    ports:
      - "8000:8000"
    environment:
      DATABASE_URL: postgresql://roi24:change_me@postgres:5432/roi24
    depends_on:
      - postgres

  scheduler:
    build:
      context: ./scripts
    environment:
      DATABASE_URL: postgresql://roi24:change_me@postgres:5432/roi24
    depends_on:
      - postgres
    command: ["python", "update_data.py"]

  nginx:
    image: nginx:alpine
    ports:
      - "80:80"
    volumes:
      - ./docker/nginx/default.conf:/etc/nginx/conf.d/default.conf
      - ./dashboard:/usr/share/nginx/html
    depends_on:
      - api

volumes:
  postgres_data:

Безопасность

  1. Контент credentials в data_sources.config — шифровать (AES-256)
  2. API — JWT tokens для каждого клиента
  3. Данные — изоляция между клиентами на уровне client_id
  4. SSL — Nginx + Let's Encrypt (certbot)
  5. Бэкапы — ежедневные бэкапы PostgreSQL (pg_dump)

Хостинг

Вариант для старта (один VPS)

Ресурс Минимум Рекомендуемый
CPU 2 vCPU 4 vCPU
RAM 4 ГБ 8 ГБ
SSD 50 ГБ 100 ГБ NVMe
Стоимость ~1500₽/мес ~3000₽/мес

Recomended providers: Selectel, Timeweb, Yandex Cloud

Этапы масштабирования

  1. Этап 1 (1-10 клиентов): Один VPS, всё в одном Docker Compose
  2. Этап 2 (10-50 клиентов): Выделение PostgreSQL на отдельный сервер
  3. Этап 3 (50+ клиентов): Kubernetes + автоскейлинг + Redis кэш