# Схема Опуса — генератор + симулятор (v1.0) Ответ Опуса от 25.06.2026 на запрос `History/opus-generator-request.md`. --- ## Оценка схемы ### Сильные стороны 1. **v1→v2 diff-пары** — ключевая идея. Генератор закладывает конкретные изменения между версиями спецификаций (цена, объём, ADD/DELETE/UPDATE), и верификатор проверяет что compare нашёл именно их. Это даёт детерминированную проверку самой важной функции сервиса. 2. **manifest.json как ground truth** — каждая проверка сверяется с эталоном, а не с «примерно ожидаемым». Никакой неоднозначности. 3. **Пул услуг из реальных данных** — стойко-место, PDU, L2-канал, cross-connect, Cloud, IPv4. Не выдуманные, а те что в реальных файлах. 4. **17 ошибочных кейсов** — покрытие крайне широкое: от битого XML до сиротских спецификаций. 5. **Структура папок** — `testgen/` + `sim/` с чётким разделением генерации и симуляции. ### Что нужно уточнить/доработать 1. **Симулятор: API или браузер?** Опус описывает «реальное приложение (API/UI)». Два варианта: - **curl-симулятор** (только API): дёргаем `/upload`, `/api/classify-batch`, `/api/groups`, `/process-v2`. Быстро, дёшево, но не проверяет фронтенд (duplicate confirm, renderFiles, SSE в UI). - **Playwright** (полный UI): открывает браузер, кликает кнопки, читает DOM. Медленно, но проверяет ВСЁ включая confirm-диалоги. - **Рекомендация**: curl для генерации/классификации/групп + выборочно Playwright для UI-специфичных сценариев (дубликаты, confirm, удаление). 2. **.doc файлы** — парсинг `допник-1-XXX002-01200_3.doc` не удался (старый формат). Нужен LibreOffice для конвертации, либо исключить .doc из генерации и использовать только .docx. 3. **Стресс-сценарий F** — rapid-fire загрузки во время classify. Это может быть сложно воспроизвести и проверить. Вероятно, отложить на потом. 4. **Количество файлов** — раскладка даёт ~92 файла + edge cases. Можно докрутить до ровно 100 добавив ещё дубликатов или мусора. --- ## Исходная схема Опуса ### Часть 1 — Генерация ~100 файлов **Базовая структура:** 10 компаний × 10 договоров × (спецификация v1 + v2 + 1-3 допника) **Раскладка:** | Категория | Кол-во | Зачем | |---|---|---| | Договоры (валидные) | 10 | по 1 на компанию | | Спецификации v1 | 10 | базовая версия | | Спецификации v2 (ревизия) | 10 | diff для сравнения | | Допники | 18 | 1–3 на договор | | Битые/edge .docx | 15 | ошибки парсинга/классификации | | Форматы .doc/.pdf | 12 | ветка конвертера | | ZIP-архивы | 5 | внутри — наборы файлов | | Дубликаты (имя/контент) | 10 | upload-логика | | Мусор (не договор) | 10 | негативная классификация | | **Итого** | **~100** | | **Оси вариативности:** - Компания: ЗАО/ООО/АО/ПАО/ИП, латиница/кириллица - Номер: 5 цифр, опц. суффикс `_N`, edge — буквы/длинный/пустой - Дата: 12.2025–06.2026, форматы `01.02.2026` и «01 февраля 2026» - Цены: базовые ±20%, разрядность с пробелом - Услуги (пул): стойко-место, PDU, L2-канал, IPv4 /30 и /32, cross-connect ВОЛС/UTP, Cloud Intel/AMD, Интернет, порт, подсеть - Таблица: 3–12 строк, 6 или 7 колонок **Различия v1→v2 (для компаратора):** изменение цены, изменение объёма, добавленная услуга, удалённая услуга, переименование, сдвиг даты, пересчёт итога. **Ошибочные кейсы (17 шт):** нет номера, нестандартный заголовок, битый XML, пустой файл, только таблицы, только текст, .doc формат, битый PDF, дубликат имени, дубликат контента, гигант (500 строк), не-договор, сирота (спека без договора), рассинхрон (имя файла ≠ номер в тексте). **Parent-child:** ключ — НОМЕР. Генератор связывает договор→спеку/допник по НОМЕРу. **Шаблоны python-docx:** - Билдеры: `build_contract(ctx)`, `build_spec(ctx)`, `build_addendum(ctx)` - ctx = {company, number, date, parties, services[], totals} - Хелперы: `add_title`, `add_section`, `add_services_table` - Битый XML: валидный docx → распаковать zip → испортить document.xml → запаковать - .doc: docx → libreoffice --convert-to doc - Битый PDF: docx → pdf → обрезать байты ### Часть 2 — Симулятор **Словарь действий:** upload, delete, classify, groups, compare (SSE), confirm/cancel, reset. **Сценарии:** - **A. Хэппи-путь:** договор + спека v1 + v2 → classify → группа → compare → diff совпал - **B. Зигзаг:** загрузил 3 → удалил 1 → добавил 2 → classify → проверить консистентность - **C. Bulk:** все ~100 разом → classify → число групп, битые ✗ - **D. Тупые действия:** дубликат (OK/Отмена), удалить всё, смешать форматы, classify на нуле, compare с одной версией - **E. ZIP:** валидный, с дубликатами, с битым файлом внутри - **F. Стресс:** случайные паузы, rapid-fire, загрузка во время classify **Тайминги:** think-time (random 0.1-3с), classify — поллинг, compare — SSE до done/таймаут. ### Часть 3 — Верификация После каждого шага: - `state.files` count == ожидаемого - Статусы ✓/✗ соответствуют (битые → ✗) - После classify: группы есть, состав по НОМЕРу верный - Сироты без краша - Compare: diff == заложенному (ground truth из manifest.json) - Дубликаты: счётчик после OK ≠ после Отмена - Нет 500-х, нет необработанных исключений - Идемпотентность classify **Ground truth:** `manifest.json` — что сгенерировано + ожидаемые группы и diff-ы. ### Структура папок ``` testgen/ pools.py # услуги, компании, цены templates.py # build_contract / build_spec / build_addendum corrupt.py # порча docx/pdf, конвертация .doc generate.py # оркестратор → out/ + manifest.json out/{valid,errors,formats,zips,duplicates}/ manifest.json # ground truth sim/ actions.py # обёртки над API scenarios.py # A..F verify.py # чек-лист run.py ```