DealDocumentScreening/docs/TICKETS.md
2026-09-02 23:32:47 +03:00

232 lines
16 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.

# Тикеты реализации «Контракт-чек» (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/`, `prototype/`;
> 7 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 | done | `src/contract_check/prototype/` работает; `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,prototype}/Dockerfile` — все 7 (deps-группы PEP 735 заточены на сервис). |
| Docker Compose | done | Инфра (default) + профиль `services` (api, 3 worker-а, bot) + профиль `edge` (nginx+certbot) с `depends_on: service_healthy`. Профиль `obs` — позже. |
| 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
Требует живых прогонов 35 договоров через 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.
### 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`, `bot` с `depends_on: service_healthy`;
профиль `edge` = nginx+certbot. Все 7 Dockerfile-ов в `srv/`. Профиль `obs` добавляет `loki` + `promtail` + `grafana` для логов (сделано в T-E1-010a).
### T-E1-008 — Оплата (ЮKassa) и пополнение кредитов
**Статус:** done · **Оценка:** M · **Зависимости:** T-E1-003, T-E1-006
Реализовано треком `.scratch/user-profile-billing/` (тикеты 001019): порт
`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.001007** — ядро + БД + 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/`, тикеты 001019: профили, планы/подписки, платежи, возвраты, worker-billing, админ-панель).
5. **E1.009010** — деплой/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).