- Python 73%
- TypeScript 15.6%
- Shell 11.2%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
Приложение писало во ВНУТРЕННИЙ SeaweedFS узла de-u26-pg-sr1 (seaweedfs:8333), а presigned-ссылки строило на публичном домене s3.isdata.tech, который с 21.09 ведёт на ho-u26-pg-sr2. Загрузка и выдача указывали в разные хранилища. Сейчас не было сломано (активность близка к нулю), но любая новая загрузка развела бы хранилища. MPTOOL_REACT_S3_ENDPOINT: seaweedfs:8333 → https://s3.isdata.tech Проверено замером: потеряно при переключении 0 объектов (799 на de-u26-pg-sr1 против 800 на ho-u26-pg-sr2; разница — один файл, который есть только на ho-u26-pg-sr2), расхождений по содержимому 0. Полный цикл на живом приложении: upload → stat → presigned_url → скачивание HTTP 200 с верным содержимым. Откат: вернуть seaweedfs:8333 в .env и перезапустить api/worker/beat/worker-retro. SeaweedFS на de-u26-pg-sr1 не тронут — контейнер жив, данные на месте. |
||
| .forgejo/workflows | ||
| .husky | ||
| backend | ||
| ci | ||
| deploy | ||
| docs | ||
| frontend | ||
| graphify-out | ||
| infra | ||
| marketplaces-docs | ||
| memory | ||
| monitoring | ||
| reference | ||
| scripts | ||
| .env.example | ||
| .gitattributes | ||
| .gitignore | ||
| .graphifyignore | ||
| AGENTS.md | ||
| CHANGELOG.md | ||
| docker-compose.prod.yml | ||
| docker-compose.tests.yml | ||
| PLAN.md | ||
| README.md | ||
| WIKI.md | ||
MP Tool React
Самостоятельный проект анализа маркетплейсов на современном стеке: (Wildberries, Ozon, Yandex Market) + монетизация через Workspace (рабочие области), пакеты подписок, гранулярные фичи и RBAC.
Полностью автономен: собственная папка / БД / Docker-стек / сеть / Traefik / S3 (SeaweedFS) / git. Внешних зависимостей от других проектов нет.
Стек
- Frontend: React 19 + TypeScript, Vite (rolldown), Refine 5, Ant Design 5, zustand, openapi-typescript, recharts; Prettier + Oxlint.
- Backend: FastAPI (Python 3.12), SQLAlchemy 2 + Alembic, pydantic v2, Celery, cryptography (Fernet); Ruff + pytest.
- БД:
mptool_reactна PostgreSQL 18 —de-u26-pg-sr1(31.77.11.188, хостde-u26-pg-sr1), переезд 18.09.2026. Прежний узел.41выведен из эксплуатации 18.09.2026 (реплика остановлена, метрики и off-host бэкап переведены наde-u26-pg-sr1). - Хранилище: SeaweedFS 4.47 (S3) для картинок товаров / аватаров —
s3.isdata.tech, бакетmptool-react-files. - Деплой: собственный Docker-стек (10 сервисов: api / web / worker / worker-retro / beat / mq / cache / seaweedfs / traefik / forgejo),
публикация через собственный Traefik (HTTPS Let's Encrypt). Домены:
mptool-react.isdata.tech,git.isdata.tech,s3.isdata.tech.
Ключевые возможности
- Workspace — изолированные рабочие области: люди + организации + подписки + платильщик.
- Пакеты и фичи — конструктор тарифов (Plan), гранулярный динамический реестр фич (Feature).
- Подписки — привязаны к Workspace, комбинируются (фичи всех активных объединяются).
- RBAC — роли платформы (
platform_*) и воркспейса (owner/admin/manager/member), передача владения. - Self-service регистрация — создаёт пользователя + Workspace(owner) + trial-подписку.
- API-ключи WB/Ozon/Yandex — шифрование Fernet (секрет в
_api_key_raw, маска в API). WB-ключ проверяется при добавлении и по кнопке (/probe): фазыprobe→retro→steady, продавец/ИНН/статус токена видны в UI. - Ретро-синхронизация WB — из UI: создание задачи (
orders_sales/finance/advert), прогресс в реальном времени, перезапуск упавших (/admin/retro/jobs). - Расписание синхронизаций — из UI: выключить шумную задачу или сменить интервал без правки
кода и перезапуска beat (
/admin/schedule; оверрайды вapp_metastate). - Дашборды (раздел «Аналитика»): WB Dash, WB P&L (фильтрация по бренду/категории/предмету/SKU), WB Adv (рекламная аналитика: KPI, динамика, топы, платформы), OZON Dash; сравнение периодов, графики с градиентами, остатки WB, админ-версия рекламы по всем организациям; тёмная тема, экспорт XLSX.
Структура проекта
backend/
app/
main.py # FastAPI entrypoint + подключение роутеров
config.py # pydantic-settings (env)
database.py # SQLAlchemy 2 engine/session
models/ # модели (auth, billing, accounts, wb, oz, ya, finance)
# schema_invariants.py — общие имена UNIQUE/индексов для моделей И миграций
api/ # роутеры (auth, register, workspace, billing, keys,
# dashboard, files, admin_users, routers=GenericCRUD)
services/ # permissions (RBAC), feature_catalog, meta_state (версии алгоритмов),
# day_range (sargable-фильтр по суткам для timestamptz-колонок)
tasks/ # celery-задачи синхронизации, crypto (Fernet)
# wb_dim_tasks.py — единый справочник размерностей:
# sync_subject_map / build_product_dim / normalize_dimensions
alembic/versions/ # миграции (billing, workspace, keys-encrypt, ...)
tests/ # pytest (164 теста, 18.09.2026)
frontend/
src/
App.tsx # Refine + роутинг + меню
pages/ # Dashboard, Workspace, Packages, Features, Subscriptions, Keys,
# Retro (ретро-синхронизация), Schedule (расписания beat), ...
api/ # dataProvider, authProvider, s3
store/ # zustand (workspaceId, theme)
docker-compose.prod.yml # полный продуктивный стек
monitoring/ # отдельный стек мониторинга (Prometheus+Grafana, см. monitoring/README.md)
scripts/deploy.sh # деплой с пиннингом образов по SHA и автооткатом
WIKI.md # полная документация (архитектура, роли, API, эндпоинты)
PLAN.md # план разработки (Workspace-модель и фазы)
Локальная разработка
Требования: Node.js 24 + npm 11, Python 3.12, uv (Python-менеджер зависимостей).
# backend
python3 -m uv venv .venv --python 3.12
source .venv/bin/activate
uv pip install -r requirements.txt -r requirements-dev.txt
uvicorn app.main:app --reload
# frontend
cd frontend && npm install && npm run dev
Тесты и качество (Dev-инструменты — ОБЯЗАТЕЛЬНЫ при кодинге)
Актуальные версии инструментов: Ruff 0.16.2, pytest 8.3.4, pytest-asyncio 0.24.0, mypy 1.16.0, vitest 4.1.11, Oxlint 1.79, TypeScript 7.0.2, Prettier 3.9.6.
# backend (ВАРИАНТ Б: тесты на реальном PostgreSQL — тестовая БД mptool_react_tests (de-u26-pg-sr1))
# Задайте MPTOOL_REACT_DATABASE_URL в своём окружении (креды тестовой БД не храним в README).
# Пример формата: postgresql+psycopg://<user>:<pass>@<host>:5432/mptool_react_tests
cd backend && .venv/bin/ruff check app tests
export MPTOOL_REACT_DATABASE_URL="postgresql+psycopg://mptool_react_tests:<TEST_PASSWORD>@<TEST_DB_HOST>:5432/mptool_react_tests"
# В CI тот же DSN лежит в repo-secret MPTOOL_REACT_TESTS_DB_URL (см. ci/README.md) — sqlite не используется.
.venv/bin/python -m pytest tests/ # все тесты на PG (схема через alembic; контейнер сбрасывает кэш фич)
.venv/bin/python -m pytest tests/ --cov=app --cov-report=html # покрытие
.venv/bin/mypy --explicit-package-bases --config-file=pyproject.toml app # строгая типизация (+ infra: 15 файлов)
./alembic_check.sh # проверка миграций: единственный head, цепочка без разрывов, сверка модели↔БД
# python берётся из .venv (или PYTHON=/путь/к/python); --no-db — без сверки с БД
# ⚠️ 18.09.2026: тестовая БД переехала на de-u26-pg-sr1 (31.77.11.188); хост в conftest больше не фиксирован
# frontend
cd frontend && npm run build # tsc -b + сборка
npm run typecheck # tsc --noEmit (быстрая проверка типов)
npm run lint # oxlint src/
npm run test # vitest (52)
# диагностика прода ОДНИМ вызовом (см. AGENTS.md «⚡ Скорость»):
# export SSHPASS; ./scripts/prod_probe.sh [all|stack|data]npm run test:cov # vitest + coverage (v8)
# адаптивность (ОБЯЗАТЕЛЬНО при правках UI): аудит всех страниц на 3 вьюпортах (390/768/1440)
cd ~/.openclaw/workspace-mptool-react/audit-mobile && node audit.js
# требует playwright + токен доступа в /tmp/token.txt (chmod 600); файл ВРЕМЕННЫЙ —
# удалять сразу после прогона (живой токен в /tmp = утечка, см. AGENTS.md)
# git-хук pre-commit: prettier(lint-staged) + oxlint + tsc --noEmit + ruff (авто, обязательно чисто)
🩺 Разбор дефекта и верификация (правила, 17.09.2026)
Применяется при любом разборе дефекта и решении «внедрять ли». Введено по решению хозяина
(практики взяты из GitHub Spec Kit, адаптированы — читались первоисточники, не README).
Полный текст правил — в AGENTS.md, раздел «Разбор дефекта и верификация» (здесь только суть,
чтобы не дублировать источник).
Триада, шаги не смешивать: диагноз (корневая причина + воспроизведение) → фикс (по корню, ponytail) → верификация (симптом ушёл И регрессий нет). Нельзя чинить, не записав диагноз.
Вердикт верификации — три градации:
verified · partial (симптом ушёл, но есть регрессия/часть проверок неинформативна) · failed.
⚠️ Запрет ложной верификации: зелёные тесты не дают verified, если в диагнозе записано
воспроизведение, которое фактически не выполнялось. Честный вердикт — partial.
Дефект, наблюдавшийся в кабинете МП или на проде, проверяется тем же путём (psql/curl/
скриншот), а не только pytest.
Таблица проверок (обязательна, две группы — симптом и регрессия):
| Check | Command | Result | Notes |
|---|---|---|---|
| Воспроизведение (после фикса) | из диагноза | pass / fail / skipped / not-run | причина |
| Новые/обновлённые тесты | pytest tests/… |
pass / fail | |
| Регрессия (backend) | ruff · pytest · mypy |
pass / fail | |
| Регрессия (frontend) | tsc · oxlint · vitest |
pass / fail | |
| Миграции | alembic_check.sh |
pass / skipped | |
| UI | аудит адаптивности | pass / skipped |
⚠️ Инструмент недоступен → not-run с причиной, не пропускать молча (наш случай: pytest
в git-хуке пропускается без тестового DSN — это должно называться явно).
Decision-gate «внедрять ли» (scorecard, 6 критериев: ценность проблемы · сила доказательств ·
ценность против бездействия · реализуемость · стратегическое соответствие · риск):
go требует силы доказательств adequate+; при weak/unknown вердикт — needs-clarification.
kill — это успех, а не провал, с прямой причиной.
Прочее из тех же правил: path safety (симлинки/выход за корень — отказать, не следовать;
корни вычислять от __file__/git rev-parse, не хардкодить); URL Trust Policy (RFC1918 192.168.*
и loopback — отказ; фиксировать сработавшую ветку); чек-лист заполняет проверяющий, не исполнитель.
🔀 Консистентность при нескольких агентах (scripts/)
Агенты и субагенты работают параллельно в одном репозитории. Конфликты убираются по категории ресурса — сначала изоляция/вычисление, блокировки только где ресурс физически один:
| Категория | Инструмент | Зачем |
|---|---|---|
| Изолируемое | git worktree add ../wt-<задача> -b <ветка> |
своё дерево, общий .git — конфликта нет |
| Вычисляемое | scripts/changelog_tool.py --check/--fix |
номер записи вычисляется из файла, не назначается |
| Общий ресурс | scripts/agent_lock.sh <имя> -- <cmd> |
ровно один писатель (тестовая БД, прод) |
| Общая ветка | scripts/git_sync.sh -m "..." |
flock → fetch → rebase → push с повторами |
python3 scripts/changelog_tool.py --check # 0 = канон, 1 = коллизия (зовётся из pre-commit)
scripts/git_sync.sh -m "сообщение" # коммит + сериализованный push
scripts/agent_lock.sh тестовая-БД -- python -m pytest tests/ # 75 = ресурс занят
⚠️ pre-commit проверяет нумерацию CHANGELOG, только когда он в staged — чтобы не тормозить коммиты.
Локи живут в $GIT_COMMON_DIR/agent-locks/ (вне git, общие для всех worktree).
📋 Сбор работы субагентов (scripts/subagent_worklog.py)
Результаты субагентов рассеяны по сессиям — из PLAN.md их не видно. Инструмент собирает
структурированный источник (openclaw tasks --json --runtime subagent) и сверяет его с планом:
python3 scripts/subagent_worklog.py # что сделали + есть ли след в CHANGELOG/memory/PLAN
python3 scripts/subagent_worklog.py --check # 1 = успешный прогон без следа (для автоматизаций)
python3 scripts/subagent_worklog.py --since 2026-09-01 --json
⚠️ Плейбуки лежат в воркспейсе агента (~/.openclaw/workspace-mptool-react/memory/),
а не в репозитории — инструмент проверяет оба каталога.
🩺 Эксплуатация и надёжность (scripts/, deploy/)
Полный порядок действий — в docs/RUNBOOK.md. Инструменты:
| Инструмент | Зачем |
|---|---|
scripts/verify_stack.sh |
health-gate: сервисы, HTTP, readiness БД, потребители очередей, краш-лупы |
scripts/stack_watchdog.sh |
то же + алерт в локальный webhook (JSONL на проде + Telegram); гоняется таймером каждые 5 мин |
scripts/config_validate.sh |
статическая проверка ПЕРЕД применением: compose + promtool + bash -n |
scripts/backup_db.sh |
ночной бэкап (systemd-timer mptool-backup-db, 04:15 MSK) + off-host копия на de-u26-pg-sr1; пароль через env, не argv |
scripts/restore_drill.sh |
учение восстановления: дамп → отдельная БД → сверка счётчиков → verdict (запуск на .41 под sudo) |
scripts/deploy.sh |
деплой с тегом по SHA, авто-миграциями, verify, --rollback и push в реестр |
scripts/pitr_base_backup.sh / pitr_drill.sh |
PITR: базовая копия кластера + учение восстановления на точку времени (узел de-u26-pg-sr1) |
scripts/wal_cleanup.sh |
чистка WAL-архива PITR + ретенция копий (таймер каждые 15 мин, узел de-u26-pg-sr1) — инцидент 16.09.2026 |
scripts/setup_replication_primary.sh, setup_replica.sh, replica_check.sh, promote_replica.sh |
выведены из работы 18.09.2026 вместе с репликой (узел .41 погашен): содержимое осталось только как справка для аварийного сценария |
./scripts/config_validate.sh # ПЕРЕД up/деплоем (дешевле инцидента)
./scripts/verify_stack.sh # 0 = стек работает
sudo /opt/mptool-react/scripts/restore_drill.sh # на .41: проверить, что бэкап восстанавливается
Таймеры (systemd): mptool-react-stack.service (старт + гейт), -watchdog.timer (5 мин),
-prune.timer (вс 05:30, чистка образов/кэша), mptool-react-monitoring-verify.timer.
Временные зоны (проверено фактом 15.09.2026):
| Слой | Зона | Как задаётся |
|---|---|---|
| Приложение (api/web/worker/beat/mq/cache/traefik) | MSK | TZ (якорь x-tz) |
| Мониторинг (prometheus, alertmanager, grafana, экспортёры) | MSK | TZ + bind-mount zoneinfo |
Хосты .40 и .41 |
MSK | timedatectl (переведены 15.09.2026) |
| Cron de-u26-pg-sr1 | MSK | время задаём прямо в MSK (15 4 * * * = 04:15 MSK) |
| systemd-таймеры | MSK | зона суффиксом в OnCalendar (Sun *-*-* 05:30:00 Europe/Moscow) |
| Celery beat | MSK | timezone=Europe/Moscow + enable_utc=True |
PostgreSQL: timezone (зона ДАННЫХ) |
UTC (явно) | timezone = 'Etc/UTC' — от неё зависят date::date и EXTRACT(HOUR) |
PostgreSQL: log_timezone (метки логов) |
MSK | log_timezone = 'Europe/Moscow' |
⚠️ Разделение зоны данных и зоны логов в PostgreSQL — намеренное. timezone влияет на
date::date и EXTRACT(HOUR ...) в отчётах и прекомпьютах, поэтому зафиксирована явно в UTC
и не меняется со сменой зоны хоста. log_timezone влияет только на метки в журнале.
На бывшей реплике то же самое — иначе после promote группировка по дням сместилась бы на 3 часа.
⚠️ Ловушки, проверенные на практике:
- ключа
Timezone=в секции[Timer]системы systemd не существует — он молча игнорируется (Unknown key name 'Timezone' ... ignoring), таймер идёт по TZ хоста; - смена зоны хоста переинтерпретирует cron в новой зоне:
15 1(был 04:15 MSK) стал бы 01:15 MSK. Пересчёт выражения обязателен —scripts/set_host_tz.shделает это сам (ядро —scripts/lib/tz_shift_cron.py), сохраняя фактическое время запуска; - брать время запуска cron надо из факта (
cron.log, имена файлов дампов), а не из выражения; - проверить зону контейнера через
dateнельзя, еслиdate— BusyBox (prometheus/экспортёры): он берёт зону изlocaltime, а не изTZ, и врёт UTC. Смотреть логи (time=...+03:00); - в образах
forgejo,seaweedfs,cadvisorнет tzdata —TZне работает без bind-mount/usr/share/zoneinfo.
Персистентность: именованные тома mq_data / cache_data (AOF) / celerybeat_data —
очереди и rate-limit-ключи переживают пересоздание контейнеров.
🔎 Проверка утечек секретов (scripts/check_secret_leaks.sh)
Ищет пароли/токены проекта в логах, дампах, рабочем дереве, git HEAD, argv процессов и
системных логах. Создан по следам инцидента 14.09.2026 (пароль БД в cron.log; след в
/var/log/auth.log от передачи пароля аргументом).
./scripts/check_secret_leaks.sh # без root (системные логи → WARN)
echo "$LOCAL_SUDO_PASS" | sudo -S ./scripts/check_secret_leaks.sh # полная
# коды: 0 = чисто, 1 = утечка, 2 = проверка неполная
⚠️ Скрипт сам не передаёт секреты в argv (иначе проверка создаст утечку).
Стиль кодинга — ponytail (ВКЛЮЧЁН, решение хозяина 17.09.2026)
Скилл ponytail v4.9.0 (ClawHub, @dietrichgebert/ponytail) — режим «ленивого сеньора»:
YAGNI, сначала stdlib и нативные фичи платформы, без абстракций «на будущее».
Парный скилл ponytail-audit — разовый аудит репозитория на переусложнение.
Статус: включён; уровень — lite (постоянно с 14.09.2026; 16.09 уровень full → ultra;
17.09 кратко отключён и в тот же день возвращён; уровень понижен до lite 17.09.2026).
lite = сделать что просят, но в одну строку назвать более ленивую альтернативу — выбор за хозяином.
Уровень меняется командой /ponytail lite|full|ultra и закреплён в якорях AGENTS.md/MEMORY.md
(сам по себе он session-scoped и в новой сессии сбросился бы к дефолту full).
- Включён в двух местах, иначе режим не закрепится (он живёт в контексте, а не в скилле):
~/.openclaw/openclaw.json→skills.entries.ponytail.enabled = true— штатный механизм (бэкап конфига:openclaw.json.bak-ponytail-on-20260917-173411);- секция «Стиль кодинга (ponytail)» в
AGENTS.md— предписание YAGNI-лестницы.
- Заголовок секции в
AGENTS.mdне переименовывать: он зафиксирован вagents.defaults.compaction.postCompactionSections, поэтому после сжатия контекста возвращается актуальный статус, а не пустое место. - Установка:
skills/ponytail/,skills/ponytail-audit/(воркспейс агента). - Выключение (по команде хозяина):
openclaw config set skills.entries.ponytail.enabled false --strict-json+ снять предписание вAGENTS.md. Уровень —/ponytail lite|full|ultra. - ⚠️ Обязательные процессы проекта остаются в силе — они никогда не были частью ponytail:
README/PLAN/WIKI,
ruff+pytest+mypy,tsc+oxlint+vitest+build,alembic_check.sh, аудит адаптивности, плейбук+CHANGELOG при изменении математики P&L. - ⚠️ Вывод
ponytail-audit— гипотезы, а не решения: сверять с фактами (аудит 14.09 переоценил экономию более чем вдвое: −322 против фактических −98).
Graphify — граф знаний кодовой базы (локально, без LLM)
Карта связей проекта (функции/классы/модули frontend↔backend + схема БД PostgreSQL) в виде knowledge graph. Позволяет агенту искать по коду запросом вместо грепания файлов.
# установлен через uv tool (graphifyy); граф лежит в git (graphify-out/)
graphify query "какие функции считают P&L" # scoped-подграф по вопросу
graphify path "<A>" "<B>" # путь между двумя сущностями
graphify explain "<концепт>" # узел + его связи
graphify update . # обновить граф после правок кода (AST, 0 LLM)
graphify cluster-only . # пересобрать отчёт/HTML после ручных правок графа
# Мост «модель → таблица БД» (MAPS_TO): восстанавливается авто-хуком post-commit.
# Запускать вручную после полного extract:
python3 scripts/graphify_add_model_edges.py
⚠️ Карта БД обновляется АВТОМАТИЧЕСКИ хуком post-commit (раз в 7 дней, скрипт
scripts/graphify_refresh_db_map.py); принудительно — python3 scripts/graphify_refresh_db_map.py --force.
Раньше снимок устаревал молча (в сентябре 2026 не хватало 5 таблиц → терялись MAPS_TO-рёбра).
Если обновление нужно вручную (интерпретатор — из uv tool, не системный python3, иначе
ModuleNotFoundError: graphify):
GPY=/home/alex/.local/share/uv/tools/graphifyy/bin/python
PGDSN="<DSN>" $GPY -c "import os,json;from graphify.pg_introspect import introspect_postgres as i;json.dump(i(os.environ['PGDSN']),open('/tmp/pg_fresh.json','w'),ensure_ascii=False)"
Порядок после любой переинтроспекции: python3 scripts/graphify_add_model_edges.py
(мост MAPS_TO) → graphify cluster-only . (отчёт/HTML).
Актуальность (на 17.09.2026): граф 2105 узлов / 3862 ребра, карта БД 70 pg-узлов,
MAPS_TO 68 рёбер, 215 сообществ с семантическими именами.
Проверено: graphify update . карту БД и мост НЕ теряет (это главный инвариант эксплуатации).
label-бэкенд (имена сообществ, LLM) — рабочий рецепт на 17.09.2026: PlusVibe через openai-совместимый эндпоинт, ключ подставляет secret egress proxy (в окружении — sentinel, plaintext не используется):
export OPENAI_BASE_URL="https://plusvibeapi.ru/v1"
export OPENAI_API_KEY="$PLUSVIBE_API_KEY" # sentinel → подстановка на прокси
graphify label . --backend openai --model deepseek-v4.1-flash --max-concurrency 2
⚠️ max_tokens задавать с запасом: модель тратит токены на reasoning, при малом лимите — 502
reasoning_budget_exhausted. Исторический рецепт (RouterAI) сохранён в AGENTS.md, правило №6.
hooks: post-commit/post-checkout авто-перестраивают граф; merge driver union для graph.json.
Context7 (MCP-доступ к документации)
Установлен @upstash/context7-mcp@4.0.4 через OpenClaw MCP. Позволяет запрашивать актуальную документацию
сторонних библиотек (Refine, FastAPI, AntD, SQLAlchemy и др.) без поиска вручную.
# конфигурация в ~/.openclaw/openclaw.json → mcp.servers.context7
# использование: инструмент context7__query-docs после context7__resolve-library-id
Отладка
- Backend (FastAPI):
python3 -m pip install --user --break-system-packages debugpy; запускbackend/run_debug.sh(debugpy слушает порт 5678) → VS Code «Remote Attach» (готовый конфиг в.vscode/launch.json). - Frontend (Vite): запуск через VS Code-конфиг «Vite (Node inspect)».
Оркестрация синков (очереди Celery) и P&L по дням
- Раздельные очереди:
wb-fast(ежедневные синки + пересчёт P&L) иwb-retro(длинные глубокие ретро) — без конкуренции/накопления. - Воркеры:
mptool-react-worker(wb-fast, concurrency=2),mptool-react-worker-retro(wb-retro, concurrency=1). - Redis dedup-локи против накопления: advert/media/deep-retro/build_finance_daily.
- Прекомпьют P&L — оконный пересчёт (17.09.2026):
build_finance_dailyпересобирает не всю историю, а только(org, день), чьи строки детализации менялись с прошлого успешного прогона (водяной знакwb.finance_daily.watermarkпоsynced_at, TEMP_fd_scope). День перестраивается целиком (предикат на DELETE+INSERT+UPDATE) — иначе размазывание орг-расходов считалось бы от устаревшей базы.full=True— принудительно вся история (импорт себестоимости, дедуп biz_hash, ручнойrecompute_pnl); beat: окно*/20 мин+ полная 04:20 как страховочная сетка. Прежний режим (DELETE+залив всей истории каждые 4 ч) давал 2.52 перезаливки/час и раздул heap до 479 МБ против 54 МБ живых данных — см.memory/2026-09-17-precompute-window.md. - P&L по дням: финансы (wb_financedaily.rr_date), реклама (wb_advertcampaignstats.report_date), медиа (wb_mediadaily.report_date).
- Продвижение (P&L) в
wb_financedailyраскрывается на строки (UI) как у indeepa: «Оказание услуг ВБ.Продвижение» (=promotion_advert, источникwb_advertcampaignstats), «ВБ.Медиа» (=promotion_media), Аванс/Возврат «Баллы за отзывы» + Списание + Витрина (изpromo_services_breakdown) + Подписка «Джем». Источник advert — кампании (НЕ deduction); разбивка строк не меняет сумму Продвижения. - Лимиты WB per-аккаунт (по ключу), не IP → параллельные эндпоинты безопасны (429 при превышении).
Деплой на прод (u24-sr1-soft)
Штатный способ — scripts/deploy.sh (сборка → миграции → запуск → verify, с автооткатом):
cd /opt/mptool-react && git pull
./scripts/deploy.sh # полный деплой текущего HEAD
./scripts/deploy.sh --no-build # только миграции + перезапуск
./scripts/deploy.sh --status # текущий/предыдущий тег образов
./scripts/deploy.sh --rollback # откат на предыдущий тег
Механика отката: образы тегируются по короткому SHA (не только :latest); состояние —
в .deploy-state / .deploy-state.prev (вне git). Тег передаётся compose через
MPTOOL_REACT_TAG (дефолт latest — обратная совместимость).
Скрипт требует чистого git-дерева, применяет миграции до переключения и при провале
verify_stack.sh автоматически откатывается на предыдущий тег.
Ручной эквивалент (если скрипт недоступен):
git pull && docker compose -f docker-compose.prod.yml up -d --build
docker exec -w /app mptool-react-api-1 alembic upgrade head
После деплоя FastAPI-документация доступна по внешним адресам:
- Swagger UI:
https://mptool-react.isdata.tech/api/v1/docs - OpenAPI JSON:
https://mptool-react.isdata.tech/api/v1/openapi.json
Генерация типов фронтенда из продового OpenAPI:
cd frontend && npm run typegen # из продового API
npm run typegen:local # из локального http://localhost:8000/api/v1/openapi.json
Бэкапы БД (ночной systemd-timer)
Скрипт scripts/backup_db.sh (таймер mptool-backup-db.timer, 04:15 MSK; юнит — deploy/):
pg_dump в контейнере postgres:18.4 → .tmp → валидация gzip → публикация; хранит KEEP_DAYS
(14) дней, исключает схемы staging/backup_2608.
⚠️ Пароль/credentials НЕ пишутся в лог. Всё, что идёт в cron.log, проходит через redact()
(sed -E 's#://[^[:space:]]*@#://***@#g'): log(), хвост stderr pg_dump и stderr gzip -t.
Добавляя новую запись в лог — только через log() или с явным redact, иначе DSN с паролем утечёт
(дефект 14.09.2026: cron.log содержал 30 строк с паролем роли mptool_react).
⚠️ Права дампа — 600 (umask 077 в скрипте). Дамп содержит все данные БД; до аудита
15.09.2026 файлы выходили 664 — world-readable для любого пользователя узла.
Свежесть бэкапа контролируется мониторингом (backup_last_success_timestamp_seconds,
алерт при возрасте >30 ч) — чтобы молчаливый отказ не повторял инцидент 10.09.
Мониторинг и алерты
Отдельный стек monitoring/ (Prometheus + Grafana + Alertmanager + экспортёры) —
не перезапускается деплоем приложения. Grafana: https://monitor.isdata.tech.
6 дашбордов (Обзор, Контейнеры, Хосты, PostgreSQL, Внешние проверки, Мониторинг-самоконтроль) —
автозагрузка через provisioning; генерируются monitoring/grafana/generate_dashboards.py.
Метрики хоста/контейнеров/БД + срок TLS-сертификатов + качество данных (дубли P&L,
свежесть бэкапа, stale biz_hash) + OOM-события + самоконтроль контура (Prometheus/Alertmanager
up, доставка алертов, TSDB). Алерты фиксируются на de-u26-pg-sr1, опционально — Telegram.
⚠️ Конфиги монтируются каталогами, не файлами: single-file bind mount держит inode и
после git pull контейнер видит старое содержимое (инцидент 19.09.2026).
Подробности и состав — monitoring/README.md.
⚠️ Метрики качества данных пишет scripts/data_quality_metrics.sh по systemd-таймеру mptool-data-quality (*:0/15).
Лог — /opt/backups/mptool-react/var/metrics.log (НЕ /var/log: каталог принадлежит
root:syslog 775, alex в группе syslog не состоит → cron не мог открыть редирект и
скрипт не запускался вообще — дефект найден аудитом 15.09.2026). Ротация —
deploy/logrotate-mptool-metrics. Свежесть .prom стережёт алерт CriticalPromFileStale.
Документация
Полное описание архитектуры, ролей, админ-панели, URL-карты API и монетизации — в WIKI.md. План разработки и статус — в PLAN.md.
Правила поддержки документации
При каждом изменении проекта синхронно обновлять: PLAN.md (статус/этапы), README.md
(структура/стек/команды), WIKI.md (архитектура/роли/эндпоинты).
Три файла — единый источник правды для проекта.