DealDocumentScreening/README.md
2026-08-24 01:00:11 +03:00

203 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.

# Контракт-чек
LLM-сервис скрининга рисков в договорах по ГК РФ / ГК РБ.
Telegram-бот + web-аутентификация (email/password, JWT-пара; пасскеи и магические ссылки — passwordless) + B2B API сейчас; подписки — позже.
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/IMPLEMENTATION_PLAN.md`](docs/IMPLEMENTATION_PLAN.md) — исходный «ленивый» план. Этап 0 (прототип) актуален; этапы 1+ superseded в `docs/ARCHITECTURE.md`.
- [`docs/TICKETS.md`](docs/TICKETS.md) — тикеты реализации со статусами.
- [`docs/PHASE2_HANDOFF.md`](docs/PHASE2_HANDOFF.md), [`docs/PHASES_2_PLUS_ROADMAP.md`](docs/PHASES_2_PLUS_ROADMAP.md), [`docs/PRESCREEN_HYBRID_REFACTOR_PLAN.md`](docs/PRESCREEN_HYBRID_REFACTOR_PLAN.md) — этап 2: prescreen-стейдж и дальнейший роадмап.
- [`docs/SPIKE_PHASE0.md`](docs/SPIKE_PHASE0.md) — спайки: prescreen-библиотеки, RustFS (артефакты в `rustfs-spike/`).
## Архитектура (одна строка)
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-логгер.
- **bot** — адаптер: HTTP-клиент к api, **не импортирует** core.db/s3/llm/mq (граница проверяется тестом `tests/unit/test_bot_boundary.py`).
## Структура репозитория
```
src/contract_check/
__main__.py # указывает на prototype (stage-0 CLI сохранён)
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
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; позже — подписки)
routes/ # префикс /api/v1
__init__.py health.py documents.py reports.py me.py metrics.py b2b.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 common.py documents.py me.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
bot/ # aiogram-адаптер (самый «тощий» образ: только core.logging)
client.py config.py handlers.py rate_limit.py __main__.py
prototype/ # stage-0 standalone CLI (бенчмарк go/no-go)
docs/ # документация проекта (README остаётся в корне)
srv/ # Dockerfile-ы (один на сервис, deps заточены)
api/ worker-extract/ worker-prescreen/ worker-analyze/ worker-notify/ bot/ prototype/
migrations/ # alembic (async): 0001_initial … 0010_passkeys_magic_links
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
integration/ upload_pipeline, extract_worker, prescreen_worker, analyze_worker,
b2b_api, credits_db, auth_flow, passkeys_magic_link, admin_panel
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/bot/prototype/dev)
.env.example # полный список env (см. docs/ARCHITECTURE.md §11)
rustfs-spike/ # артефакты спайка RustFS (docs/SPIKE_PHASE0.md)
```
## Быстрый старт
### Локально (разработка)
```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.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, bot
```
Или через `make`: `make dev` (install + infra + migrate + services), `make help` — полный список
целей (lint, typecheck, test, seed-token, jwt-token, admin-promote, логи/шеллы сервисов и т.п.).
Профиль `services` собирает 6 образов из `srv/<service>/Dockerfile` и поднимает их
с `depends_on: condition: service_healthy`. Edge-прокси (Nginx + certbot) доступен
профилем `edge` (`deploy/nginx/`, `docs/DEPLOY.md §13`). Observability (Prometheus/Grafana/Tempo/OTel)
— за будущим профилем `obs`.
Порты на хосте (смещены, чтобы не конфликтовать): Postgres `15432`, Redis `17379`,
RabbitMQ AMQP `5672` / UI `15672`, MinIO `9000` / console `9001`, api `8000` / metrics `9100`,
worker metrics: extract `9101`, analyze `9102`, notify `9103`, prescreen `9104`, edge `80`/`443`.
### Stage-0 прототип (бенчмарк)
```bash
uv sync --group prototype
uv run python -m contract_check prototype contract.pdf # отчёт в stdout
uv run contract-check contract.pdf -o report.md # или консольная команда
uv run python -m contract_check prototype contract.pdf --json metrics.json # + метрики go/no-go
```
## 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 | `/api/v1/me/documents` | user JWT | список документов пользователя |
| POST | `/api/v1/me/telegram`, `/api/v1/me/password` | user JWT | привязка Telegram / смена пароля |
| 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) |
Полная спецификация роутов (схемы запросов/ответов, коды ошибок) —
[`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 # интеграционные (нужны контейнеры)
```