233 lines
16 KiB
Markdown
233 lines
16 KiB
Markdown
# Тикеты реализации «Контракт-чек» (production refactor)
|
||
|
||
> **Актуальная архитектура:** `ARCHITECTURE.md` (supersedes `IMPLEMENTATION_PLAN.md` для этапов 1+).
|
||
> Состояние кода на момент синхронизации: реализованы `api/` (вкл. B2B-роуты, web-auth, passkeys, magic links),
|
||
> `core/`, `worker_extract/`, `worker_prescreen/`, `worker_analyze/`, `worker_notify/`, `bot/`;
|
||
> 6 Dockerfile-ов в `srv/`; `docker-compose.yml` разводит профили `services` и `edge`;
|
||
> миграции `0001_initial` … `0010_passkeys_magic_links`; unit- и интеграционные тесты зелёные.
|
||
> DoD: `ruff check src tests`, `ruff format --check src tests`, `uv run ty check src`, `pytest` зелёные;
|
||
> `.env.example` актуален.
|
||
>
|
||
> Статусы: `todo` / `in_progress` / `done` / `blocked`.
|
||
|
||
---
|
||
|
||
## Аудит реализации (текущее состояние)
|
||
|
||
> Проверено: код собирается (`pyproject.toml` + `uv.lock`), миграции накатываются,
|
||
> `api/` стартует, unit-тесты проходят (≈80 тестов зелёные), интеграционные тесты проходят на поднятой инфраструктуре.
|
||
|
||
| Компонент | Статус | Доказательство / пробел |
|
||
|-----------|--------|---------------------------|
|
||
| Stage 0 prototype | removed | Удалён: standalone CLI больше не нужен; `tests/unit/test_checklist_report.py`, `test_chunker.py`, `test_extractor.py` зелёные. |
|
||
| `core/` — общий домен | done | `db/` (models, session, enums, repositories), `mq/`, `s3/`, `llm/`, `analysis/`, `extraction/`, `notifications/`, `security/`, `credits.py`, `tokens.py`, `api_keys.py`, `rate_limit.py`, `redis_client.py`, `config.py`, `logging.py`, `metrics.py`, `telemetry.py`, `sentry.py`, `passkeys.py`. |
|
||
| Миграции / БД | done | `0001_initial.py` (6 таблиц) … `0010_passkeys_magic_links.py` (passkeys, magic links, user name). |
|
||
| `api/` — FastAPI ядро | done | `POST /api/v1/documents`, `GET /api/v1/reports/{id}`, `GET /api/v1/me`, `/healthz`, `/readyz`, `/metrics`; user JWT auth (`/api/v1/auth/telegram/*`, `/api/v1/auth/{register,login,...}`, passkeys, magic links); B2B `/api/v1/analyze`; `/admin/*`; reserve-on-enqueue (`services.py`). |
|
||
| `worker_extract/` | done | `consumer.py`/`handler.py`/`extract_document.py`/`__main__.py`: consume `DocumentUploaded` → MinIO dl → extract/OCR → `.txt` upload → publish `PrescreenRequested` (или `DocumentExtracted`, если прескрин выключен); failure-class + refund. |
|
||
| `worker_prescreen/` | done | `consumer.py`/`handler.py`/`__main__.py` + `extractor*.py`/`router.py`/`config.py`: consume `PrescreenRequested` → гибридная экстракция метаданных → роутинг `deep_analysis`/`manual_review`/`auto_approve`; публикация `AnalyzeRequested` или терминальный статус/лёгкий отчёт. |
|
||
| `worker_analyze/` | done | `consumer.py`/`handler.py`/`__main__.py`: consume `DocumentExtracted` → LLM → Report → `status=done`; refund-on-DLQ по политике. |
|
||
| `worker_notify/` | done | `consumer.py`/`handler.py`/`__main__.py`: consume `NotificationMessage` → SMTP (password reset, magic link) или dev-лог при пустом `SMTP_HOST`. |
|
||
| `bot/` — Telegram adapter | done | `client.py`/`config.py`/`handlers.py`/`__main__.py`: `/start`, upload→`POST /documents`, poll→deliver; граница импортов проверяется `tests/unit/test_bot_boundary.py`. |
|
||
| Dockerfile-ы | done | `srv/{api,worker-extract,worker-prescreen,worker-analyze,worker-notify,bot}/Dockerfile` — все 6 (deps-группы PEP 735 заточены на сервис). |
|
||
| Docker Compose | done | Инфра (default) + профиль `services` (api + worker-ы) + профиль `bot` (Telegram-адаптер, можно на отдельном хосте) + профиль `edge` (nginx+certbot) с `depends_on: service_healthy`. Профили `obs`/`observer` — позже. |
|
||
| Observability | in_progress | Prometheus-метрики (`/metrics`), Sentry, OpenTelemetry SDK — в коде. Полный стек Prom/Grafana/Tempo/OTel-collector — позже. |
|
||
| Stage 3 — B2B API | done | `api/routes/b2b.py`, `core/api_keys.py`, `core/rate_limit.py`, `core/redis_client.py`, миграция `0002_api_keys.py`, `tests/integration/test_b2b_api.py`, `tests/unit/test_rate_limit.py`; `X-API-Key` auth + token-bucket rate-limit. |
|
||
|
||
---
|
||
|
||
## Этап 0 — Прототип (go/no-go)
|
||
|
||
### T-E0-001 — Аккаунт Ollama Cloud + каркас прототипа
|
||
**Статус:** done · **Оценка:** S
|
||
|
||
`.env.example` содержит `OLLAMA_HOST`/`OLLAMA_API_KEY`/`OLLAMA_MODEL=qwen2.5:14b`/`OLLAMA_FALLBACK_MODEL=qwen2.5:7b`.
|
||
`python -m contract_check --help` работает. Теги без `-instruct`.
|
||
|
||
### T-E0-002 — Экстрактор текста (PDF/DOCX)
|
||
**Статус:** done · **Оценка:** S
|
||
|
||
`src/contract_check/core/analysis/extractor.py`; покрыт `tests/unit/test_extractor.py`.
|
||
|
||
### T-E0-003 — Чек-лист анализа (v1)
|
||
**Статус:** done · **Оценка:** S
|
||
|
||
`src/contract_check/core/analysis/checklist.py` — 10 пунктов с `id/title/description`.
|
||
|
||
### T-E0-004 — Ollama Cloud-клиент + repair-loop
|
||
**Статус:** done · **Оценка:** M
|
||
|
||
`src/contract_check/core/llm/ollama_cloud.py`; мок-тесты 200/429-fallback/битый-JSON в `tests/unit/test_llm_ollama_cloud.py`.
|
||
|
||
### T-E0-005 — Промпт + JSON-отчёт (с чанками)
|
||
**Статус:** done · **Оценка:** M
|
||
|
||
`core/analysis/analyzer.py`, `chunker.py`, `report_schema.py`; disclaimer в markdown.
|
||
|
||
### T-E0-006 — Замеры метрик и economics go/no-go
|
||
**Статус:** todo · **Оценка:** M
|
||
|
||
Требует живых прогонов 3–5 договоров через Ollama Cloud + `docs/stage0-results.md`.
|
||
|
||
---
|
||
|
||
## Этап 1 — Production refactor (api + core + workers + bot)
|
||
|
||
> Архитектура: hexagonal, RabbitMQ pipeline (`extract.q` → `analyze.q`), MinIO, Postgres, Redis.
|
||
> Профили Docker Compose: default = инфра; `services` = api + workers; `bot` = Telegram-адаптер.
|
||
|
||
### T-E1-001 — Ядро `contract_check.core`
|
||
**Статус:** done · **Оценка:** L
|
||
|
||
Пакет `src/contract_check/core/` содержит `db/`, `mq/`, `s3/`, `llm/`, `analysis/`, `credits.py`, `tokens.py`, `config.py`, `logging.py`, `metrics.py`, `telemetry.py`, `sentry.py`.
|
||
|
||
### T-E1-002 — Миграции БД (6 таблиц)
|
||
**Статус:** done · **Оценка:** M
|
||
|
||
`migrations/versions/0001_initial.py`: `users`, `documents`, `reports`, `jobs`, `service_tokens`, `invoices` (stub).
|
||
|
||
### T-E1-003 — FastAPI ядро: документы, кредиты, enqueue, отчёты
|
||
**Статус:** done · **Оценка:** L
|
||
|
||
`src/contract_check/api/`: `POST /api/v1/documents`, `GET /api/v1/reports/{id}`, `GET /api/v1/me`, `/healthz`, `/metrics`.
|
||
Reserve-on-enqueue (`core/credits.py`) и user JWT auth (`core/auth.py`, `api/routes/auth/`).
|
||
|
||
### T-E1-004 — Worker-extract (CPU: pymupdf + tesseract)
|
||
**Статус:** done · **Оценка:** M · **Зависимости:** T-E1-001, T-E1-002
|
||
|
||
`src/contract_check/worker_extract/` (`consumer.py`/`handler.py`/`extract_document.py`/`__main__.py`):
|
||
consume `DocumentUploaded` из `extract.q` → скачать blob из MinIO →
|
||
`extractor` + `ocr.ocr_pdf()` → загрузить `.txt` → publish `PrescreenRequested` в `prescreen.q` (или `DocumentExtracted` в `analyze.q`, если прескрин выключен).
|
||
Failure-classification + refund-on-DLQ. Образ `srv/worker-extract/Dockerfile`.
|
||
|
||
### T-E1-005 — Worker-analyze (I/O: LLM provider)
|
||
**Статус:** done · **Оценка:** M · **Зависимости:** T-E1-001, T-E1-002, T-E1-004
|
||
|
||
`src/contract_check/worker_analyze/` (`consumer.py`/`handler.py`/`__main__.py`):
|
||
consume `DocumentExtracted` из `analyze.q` → скачать `.txt` → chunk → LLM (через `core/llm` port)
|
||
→ validate/repair → сохранить `Report`, `status=done`; refund при terminal failure (`core/credits.py`).
|
||
Образ `srv/worker-analyze/Dockerfile`.
|
||
|
||
### T-E1-006 — Telegram-бот (aiogram 3) — HTTP-адаптер
|
||
**Статус:** done · **Оценка:** M · **Зависимости:** T-E1-003
|
||
|
||
`src/contract_check/bot/` (`client.py`/`config.py`/`handlers.py`/`__main__.py`):
|
||
`/start`, приём PDF/DOCX → `POST /api/v1/documents`, poll `GET /api/v1/reports/{id}`
|
||
→ отправка отчёта (разбивка/файл >4096 симв.), реакция на 402/400/202.
|
||
Образ `srv/bot/Dockerfile`. **Не импортирует** `core.db`/`core.s3`/`core.llm`/`core.mq`/`core.credits`
|
||
(проверяется `tests/unit/test_bot_boundary.py`).
|
||
|
||
### T-E1-007 — Docker Compose: сервисы и профили
|
||
**Статус:** done · **Оценка:** M · **Зависимости:** T-E1-004, T-E1-005, T-E1-006
|
||
|
||
`docker-compose.yml`: default-профиль = инфра (`postgres`, `redis`, `rabbitmq`, `minio`, `minio-init`);
|
||
профиль `services` = `api`, `worker-extract`, `worker-prescreen`, `worker-analyze`, `worker-notify` с `depends_on: service_healthy`;
|
||
профиль `bot` = `bot` (Telegram-адаптер), можно поднять на этом же или на отдельном хосте;
|
||
профиль `edge` = nginx+certbot. Все 7 Dockerfile-ов в `srv/`. Профили `obs`/`observer` добавляют observability-стек.
|
||
|
||
### T-E1-008 — Оплата (ЮKassa) и пополнение кредитов
|
||
**Статус:** done · **Оценка:** M · **Зависимости:** T-E1-003, T-E1-006
|
||
|
||
Реализовано треком `.scratch/user-profile-billing/` (тикеты 001–019): порт
|
||
`PaymentProvider` + адаптер ЮKassa, checkout топ-апов/подписок, webhook со
|
||
state-machine, авто-возвраты с клавбэком и billing-hold, worker-billing,
|
||
админ-панель счетов. Архитектура — `ARCHITECTURE.md §8a`, включение — `DEPLOY.md §4a`.
|
||
|
||
### T-E1-009 — Деплой + edge (Nginx/certbot)
|
||
**Статус:** done · **Оценка:** M · **Зависимости:** T-E1-007
|
||
|
||
`deploy/nginx/templates/contract-check.conf.template`, `deploy/nginx/certbot-init.sh`, `DEPLOY.md §13`, `docker-compose.yml` профиль `edge`.
|
||
|
||
### T-E1-010a — Мониторинг: логи (Grafana + Loki + Promtail)
|
||
**Статус:** done · **Оценка:** S · **Зависимости:** T-E1-007
|
||
|
||
`deploy/observability/loki-config.yaml`, `promtail-config.yaml`, `grafana/provisioning/{datasources,dashboards}/`,
|
||
`grafana/dashboards/logs.json`; профиль `obs` в `docker-compose.yml` поднимает `loki`, `promtail`, `grafana`.
|
||
Promtail забирает логи всех compose-контейнеров через Docker socket и пушит в Loki;
|
||
доступен дашборд `Contract Check — Logs` с фильтром по `service` и поиском по `correlation_id`.
|
||
|
||
### T-E1-010b — Мониторинг: метрики + трейсы (Prometheus + Tempo + OTel collector + Sentry)
|
||
**Статус:** todo · **Оценка:** M · **Зависимости:** T-E1-010a
|
||
|
||
Добавить `prometheus`, `tempo`, `otel-collector` в профиль `obs`. Prometheus scrape `:9100`/`:9101`/`:9102`.
|
||
OTel collector принимает OTLP и маршрутизирует в Tempo. Дашборды: queue depth, job latency, LLM tokens, credits.
|
||
Sentry уже инициализируется в коде при наличии `SENTRY_DSN`.
|
||
|
||
---
|
||
|
||
## Этап 2 — Веб + подписки
|
||
|
||
### T-E2-001 — Миграция БД: планы и счета
|
||
**Статус:** todo · **Оценка:** M · **Зависимости:** T-E1-002
|
||
|
||
`users.plan` (`free|solo|team`), `users.plan_renews_at`. Таблица `invoices` уже существует (stub); наполнить логикой.
|
||
|
||
### T-E2-002 — Telegram Login Widget auth
|
||
**Статус:** done · **Оценка:** M · **Зависимости:** T-E1-003
|
||
|
||
Реализовано в `core/auth.py` + `api/routes/auth/telegram.py`: `/api/v1/auth/telegram/web` и `/api/v1/auth/telegram/miniapp` проверяют HMAC-подпись Telegram и выдают тот же user JWT, что и бот.
|
||
|
||
### T-E2-003 — React SPA (Vite)
|
||
**Статус:** todo · **Оценка:** L · **Зависимости:** T-E2-002
|
||
|
||
Загрузка, история, профиль, подписка. Сборка в статику; `api` отдаёт `index.html` на `/`.
|
||
|
||
### T-E2-004 — Подписки ЮKassa recurring
|
||
**Статус:** todo · **Оценка:** M · **Зависимости:** T-E2-001, T-E2-003
|
||
|
||
Тарифы solo (1 490 ₽), team (3 990 ₽). Ежемесячное продление/reset.
|
||
|
||
---
|
||
|
||
## Этап 3 — B2B API
|
||
|
||
> **Примечание:** в текущей архитектуре B2B API строится **не после веба**, а поверх уже готового `api` и существующей очереди.
|
||
> Зависимость от T-E2-002 снята.
|
||
|
||
### T-E3-001 — Миграция БД: `api_keys` и `api_key_requests`
|
||
**Статус:** done · **Оценка:** M · **Зависимости:** T-E1-002
|
||
|
||
`migrations/versions/0002_api_keys.py`: таблицы
|
||
- `api_keys(id, user_id FK, name, key_hash, rate_limit_rps, monthly_quota, monthly_used, resets_at, revoked, created_at, last_used_at)`
|
||
- `api_key_requests(id, api_key_id FK, document_id FK, created_at)`
|
||
Накатывается/откатывается чисто; SQLAlchemy-модели в `core/db/models.py`.
|
||
|
||
### T-E3-002 — Redis-клиент и token-bucket rate limiter
|
||
**Статус:** done · **Оценка:** M · **Зависимости:** T-E3-001
|
||
|
||
`core/redis_client.py` (async Redis из `redis_url`), `core/rate_limit.py` (token bucket per `api_key_id`,
|
||
с `MemoryRateLimiter`-фолбэком для unit-тестов без Redis). Лимит по умолчанию = `B2B_DEFAULT_RATE_LIMIT_RPS` (3),
|
||
переопределяется `api_keys.rate_limit_rps`. Покрыт `tests/unit/test_rate_limit.py`; 429 при превышении.
|
||
|
||
### T-E3-003 — `X-API-Key` auth dependency
|
||
**Статус:** done · **Оценка:** M · **Зависимости:** T-E3-001, T-E3-002
|
||
|
||
`api/deps.py`: `require_api_key` — проверяет `X-API-Key` по `key_hash` (`core/api_keys.py`),
|
||
отклоняет revoked, обновляет `last_used_at`, применяет rate-limit. `api/routes/b2b.py`:
|
||
`POST /api/v1/analyze`, `GET /api/v1/b2b/reports/{document_id}`, `GET /api/v1/b2b/usage`.
|
||
Покрыт `tests/integration/test_b2b_api.py` (вкл. 401/429).
|
||
|
||
### T-E3-004 — Управление API-ключами (user JWT auth)
|
||
**Статус:** done · **Оценка:** S · **Зависимости:** T-E3-001
|
||
|
||
`api/routes/b2b.py` под user JWT auth: `POST /api/v1/b2b/keys` (plaintext ключ возвращается
|
||
**только один раз**), `GET /api/v1/b2b/keys`, `POST /api/v1/b2b/keys/{id}/revoke`,
|
||
`GET /api/v1/b2b/keys/{id}/usage`. Revoke мгновенно отключает аутентификацию.
|
||
|
||
### T-E3-005 — Интеграционные тесты B2B API
|
||
**Статус:** done · **Оценка:** M · **Зависимости:** T-E3-003
|
||
|
||
`tests/integration/test_b2b_api.py`: upload → 202 → poll report; ветки 401/429.
|
||
Seed API key в `tests/integration/conftest.py`.
|
||
|
||
---
|
||
|
||
## Сводка порядка (обновлённая)
|
||
|
||
1. **E0** — прототип (done, кроме живых замеров T-E0-006).
|
||
2. **E1.001–007** — ядро + БД + FastAPI `api` + worker-extract + worker-analyze + bot + Docker/compose (done).
|
||
3. **E3** — B2B API (done: миграция, rate-limit, auth, роуты, тесты).
|
||
4. **E1.008** — оплата ЮKassa (done — трек `.scratch/user-profile-billing/`, тикеты 001–019: профили, планы/подписки, платежи, возвраты, worker-billing, админ-панель).
|
||
5. **E1.009–010** — деплой/edge (Nginx/certbot — done) и observability: логи Grafana+Loki+Promtail — done, метрики/трейсы Prometheus/Tempo/OTel — todo.
|
||
6. **E2** — веб (todo; React SPA; Login Widget и подписки уже реализованы в рамках E1.008-трека).
|
||
|
||
**Ближайшие работы:** E1.010 (observability), затем E2 (React SPA поверх готового API).
|