DealDocumentScreening/README.md
febux 0c354492ef
Some checks failed
CI / Lint & typecheck (push) Successful in 26s
CI / Unit tests (push) Successful in 57s
CI / Integration tests (push) Failing after 21s
Git check
2026-09-13 04:31:10 +03:00

217 lines
19 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.

# Контракт-чек
LLM-сервис скрининга рисков в договорах по ГК РФ / ГК РБ.
Telegram-бот + web-аутентификация (email/password, JWT-пара; пасскеи и магические ссылки — passwordless) + B2B API + биллинг (ЮKassa: подписки, пакеты кредитов, авто-возвраты) сейчас; web-SPA — позже.
Pipeline: `PDF/DOCX/RTF/TXT/CSV/изображения (pymupdf / mammoth / tesseract OCR) → текст → чанки →
прескрин (гибридная эвристика + LLM: метаданные договора, роутинг) → LLM-анализ
(Ollama Cloud или YandexGPT, json-schema + repair-loop) → markdown-отчёт`
с цитатами, ссылкой на пункт и дисклеймером «не заменяет юриста».
## Документы
- [`docs/ARCHITECTURE.md`](docs/ARCHITECTURE.md) — **единственный источник истины** для production-рефакторинга. Hexagonal-архитектура, RabbitMQ-конвейер, схемы БД, очередь/ретраи/DLQ, конфиг, deploy, observability.
- [`docs/DEPLOY.md`](docs/DEPLOY.md) — руководство по развёртыванию: локально, Docker Compose, VPS, seed-token, backup, troubleshooting.
- [`docs/BUSINESS_IDEA.md`](docs/BUSINESS_IDEA.md) — идея и бизнес-модель.
- [`docs/TICKETS.md`](docs/TICKETS.md) — тикеты реализации со статусами.
## Архитектура (одна строка)
Hexagonal (ports & adapters). Ядро `contract_check.core` владеет всем состоянием
(Postgres/MinIO/RabbitMQ/LLM/кредиты/биллинг). Семь сервисов собираются из него:
```
Telegram ──► bot (aiogram, HTTP-only) ──HTTP──► api (FastAPI) ──publish──► RabbitMQ
владеет: PG, MinIO, │
Redis, кредитами ▼
extract.q ──► worker-extract
(pymupdf/mammoth/tesseract, CPU) │
▼ publish
prescreen.q ──► worker-prescreen
(гибридные метаданные + роутинг) │
┌──────────────────────────────────────┤
▼ deep_analysis manual_review
analyze.q ──► worker-analyze │
(LLM, I/O) → Report │
notify.q ──► worker-notify (SMTP: password reset, magic link и др.)
```
- **api** — единственный писатель для пользовательских мутаций (upload → reserve credit → MinIO → publish `DocumentUploaded`).
- **worker-extract** — CPU: достаёт текст (PDF/DOCX/RTF/TXT/CSV, OCR для изображений; детект формата через python-magic), грузит извлечённый текст в MinIO, публикует `PrescreenRequested` (если `PRESCREEN_ENABLED=true`) или сразу `DocumentExtracted` в `analyze.q`.
- **worker-prescreen** — гибридное извлечение метаданных договора (Stage 1 эвристика + Stage 2 LLM при низкой уверенности), роутинг: `deep_analysis` → публикует `AnalyzeRequested` в `analyze.q`; `manual_review` — терминальный статус; `auto_approve` — записывает лёгкий `Report`, `status=done`. Пишет в `prescreen_results`.
- **worker-analyze** — I/O: LLM-анализ по чек-листу, валидация/repair, сохраняет `Report`, `status=done`.
- **worker-notify** — доставка email-уведомлений (восстановление пароля и др.) через SMTP; без `SMTP_HOST` — dev-логгер.
- **worker-billing** — таймерный планировщик биллинга (без MQ): продления подписок (счета за 3 дня до конца периода), expiry/past_due-переходы, сверка pending-счетов старше 15 минут через ЮKassa API.
- **bot** — адаптер: HTTP-клиент к api, **не импортирует** core.db/s3/llm/mq (граница проверяется тестом `tests/unit/test_bot_boundary.py`). Может работать на отдельном сервере — см. `docs/DEPLOY.md` §14.
## Структура репозитория
```
src/contract_check/
core/ # общий домен (импортируется каждым сервисом)
config.py logging.py telemetry.py sentry.py metrics.py errors.py
credits.py tokens.py api_keys.py rate_limit.py redis_client.py
auth.py auth_refresh.py auth_refresh_key.py passkeys.py
db/ models.py session.py enums.py repositories/
mq/ topology.py publisher.py consumer.py messages.py
s3/ port.py minio_storage.py
llm/ port.py factory.py ollama_cloud.py yandex_gpt.py prescreen.py errors.py
billing/ port.py yookassa.py errors.py quota.py fulfillment.py refunds.py
extraction/ port.py factory.py formats.py + adapters/
(pdf_pymupdf, docx_mammoth, rtf_striprtf, txt_chardet, ocr_tesseract)
analysis/ extractor.py chunker.py checklist.py report_schema.py ocr.py analyzer.py
notifications/ transport.py publisher.py # SMTP + dev-log
security/ passwords.py # argon2
api/ # FastAPI-образ
app.py deps.py middleware.py services.py __main__.py
admin/ # серверный UI по /admin (login, users, счета, возвраты, hold)
billing.py users.py auth.py router.py templating.py templates/
billing/ # token-gated HTML pay-страницы (/pay/{id})
routes/ # префикс /api/v1
__init__.py health.py documents.py reports.py me.py metrics.py b2b.py billing.py webhooks.py
auth/ # Telegram, email/password, passkeys, magic links
__init__.py telegram.py password.py passkeys.py magic_links.py support.py
schemas/ # Pydantic-схемы запросов/ответов
auth.py b2b.py billing.py common.py documents.py me.py profile.py reports.py
worker_extract/ # CPU-образ (pymupdf + mammoth + tesseract)
consumer.py handler.py extract_document.py __main__.py
worker_prescreen/ # прескрин-образ (гибрид: эвристика + LLM)
consumer.py handler.py router.py config.py
extractor.py extractor_heuristic.py extractor_llm.py extractor_hybrid.py
worker_analyze/ # I/O-образ (LLM provider)
consumer.py handler.py __main__.py
worker_notify/ # email-уведомления (aiosmtplib)
consumer.py handler.py __main__.py
worker_billing/ # планировщик биллинга (продления, expiry, сверка)
scheduler.py __main__.py
bot/ # aiogram-адаптер (самый «тощий» образ: только core.logging)
client.py config.py handlers.py rate_limit.py __main__.py
docs/ # документация проекта (README остаётся в корне)
srv/ # Dockerfile-ы (один на сервис, deps заточены)
api/ worker-extract/ worker-prescreen/ worker-analyze/ worker-notify/ worker-billing/ bot/
migrations/ # alembic (async): 0001_initial … 0011_user_profiles_billing
tests/
conftest.py
unit/ chunker, extractor, extraction_factory/adapters, llm_ollama_cloud,
llm_yandex_gpt, llm_prescreen_extraction, prescreen_extractor(+heuristic/
hybrid/llm), prescreen_router, credits, messages, rate_limit,
checklist_report, auth, web_auth, passkeys, extract_handler,
bot_client, bot_boundary, refunds, worker_billing
integration/ upload_pipeline, extract_worker, prescreen_worker, analyze_worker,
b2b_api, credits_db, auth_flow, passkeys_magic_link, admin_panel,
profile_api, b2b_profile, billing_checkout, webhook_yookassa,
pay_page, subscription_purchase, quota_reservation, refunds,
admin_billing
Makefile # повседневные команды (make help)
docker-compose.yml # default = инфра; --profile services = стек; --profile edge = nginx+certbot
pyproject.toml # hatchling + PEP 735 dependency-groups (db/mq/s3/obs/api/extract/prescreen/analyze/notify/billing/bot/dev)
.env.example # полный список env (см. docs/ARCHITECTURE.md §11)
```
## Быстрый старт
### Локально (разработка)
```bash
uv sync --group dev # все группы для локальной разработки
cp .env.example .env # впишите OLLAMA_* / YANDEXGPT_* / JWT_SECRET / BOT_TOKEN
docker compose up -d # только инфра (postgres/redis/rabbitmq/minio)
uv run alembic upgrade head # миграции
uv run python -m contract_check.api # api на :8000
uv run python -m contract_check.worker_extract # воркер экстракции
uv run python -m contract_check.worker_prescreen # воркер прескрина
uv run python -m contract_check.worker_analyze # воркер анализа
uv run python -m contract_check.worker_notify # воркер уведомлений
uv run python -m contract_check.worker_billing # планировщик биллинга
uv run python -m contract_check.bot # Telegram-бот
```
### Через Docker Compose / Make
```bash
cp .env.example .env # заполнить секреты (DB, Rabbit, MinIO, LLM, BOT_TOKEN, JWT_SECRET, ...)
docker compose up -d # только инфра с healthchecks
docker compose --profile services up -d --build # + api, worker-extract, worker-prescreen, worker-analyze, worker-notify, worker-billing (без бота)
docker compose --profile bot up -d --build # + bot (можно запускать отдельно или на другом хосте)
```
Или через `make`: `make dev` (install + infra + migrate + api + workers + bot), `make help` — полный список
целей (lint, typecheck, test, seed-token, jwt-token, admin-promote, логи/шеллы сервисов и т.п.).
Профили:
- `services` — api + worker-ы (без бота).
- `bot` — Telegram-бот; можно поднять на этом же хосте или на отдельном сервере (`docs/DEPLOY.md` §14).
- `edge` — Nginx + certbot (`deploy/nginx/`, `docs/DEPLOY.md` §13).
- `obs` / `observer` — observability (Grafana/Loki/Prometheus или OpenObserve + Vector).
Порты на хосте (смещены, чтобы не конфликтовать): Postgres `15432`, Redis `17379`,
RabbitMQ AMQP `5672` / UI `15672`, MinIO `9000` / console `9001`, api `8000` / metrics `9100`
(по умолчанию только на loopback), OpenObserve UI `5080`, edge `80`/`443`.
Метрики worker-ов больше не публикуются на хост; их скрейпит Vector внутри сети compose.
## API (кратко)
JSON-роуты под `/api/v1`; серверный admin UI — по `/admin/*` (FastAPI + Jinja2 + HTMX). Auth зависит от роута:
- **Пользовательские роуты** (bot / web / Mini App) — `Authorization: Bearer <user_jwt>`. Telegram-JWT выдаётся через `/api/v1/auth/telegram/*`; web — JWT-пара (access + refresh, refresh отзывается через Redis); passwordless (пасскей / магическая ссылка) — одиночный access JWT.
- **Адаптер-level** (только `/api/v1/auth/telegram/bot`) — `Authorization: Bearer <service_token>`.
- **B2B** — `X-API-Key`. Управление B2B-ключами требует пользовательский JWT.
- **Health/metrics** — без auth.
- **/admin** — HttpOnly cookie с user JWT + `users.role = admin` (или `ADMIN_REQUIRED_ROLE`).
| Метод | Путь | Auth | Назначение |
|---|---|---|---|
| GET | `/healthz`, `/readyz`, `/metrics` | — | liveness / readiness / Prometheus |
| POST | `/api/v1/auth/telegram/bot` | service token | бот меняет verified `telegram_id` на JWT |
| POST | `/api/v1/auth/telegram/web` | — | Telegram Login Widget → JWT |
| POST | `/api/v1/auth/telegram/miniapp` | — | Mini App `initData` → JWT |
| POST | `/api/v1/auth/register` | — | регистрация email/password → JWT-пара |
| POST | `/api/v1/auth/login` | — | вход email/password → JWT-пара |
| POST | `/api/v1/auth/logout` | — | отзыв refresh-токена |
| POST | `/api/v1/auth/forgot-password` | — | reset-токен (хранится хэшем) + уведомление через notify.q |
| POST | `/api/v1/auth/reset-password` | — | задать новый пароль, отозвать все refresh |
| POST | `/api/v1/auth/passkeys/register/start`, `.../finish` | user JWT | регистрация пасскея (WebAuthn, challenge в Redis) |
| POST | `/api/v1/auth/passkeys/authenticate/start`, `.../finish` | — | вход по пасскею (discoverable) → access JWT |
| GET/DEL | `/api/v1/auth/passkeys`, `/api/v1/auth/passkeys/{id}` | user JWT | список / удаление пасскеев |
| POST | `/api/v1/auth/magic-link/request` | — | одноразовая ссылка входа на email (через notify.q) |
| POST | `/api/v1/auth/magic-link/verify` | — | обмен токена из письма на access JWT |
| GET | `/api/v1/auth/me`, `/api/v1/auth/me/permissions` | user JWT | introspect JWT / права |
| POST | `/api/v1/documents` | user JWT | multipart upload → reserve credit → MinIO → publish → `202 {document_id, correlation_id}` |
| GET | `/api/v1/reports/{document_id}` | user JWT | поллинг: `{status, stage}` или готовый `200 {markdown, findings, ...}` |
| GET | `/api/v1/me` | user JWT | профиль + `credits_left` |
| GET/PATCH | `/api/v1/me/profile` | user JWT | пассивные настройки профиля (язык, TZ, уведомления, дашборд) |
| GET | `/api/v1/me/overview` | user JWT | дашборд: документы, кредиты, план/квота, `billing_hold` |
| GET | `/api/v1/me/documents` | user JWT | список документов пользователя |
| POST | `/api/v1/me/telegram`, `/api/v1/me/password` | user JWT | привязка Telegram / смена пароля |
| GET | `/api/v1/billing/plans` | — | каталог тарифов + цена за документ |
| POST | `/api/v1/billing/checkout` | user JWT | топ-ап кредитов или покупка подписки → `201 {invoice_id, confirmation_url}` |
| GET | `/api/v1/billing/invoices`, `/api/v1/billing/invoices/{id}` | user JWT | история / свой счёт |
| GET | `/api/v1/billing/subscription` | user JWT | текущая подписка (active/past_due) |
| POST | `/api/v1/billing/subscriptions/autorenew` | user JWT | вкл/выкл автопродление (по умолчанию OFF) |
| POST | `/api/v1/billing/refund` | user JWT | авто-возврат по правилу D10; `409` если не возвращаем |
| POST | `/api/v1/webhooks/yookassa` | Basic (shopId:secret) | уведомления ЮKassa; платёж перезапрашивается через REST |
| GET | `/pay/{invoice_id}?token=…` | подписанный JWT | HTML-страница статуса счёта после оплаты |
| GET/PUT | `/api/v1/b2b/profile` | `X-API-Key` | настройки профиля партнёра (без биллинга) |
| POST | `/api/v1/analyze` | `X-API-Key` | B2B: анализ документа |
| GET | `/api/v1/b2b/reports/{id}`, `/api/v1/b2b/usage` | `X-API-Key` | B2B: отчёт / usage |
| POST/GET | `/api/v1/b2b/keys`, `/api/v1/b2b/keys/{id}/revoke`, `.../usage` | user JWT | управление B2B-ключами |
| GET/POST | `/admin/*` | admin cookie | login/logout + пользователи (list, create, edit, ban, role, credits, verify-telegram), счета (`/admin/invoices`), возвраты, снятие billing-hold, gift-подписки |
Биллинг-флаги: `PLANS_ENABLED` (квоты подписок; `false` = только кредиты),
`YOOKASSA_ENABLED` (платежи; `false` → мутации биллинга `503`, каталог читается).
Детали: `docs/ARCHITECTURE.md §8a`, настройка ЮKassa — `docs/DEPLOY.md §4a`.
Полная спецификация роутов (схемы запросов/ответов, коды ошибок) —
[`src/contract_check/api/routes/README.md`](src/contract_check/api/routes/README.md);
высокоуровневая поверхность — `docs/ARCHITECTURE.md §15` (актуализируется).
## Проверки (DoD)
```bash
uv run ruff check src tests && uv run ruff format --check src tests # lint (или make lint)
uv run ty check src # типы (или make typecheck)
uv run pytest -m "not integration" -q # unit, быстро
uv run pytest -m integration -q # интеграционные (нужны контейнеры)
```