docs: expanded architect — security, prompts editor, document cleanup, engineering practices, testing, isolation v1.0.184
Deploy contracts-flask / validate (push) Successful in 0s
Deploy contracts-flask / validate (push) Successful in 0s
This commit is contained in:
+195
-13
@@ -163,16 +163,70 @@
|
|||||||
|
|
||||||
<hr>
|
<hr>
|
||||||
|
|
||||||
<h2>5. Защита и валидация <span class="tag tag-warn">валидация</span></h2>
|
<h2>5. Защита от дурака и злонамеренности <span class="tag tag-warn">защита</span></h2>
|
||||||
|
|
||||||
<ul>
|
<table style="width:100%;border-collapse:collapse;margin:12px 0;border:1px solid var(--border)">
|
||||||
<li><code>ADD</code> без имени услуги → <code>UNRESOLVED</code> (не применяется, помечается для ручной проверки)</li>
|
<tr style="background:var(--code-bg)">
|
||||||
<li><code>UPDATE</code>/<code>DELETE</code> с пустым идентификатором → <code>UNRESOLVED</code></li>
|
<th style="padding:8px;text-align:left;border:1px solid var(--border)">Угроза</th>
|
||||||
<li><strong>Арифметика:</strong> <code>sum = price × qty</code> — расхождение → флаг</li>
|
<th style="padding:8px;text-align:left;border:1px solid var(--border)">Защита</th>
|
||||||
<li><strong>Дубликаты:</strong> <code>content_hash</code> — один и тот же файл дважды не обрабатывается</li>
|
</tr>
|
||||||
<li><strong>Даты:</strong> <code>01.03.2026</code> → <code>2026-03-01</code> (авто-нормализация)</li>
|
<tr>
|
||||||
<li><strong>Числа:</strong> <code>1 000,50</code> → <code>1000.50</code> (авто-нормализация)</li>
|
<td style="padding:8px;border:1px solid var(--border)">ZIP-бомба (архив 1 KB → 10 GB)</td>
|
||||||
</ul>
|
<td style="padding:8px;border:1px solid var(--border)">Лимит 500 файлов, 500 MB, ratio сжатия ≤ 100×</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td style="padding:8px;border:1px solid var(--border)">Path traversal в ZIP (<code>../../etc/passwd</code>)</td>
|
||||||
|
<td style="padding:8px;border:1px solid var(--border)">Проверка имени файла ДО <code>os.path.basename</code></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td style="padding:8px;border:1px solid var(--border)">Битые кодировки имён в ZIP</td>
|
||||||
|
<td style="padding:8px;border:1px solid var(--border)">cp437 → utf8 перекодировка</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td style="padding:8px;border:1px solid var(--border)">Файлы не-Word/PDF (exe, картинки, etc.)</td>
|
||||||
|
<td style="padding:8px;border:1px solid var(--border)">Whitelist: только <code>pdf/docx/doc/zip</code> — остальные <code>unknown_format</code></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td style="padding:8px;border:1px solid var(--border)">Дубликаты файлов (тот же контент, другое имя)</td>
|
||||||
|
<td style="padding:8px;border:1px solid var(--border)"><code>sha256(original_bytes)</code> → пропуск с пометкой <code>duplicate</code></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td style="padding:8px;border:1px solid var(--border)">Дубликаты услуг (одна услуга в разных допниках)</td>
|
||||||
|
<td style="padding:8px;border:1px solid var(--border)">UPSERT по <code>name_hash</code> (имя + дата начала) — разные периоды = разные строки</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td style="padding:8px;border:1px solid var(--border)">ADD без названия услуги</td>
|
||||||
|
<td style="padding:8px;border:1px solid var(--border)">→ <code>UNRESOLVED</code>, не применяется</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td style="padding:8px;border:1px solid var(--border)">UPDATE/DELETE без идентификатора цели</td>
|
||||||
|
<td style="padding:8px;border:1px solid var(--border)">Пустой <code>target_hash</code> → <code>UNRESOLVED</code></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td style="padding:8px;border:1px solid var(--border)">Неизвестный тип операции от LLM</td>
|
||||||
|
<td style="padding:8px;border:1px solid var(--border)">→ <code>UNRESOLVED</code></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td style="padding:8px;border:1px solid var(--border)">Кривые числа (<code>1 000,50</code>)</td>
|
||||||
|
<td style="padding:8px;border:1px solid var(--border)">Авто-нормализация: пробелы → удалить, запятая → точка</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td style="padding:8px;border:1px solid var(--border)">Кривые даты (<code>01.03.2026</code>)</td>
|
||||||
|
<td style="padding:8px;border:1px solid var(--border)">Авто-нормализация: DD.MM.YYYY → YYYY-MM-DD</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td style="padding:8px;border:1px solid var(--border)">Арифметические ошибки</td>
|
||||||
|
<td style="padding:8px;border:1px solid var(--border)">Проверка <code>price × qty = sum</code> — расхождение → флаг</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td style="padding:8px;border:1px solid var(--border)">Мусорные документы (счета, акты, платёжки)</td>
|
||||||
|
<td style="padding:8px;border:1px solid var(--border)">Трёхэтапный фильтр: имя файла → заголовки (2000 симв.) → LLM</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td style="padding:8px;border:1px solid var(--border)">Тихие ошибки (<code>try/except: pass</code>)</td>
|
||||||
|
<td style="padding:8px;border:1px solid var(--border)">Везде <code>logging.warning</code> с контекстом</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
<blockquote>
|
<blockquote>
|
||||||
📋 <strong>Заказчик:</strong> «кривых документов можно ожидать.<br>
|
📋 <strong>Заказчик:</strong> «кривых документов можно ожидать.<br>
|
||||||
@@ -233,11 +287,139 @@
|
|||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
<p>Встроенный редактор с историей версий: сохранил новую версию → она сразу используется LLM. Старые версии остаются в истории — можно откатиться.</p>
|
<p><strong>Встроенный редактор промптов</strong> с полным CRUD и историей версий:</p>
|
||||||
|
<ul>
|
||||||
|
<li>Создание, редактирование, удаление промптов — через веб-интерфейс</li>
|
||||||
|
<li>Активация версии — один клик, и она сразу используется LLM</li>
|
||||||
|
<li>История версий — все предыдущие редакции сохраняются, можно откатиться</li>
|
||||||
|
<li>Без правки кода и без редеплоя — юрист может сам экспериментировать с промптами</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
<hr>
|
<hr>
|
||||||
|
|
||||||
<h2>8. Стек технологий</h2>
|
<h2>8. Хранение документов <span class="tag tag-ok">конфиденциальность</span></h2>
|
||||||
|
|
||||||
|
<blockquote>
|
||||||
|
📋 <strong>Заказчик:</strong> «Загрузка из локали, НЕ S3 (конфиденциальность).»
|
||||||
|
</blockquote>
|
||||||
|
|
||||||
|
<p><strong>Исходные файлы (.docx/.pdf/.doc/zip) НЕ сохраняются в БД.</strong> После загрузки и парсинга они удаляются с сервера. В БД хранятся только:</p>
|
||||||
|
<ul>
|
||||||
|
<li>Метаданные: имя файла, дата, хеш содержимого</li>
|
||||||
|
<li>Результат парсинга: <code>elements_json</code> (текст и таблицы)</li>
|
||||||
|
<li>Результат классификации: тип, номер, дата, контрагент</li>
|
||||||
|
<li>События спецификации: ADD/UPDATE/DELETE</li>
|
||||||
|
</ul>
|
||||||
|
<p>Сами бинарные файлы на диске не остаются.</p>
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
|
||||||
|
<h2>9. Инженерные практики <span class="tag tag-ok">качество кода</span></h2>
|
||||||
|
|
||||||
|
<h3>Decoupling — разделение ответственности</h3>
|
||||||
|
<p>Пайплайн разбит на независимые слои с чёткими интерфейсами:</p>
|
||||||
|
|
||||||
|
<table style="width:100%;border-collapse:collapse;margin:12px 0;border:1px solid var(--border)">
|
||||||
|
<tr style="background:var(--code-bg)">
|
||||||
|
<th style="padding:8px;text-align:left;border:1px solid var(--border)">Слой</th>
|
||||||
|
<th style="padding:8px;text-align:left;border:1px solid var(--border)">Что делает</th>
|
||||||
|
<th style="padding:8px;text-align:left;border:1px solid var(--border)">Можно заменить отдельно</th>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td style="padding:8px;border:1px solid var(--border)">Контракты данных</td>
|
||||||
|
<td style="padding:8px;border:1px solid var(--border)">5 frozen dataclass'ов: <code>ParseResult</code>, <code>ClassifyResult</code>, <code>GroupingResult</code>, <code>BatchGroupingResult</code>, <code>CompareOp</code></td>
|
||||||
|
<td style="padding:8px;border:1px solid var(--border)">Формат данных не привязан к хранилищу</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td style="padding:8px;border:1px solid var(--border)">LLM-клиент</td>
|
||||||
|
<td style="padding:8px;border:1px solid var(--border)">Протокол <code>LLMClient.complete(prompt) → str</code></td>
|
||||||
|
<td style="padding:8px;border:1px solid var(--border)">HttpxLLMClient ↔ FakeLLMClient ↔ другая модель</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td style="padding:8px;border:1px solid var(--border)">Репозиторий</td>
|
||||||
|
<td style="padding:8px;border:1px solid var(--border)">Протокол <code>Repository</code> (17 методов)</td>
|
||||||
|
<td style="padding:8px;border:1px solid var(--border)">PgRepository ↔ MemRepository (тесты)</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td style="padding:8px;border:1px solid var(--border)">Загрузка</td>
|
||||||
|
<td style="padding:8px;border:1px solid var(--border)"><code>parse_multipart()</code> — чистая функция</td>
|
||||||
|
<td style="padding:8px;border:1px solid var(--border)">Парсер не зависит от HTTP-фреймворка</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td style="padding:8px;border:1px solid var(--border)">Классификация</td>
|
||||||
|
<td style="padding:8px;border:1px solid var(--border)"><code>classify_batch(batch_id, llm_client, repo)</code> — Dependency Injection</td>
|
||||||
|
<td style="padding:8px;border:1px solid var(--border)">Любой LLM + любое хранилище</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<h3>Dependency Injection</h3>
|
||||||
|
<p>Все внешние зависимости (LLM, БД) пробрасываются через параметры, а не через глобальные <code>import</code>:</p>
|
||||||
|
<ul>
|
||||||
|
<li><code>call_llm(prompt, llm_client=None)</code> — DI с обратной совместимостью</li>
|
||||||
|
<li><code>classify_batch(batch_id, llm_client=None, repo=None)</code> — можно подставить любой backend</li>
|
||||||
|
<li>В продакшене: HttpxLLMClient + PgRepository</li>
|
||||||
|
<li>В тестах: FakeLLMClient (record/replay) + MemRepository (в памяти)</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
|
||||||
|
<h2>10. Тестирование <span class="tag tag-ok">26/26 PASS</span></h2>
|
||||||
|
|
||||||
|
<table style="width:100%;border-collapse:collapse;margin:12px 0;border:1px solid var(--border)">
|
||||||
|
<tr style="background:var(--code-bg)">
|
||||||
|
<th style="padding:8px;text-align:left;border:1px solid var(--border)">Модуль</th>
|
||||||
|
<th style="padding:8px;text-align:left;border:1px solid var(--border)">Тестов</th>
|
||||||
|
<th style="padding:8px;text-align:left;border:1px solid var(--border)">Что проверяют</th>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td style="padding:8px;border:1px solid var(--border)">Контракты + загрузка</td>
|
||||||
|
<td style="padding:8px;border:1px solid var(--border)">13</td>
|
||||||
|
<td style="padding:8px;border:1px solid var(--border)">Создание dataclass'ов, garbage-фильтр, multipart-парсинг, path-traversal</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td style="padding:8px;border:1px solid var(--border)">LLM + Репозиторий</td>
|
||||||
|
<td style="padding:8px;border:1px solid var(--border)">9</td>
|
||||||
|
<td style="padding:8px;border:1px solid var(--border)">FakeLLMClient, MemRepository CRUD, все 17 методов</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td style="padding:8px;border:1px solid var(--border)">Классификация</td>
|
||||||
|
<td style="padding:8px;border:1px solid var(--border)">4</td>
|
||||||
|
<td style="padding:8px;border:1px solid var(--border)">Garbage по имени файла, garbage по заголовкам, classify с FakeLLM, no pending</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<p>Все тесты проходят без доступа к БД и LLM — через <code>FakeLLMClient</code> (record/replay) и <code>MemRepository</code>. Запуск: <code>pytest deploy/tests/ -v</code>.</p>
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
|
||||||
|
<h2>11. Изоляция окружений</h2>
|
||||||
|
|
||||||
|
<table style="width:100%;border-collapse:collapse;margin:12px 0;border:1px solid var(--border)">
|
||||||
|
<tr style="background:var(--code-bg)">
|
||||||
|
<th style="padding:8px;text-align:left;border:1px solid var(--border)">Окружение</th>
|
||||||
|
<th style="padding:8px;text-align:left;border:1px solid var(--border)">URL</th>
|
||||||
|
<th style="padding:8px;text-align:left;border:1px solid var(--border)">БД</th>
|
||||||
|
<th style="padding:8px;text-align:left;border:1px solid var(--border)">Назначение</th>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td style="padding:8px;border:1px solid var(--border)"><strong>Продакшен</strong></td>
|
||||||
|
<td style="padding:8px;border:1px solid var(--border)"><code>contracts.kube5s.ru</code></td>
|
||||||
|
<td style="padding:8px;border:1px solid var(--border)"><code>contracts</code></td>
|
||||||
|
<td style="padding:8px;border:1px solid var(--border)">Рабочая система</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td style="padding:8px;border:1px solid var(--border)"><strong>Тестовый</strong></td>
|
||||||
|
<td style="padding:8px;border:1px solid var(--border)"><code>check.kube5s.ru</code></td>
|
||||||
|
<td style="padding:8px;border:1px solid var(--border)"><code>contracts_check</code></td>
|
||||||
|
<td style="padding:8px;border:1px solid var(--border)">Отладка, эксперименты — не затрагивает продакшен</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<p>Тестовое окружение — полная копия пайплайна на отдельном порту (8777) с отдельной БД. Можно загружать, классифицировать, сравнивать — не боясь испортить рабочие данные.</p>
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
|
||||||
|
<h2>12. Стек технологий</h2>
|
||||||
|
|
||||||
<table style="width:100%;border-collapse:collapse;margin:12px 0;border:1px solid var(--border)">
|
<table style="width:100%;border-collapse:collapse;margin:12px 0;border:1px solid var(--border)">
|
||||||
<tr style="background:var(--code-bg)">
|
<tr style="background:var(--code-bg)">
|
||||||
@@ -274,7 +456,7 @@
|
|||||||
|
|
||||||
<hr>
|
<hr>
|
||||||
|
|
||||||
<h2>9. Конечная цель <span class="tag tag-future">будущее</span></h2>
|
<h2>13. Конечная цель <span class="tag tag-future">будущее</span></h2>
|
||||||
|
|
||||||
<blockquote>
|
<blockquote>
|
||||||
📋 <strong>Заказчик:</strong> «Конечная цель: построчное сравнение CRM ↔ фискальная система.<br>
|
📋 <strong>Заказчик:</strong> «Конечная цель: построчное сравнение CRM ↔ фискальная система.<br>
|
||||||
@@ -286,7 +468,7 @@ PAYG (суффикс -m) — особый случай. Сверку с CRM то
|
|||||||
|
|
||||||
<hr>
|
<hr>
|
||||||
|
|
||||||
<h2>10. Что дальше <span class="tag tag-future">требует участия Заказчика</span></h2>
|
<h2>14. Что дальше <span class="tag tag-future">требует участия Заказчика</span></h2>
|
||||||
|
|
||||||
<blockquote>
|
<blockquote>
|
||||||
📋 <strong>Заказчик:</strong> «Это всё пока опыты и набивание шишек.<br>
|
📋 <strong>Заказчик:</strong> «Это всё пока опыты и набивание шишек.<br>
|
||||||
|
|||||||
@@ -73,7 +73,7 @@
|
|||||||
<body>
|
<body>
|
||||||
<div class="topbar">
|
<div class="topbar">
|
||||||
<img src="/nubes-logo.svg" alt="Nubes">
|
<img src="/nubes-logo.svg" alt="Nubes">
|
||||||
<span class="title">Сверка договоров — LLM AI-driven Event Sourcing <span style="font-weight:400;color:var(--muted);font-size:12px;">v1.0.182-flask</span></span>
|
<span class="title">Сверка договоров — LLM AI-driven Event Sourcing <span style="font-weight:400;color:var(--muted);font-size:12px;">v1.0.184-flask</span></span>
|
||||||
<div id="pipelineStepper" style="display:flex;gap:8px;font-size:11px;align-items:center;color:var(--muted);">
|
<div id="pipelineStepper" style="display:flex;gap:8px;font-size:11px;align-items:center;color:var(--muted);">
|
||||||
<span id="stepUpload">○ Загрузка</span><span>→</span>
|
<span id="stepUpload">○ Загрузка</span><span>→</span>
|
||||||
<span id="stepClassify">○ Классификация</span><span>→</span>
|
<span id="stepClassify">○ Классификация</span><span>→</span>
|
||||||
|
|||||||
Reference in New Issue
Block a user