diff --git a/History/2026-08-26-sonnet-code-review.md b/History/2026-08-26-sonnet-code-review.md new file mode 100644 index 0000000..843764a --- /dev/null +++ b/History/2026-08-26-sonnet-code-review.md @@ -0,0 +1,73 @@ +# Sonnet: код-ревью contracts-flask (2026-08-26) + +## Контекст + +- Написан промпт для Sonnet: `contracts-flask/docs/prompt-sonnet-architecture.md` + (задача — прочитать файлы текущего сервиса сверки и дать описание/архитектуру). +- Sonnet прочитал все указанные файлы и вместо описания выдал **код-ревью** с 10 находками + (2 критические, 3 средних, 2 XSS, 3 мелких). Архитектурное описание — отдельным документом (не выдано). + +## Находки Sonnet + +### 🔴 Критические + +1. **`process.py` + `spec_events.py` — UPDATE/DELETE никогда не применяются (сломана частичная сверка).** + LLM возвращает `target_id: "r1"` (индекс строки), а `apply_ops()` читает `op.get("target_hash", "")`. + Поля `target_hash` в ответе LLM нет → все UPDATE/DELETE из `mode=partial` молча уходят в `UNRESOLVED`. + Трассировка: `_build_spec_text()` показывает строки `[id: r1]` → LLM возвращает `target_id: "r1"` → + `process.py` передаёт ops без трансляции r1→hash → `spec_events.py` `th = op.get("target_hash","")` = "" → UNRESOLVED. + Работает только `mode=full_replace` (все ADD) и первый документ (extract, только ADD). + +2. **`Dockerfile` — образ не запустится: `upload/` не скопирован.** + `COPY site /app/site` — только site/, `upload/` отсутствует. + При старте `routes/__init__.py` делает `from upload.backend.upload_refs import ...`, `app.py` добавляет `/app` в sys.path. + В образе `/app/upload/` нет → `ModuleNotFoundError`. + +### 🟠 Средние + +3. **`db/prompts.py` — `list_by_role()` запрашивает несуществующий столбец `created_by`.** + В схеме `prompts` нет `created_by` → `sqlite3.OperationalError` → `/api/prompts/list` всегда 500. + +4. **`routes/prompts_bp.py` — вызов несуществующих функций:** + - `db_prompts.insert(...)` — нет (есть `save_new_version()`); + - `db_prompts.delete(...)` — нет (есть `delete_prompt()`). + `/api/prompts/save` и `/api/prompts/delete` падают с `AttributeError`. + +5. **`services/llm.py`, `services/classify.py` — захардкожены `LLM_URL/LLM_KEY/LLM_MODEL` в обход `config.py`.** + `config.py` читает из `LLM_API_URL`, а compare/classify игнорируют его. Конфигурация расщеплена на 3 блока. + +### 🟡 XSS (frontend) + +6. **`compare.js` — `nr.name` в таблице операций не экранирован** (`escHtml()` отсутствует). + Имя услуги из договора (текст LLM) вставляется в innerHTML → XSS. + +7. **`compare.js` — `sec.filename` в заголовке секции не экранирован.** Имя файла от пользователя → XSS. + +### ⚪ Мелкие + +8. **`llm_prompt.py`** — устаревший комментарий «Lucee API» (читает напрямую из SQLite). +9. **`db/connection.py`** — `_pg_to_sqlite`: `gen_random_uuid()` добавляет `?` в SQL, но не добавляет значение в params (мёртвый код, но при исполнении сломает запрос). +10. **`services/classify.py`** — комментарий «re-classify after file changes» вводит в заблуждение: `reset_classify_status()` сбрасывает только `processing`→`pending`, уже классифицированные не трогает. + +## Итоговая таблица + +| # | Файл | Проблема | Критичность | +|---|---|---|---| +| 1 | process.py, spec_events.py | UPDATE/DELETE → UNRESOLVED, частичная сверка сломана | 🔴 критический | +| 2 | Dockerfile | upload/ не скопирован → образ не стартует | 🔴 критический | +| 3 | db/prompts.py | created_by отсутствует → 500 на /api/prompts/list | 🟠 средний | +| 4 | prompts_bp.py | insert()/delete() — не те имена функций → 500 | 🟠 средний | +| 5 | llm.py, classify.py | захардкожены LLM_URL/KEY/MODEL в обход config.py | 🟠 средний | +| 6 | compare.js | nr.name не экранирован → XSS | 🟡 XSS | +| 7 | compare.js | sec.filename не экранирован → XSS | 🟡 XSS | +| 8 | llm_prompt.py | устаревший комментарий | ⚪ мелкое | +| 9 | connection.py | gen_random_uuid() мёртвый код с ошибкой | ⚪ мелкое | +| 10 | classify.py | комментарий re-classify вводит в заблуждение | ⚪ мелкое | + +## Статус + +- ✅ Все 10 находок исправлены (v2.0.15, commit `e3f5581`, запушено). +- #1 (критично) — сам: `process.py` трансляция `target_id`→`target_hash`. +- #2–#10 (механика) — Флаш-субагент: Dockerfile, prompts CRUD, XSS, конфиг LLM, комментарии. +- Верифицировано: `py_compile`, `node -c`, `import app` — OK. +- Архитектурное описание от Sonnet — так и не получено (вместо него — ревью). Если нужно — отдельным запросом. diff --git a/docs/prompt-sonnet-architecture.md b/docs/prompt-sonnet-architecture.md new file mode 100644 index 0000000..7a3a882 --- /dev/null +++ b/docs/prompt-sonnet-architecture.md @@ -0,0 +1,104 @@ +# Задание для Sonnet: описание архитектуры сервиса «Сверка договоров» + +## Роль и задача + +Ты — технический аналитик. Твоя задача — **только прочитать указанные файлы** и написать +подробное, структурированное описание сервиса «Сверка договоров»: что это за сервис, как +устроен, как работает каждый этап, какими методами и технологиями. **Ничего не менять и +не создавать в коде.** Результат — один текстовый документ (markdown). + +## Короткий контекст (чтобы ты понимал, о чём речь) + +Сервис «Сверка договоров» — веб-приложение на **Flask + vanilla JS** (без фреймворков +на фронте), развёрнутое на managed-кластере Nubes (Штурвал). Пользователь загружает +договоры и допсоглашения (docx/pdf/zip), сервис: +1. разбирает их на структурированные спецификации; +2. автоматически классифицирует документы (тип/номер/дата/контрагент) через LLM; +3. группирует документы по договорам; +4. по цепочке допников сравнивает изменения спецификации (ADD/UPDATE/DELETE) через LLM; +5. показывает историю изменений и даёт возможность задать вопрос чату по итоговой спецификации. + +Ключевое ограничение: данные конфиденциальны, LLM — только своя (api.aillm.ru, модель +gpt-oss-120b). Большие файлы не грузятся напрямую в кластер (шлюз рвёт тела >~64 КБ) — +используется «ВМ-буфер»: браузер кладёт файл на ВМ (WebDAV), а бэк сам тянет его +исходящим запросом (egress не ограничен). + +## Что прочитать — ТОЛЬКО эти файлы (корень проекта: contracts-flask/) + +### Ядро бэкенда (обязательно, читать все) +- `site/app.py` — точка входа, фабрика приложения, sys.path +- `site/config.py` — конфигурация (версия, LLM_URL/KEY/MODEL, CONVERT_SERVICE_URL, VM_UPLOAD_*) +- `site/llm_prompt.py` — формирование промптов LLM (извлечение/сравнение/классификация) +- `site/routes/__init__.py` — регистрация всех blueprint'ов +- `site/routes/upload_bp.py` — загрузка файлов (multipart + sink закачки через ВМ) +- `site/routes/pipeline_bp.py` — классификация + SSE-сравнение (/process-v2, /api/classify-batch) +- `site/routes/api_bp.py` — API (groups, documents, supplements, sync, cleanup, spec-current, chat) +- `site/routes/pages_bp.py` — HTML-страницы + раздача ES-модулей upload/ +- `site/routes/prompts_bp.py` — CRUD и версионирование промптов +- `site/routes/health_bp.py` — healthcheck +- `site/services/parse.py` — парсинг PDF (pdfplumber), DOCX (python-docx), TXT +- `site/services/classify.py` — LLM-классификация документов (garbage-фильтры, выжимка, ThreadPool) +- `site/services/grouping.py` — нормализация номеров и группировка по договорам +- `site/services/process.py` — SSE-пайплайн сравнения (run_pipeline) +- `site/services/llm.py` — вызов LLM для сравнения (call_llm) +- `site/services/llm_client.py` — LLM-клиент (HttpxLLMClient, FakeLLMClient для тестов) +- `site/services/metrics.py` — проверка арифметики (sum == price*qty), метрики качества +- `site/db/connection.py` — SQLite: схема всех таблиц, WAL, thread-local соединения +- `site/db/documents.py` — CRUD документов (статусы, классификация, elements_json) +- `site/db/contracts.py` — договоры +- `site/db/supplements.py` — допники (связь contract↔document) +- `site/db/spec_events.py` — event sourcing: apply_ops (ADD/UPDATE/DELETE), reset +- `site/db/spec_current.py` — текущее состояние спецификации договора +- `site/db/prompts.py` — версии промптов по ролям + +### Фронтенд (для понимания потока на стороне браузера) +- `site/static/state.js` — центральное состояние приложения +- `site/static/app.js` — инициализация, обработчики загрузки/классификации/сравнения +- `site/static/files.js` — выбор файлов/папок/архивов, загрузка через ВМ-буфер +- `site/static/groups.js` — классификация и группы на фронте +- `site/static/compare.js` — сравнение (SSE) и рендер результатов +- `site/templates/index.html` — разметка UI + +### Переиспользуемый модуль загрузки через ВМ (модуль upload/) +- `upload/README.md` — описание модуля и паттерна «ВМ-буфер + pull» +- `upload/backend/upload_refs/blueprint.py` — blueprint POST /api/upload_refs с параметром sink +- `upload/backend/upload_refs/safe_name.py`, `pull_file.py`, `config.py` — транспортные примитивы +- `upload/backend/session/__init__.py` — in-memory сессия (используется drhider; сверка его не использует) + +### Инфраструктура +- `Dockerfile` — сборка образа +- `requirements.txt` — зависимости + +## Что НЕ читать и не трогать + +- `History/` — вся история сессий, ревью, старые планы (НЕ нужно) +- `contractor-legacy/` — замороженный Lucee-прод (не актуален) +- `contracts-vm/`, `convert-service/`, `loadtest/`, `testgen/`, `sim/`, `dogovora/`, `hz/`, `FILES/`, `DOC/` — смежные/вспомогательные, НЕ ядро сервиса +- `deploy/` — устаревший VM-слой (convert_server.py и т.п.), не входит в текущий managed-сервис +- `site/services/drhider.py` — совместимость с DrHider (побочное, не ядро сверки) +- `site/templates/drhider.html`, `architect.html`, `pipeline.html`, `ci-cd.html` — не ядро сверки + +## Что должно быть в ответе (структура) + +1. **Описание сервиса** — что делает, кому, какие входы/выходы (2-4 абзаца, по-простому). +2. **Общая архитектура** — слои (фронт / бэк / БД / LLM / ВМ-буфер), как они связаны. + Лучше — схема потока: загрузка → парсинг → классификация → группировка → сравнение → чат. +3. **Структура проекта** — дерево `site/` (+ `upload/`) с назначением каждого файла. +4. **Как работает каждый этап** (по пунктам, «что делает / как / где в коде»): + - загрузка файлов (включая папки/архивы, ВМ-буфер, sink); + - парсинг PDF/DOCX/TXT → elements (таблицы + параграфы); + - классификация (garbage-фильтры, выжимка, LLM, параллельность); + - группировка (нормализация номеров, виртуальные группы, __unresolved__); + - сравнение (SSE-пайплайн, ops ADD/UPDATE/DELETE, event sourcing, spec_current); + - чат по итоговой спецификации. +5. **Технологии и методы** — коротко: SQLite (WAL, thread-local), SSE, ThreadPool, LLM-клиент, + промпты (из БД + fallback), event sourcing, метрики качества, ВМ-буфер загрузки. +6. **Модель данных** — таблицы БД и их назначение (documents, contracts, supplements, + spec_events, spec_current, prompts). + +## Требования к стилю + +- Чётко, структурно, без воды и лирики. +- Без излишней заумности — понятно не только девопсу, но и обычному разработчику/аналитику. +- Каждый этап: «что делает → как → каким методом → где в коде (файл/функция)». +- Ничего не менять в коде. Только текстовый документ. diff --git a/docs/prompt-sonnet-description-v2.md b/docs/prompt-sonnet-description-v2.md new file mode 100644 index 0000000..7dc2723 --- /dev/null +++ b/docs/prompt-sonnet-description-v2.md @@ -0,0 +1,101 @@ +# Задание для Sonnet (v2): описание архитектуры сервиса «Сверка договоров» + +## Роль и задача + +Ты — технический аналитик. Задача — **только прочитать указанные файлы** и написать +подробное, структурированное описание сервиса «Сверка договоров»: что это, как устроено, +как работает каждый этап, какими методами/технологиями. **Ничего не менять в коде.** +Результат — один текстовый документ (markdown). + +## Контекст (что уже было) + +Ранее ты выдал **код-ревью** с 10 находками (вместо описания). Все 10 уже **исправлены** +(v2.0.15): критичный баг `target_id` vs `target_hash` (частичная сверка), Dockerfile, +prompts CRUD, LLM-конфиг, XSS в `compare.js`, мелочи. Учти это — читай код **после** правок, +описывай **текущее** состояние. + +## Сервис в двух словах + +«Сверка договоров» — веб-приложение на **Flask + vanilla JS** (без фреймворков на фронте), +на managed-кластере Nubes. Пользователь загружает договоры/допсоглашения (docx/pdf/zip), +сервис: разбирает их в спецификации → классифицирует документы через LLM → группирует +по договорам → по цепочке допников сравнивает изменения (ADD/UPDATE/DELETE) через LLM → +показывает историю + чат по итоговой спецификации. Данные конфиденциальны, LLM — только +своя (api.aillm.ru, gpt-oss-120b). Большие файлы грузятся через «ВМ-буфер» (браузер PUT на +WebDAV → бэк тянет egress GET), т.к. шлюз кластера рвёт тела >~64 КБ. + +## Что прочитать — ТОЛЬКО эти файлы (корень: contracts-flask/) + +### Ядро бэкенда +- `site/app.py` — точка входа, фабрика приложения, sys.path +- `site/config.py` — конфигурация (версия, LLM, конвертер .doc, VM_UPLOAD_*) +- `site/llm_prompt.py` — формирование промптов LLM (extract/diff/classify + fallback) +- `site/routes/__init__.py` — регистрация blueprint'ов +- `site/routes/upload_bp.py` — загрузка (multipart + sink закачки через ВМ) +- `site/routes/pipeline_bp.py` — классификация + SSE-сравнение (/process-v2, /api/classify-batch) +- `site/routes/api_bp.py` — API (groups, documents, supplements, sync, cleanup, spec-current, chat) +- `site/routes/pages_bp.py` — HTML-страницы + раздача ES-модулей upload/ +- `site/routes/prompts_bp.py` — CRUD и версионирование промптов +- `site/routes/health_bp.py` — healthcheck +- `site/services/parse.py` — парсинг PDF (pdfplumber), DOCX (python-docx), TXT +- `site/services/classify.py` — LLM-классификация (garbage-фильтры, выжимка, ThreadPool) +- `site/services/grouping.py` — нормализация номеров и группировка по договорам +- `site/services/process.py` — SSE-пайплайн сравнения (run_pipeline) + трансляция target_id→hash +- `site/services/llm.py` — вызов LLM для сравнения (call_llm) +- `site/services/llm_client.py` — LLM-клиент (HttpxLLMClient, FakeLLMClient) +- `site/services/metrics.py` — проверка арифметики (sum == price*qty), метрики качества +- `site/db/connection.py` — SQLite: схема, WAL, thread-local +- `site/db/documents.py` — CRUD документов +- `site/db/contracts.py` — договоры +- `site/db/supplements.py` — допники (contract↔document) +- `site/db/spec_events.py` — event sourcing: apply_ops (ADD/UPDATE/DELETE), reset +- `site/db/spec_current.py` — текущее состояние спецификации +- `site/db/prompts.py` — версии промптов + +### Фронтенд +- `site/static/state.js` — центральное состояние +- `site/static/app.js` — инициализация, обработчики +- `site/static/files.js` — выбор файлов/папок/архивов, загрузка через ВМ +- `site/static/groups.js` — классификация и группы на фронте +- `site/static/compare.js` — сравнение (SSE) и рендер +- `site/templates/index.html` — UI + +### Модуль upload/ (переиспользуемый) +- `upload/README.md` — паттерн «ВМ-буфер + pull» +- `upload/backend/upload_refs/blueprint.py` — blueprint POST /api/upload_refs с sink +- `upload/backend/upload_refs/safe_name.py`, `pull_file.py`, `config.py` +- `upload/backend/session/__init__.py` — in-memory сессия (drhider; сверка не использует) + +### Инфраструктура +- `Dockerfile`, `requirements.txt` + +## Что НЕ читать + +`History/`, `contractor-legacy/`, `contracts-vm/`, `convert-service/`, `loadtest/`, +`testgen/`, `sim/`, `dogovora/`, `hz/`, `FILES/`, `DOC/`, `deploy/` (устаревший VM-слой), +`site/services/drhider.py`, `site/templates/drhider.html|architect.html|pipeline.html|ci-cd.html`. + +## Что должно быть в ответе (структура) + +1. **Описание сервиса** — что делает, входы/выходы (2–4 абзаца, по-простому). +2. **Общая архитектура** — слои (фронт/бэк/БД/LLM/ВМ-буфер) + схема потока: + загрузка → парсинг → классификация → группировка → сравнение → чат. +3. **Структура проекта** — дерево `site/` (+ `upload/`) с назначением каждого файла. +4. **Как работает каждый этап** — «что делает → как → каким методом → где в коде (файл/функция)». +5. **Технологии и методы** — SQLite (WAL, thread-local), SSE, ThreadPool, LLM-клиент, + промпты (БД + fallback), event sourcing, метрики качества, ВМ-буфер. +6. **Модель данных** — таблицы и назначение (documents, contracts, supplements, + spec_events, spec_current, prompts). + +## Дополнительно: точечная проверка фикса #1 + +Отдельно, коротко (3–5 строк): в `site/services/process.py` глянь блок трансляции +`target_id ("r1","r2"…) → target_hash` (через `current_spec[_idx]["hash"]`). Подтверди: +корректно ли он маппит `rN` (1-based) на индекс `current_spec`, и что при невалидном id +операция уходит в UNRESOLVED, а не падает. Если видишь ошибку — укажи, но НЕ правь. + +## Требования к стилю + +- Чётко, структурно, без воды и лирики. +- Без излишней заумности — понятно не только девопсу, но и обычному разработчику/аналитику. +- Ничего не менять в коде. Только текстовый документ.