# Полные ответы Опуса — изолированная страница /teach (feedback + обучение) Дата: 26.06.2026 | Сохранено post-factum из чат-сессии. --- # ЧАСТЬ 1: Ответ про few-shot и эталонный датасет Хороший вопрос. Дай я сначала посмотрю, как сейчас устроены эти «6 файлов» и формирование промпта, чтобы мнение было по делу. Searching codebase for "формирование промпта для LLM на основе эталонных файлов few-shot examples" Searched for regex `6 (файл|file|example|эталон|reference)`, 1 result Searched for regex `few.?shot|эталон|example|пример|reference|опорн` (`**/contracts-app/**/*.py`), no results Searched for files matching `**/llm_prompt.py`, 1 match Searched for files matching `**/prompt*.{py,cfm}`, 2 matches Searched for regex `opus_cases|out_100files|6 файл|six`, 2 results Read file:///home/naeel/nubes/contracts/contractor/deploy/llm_prompt.py#1-1, lines 1 to 120 Read file:///home/naeel/nubes/contracts/testgen/opus_cases Посмотрел, как сейчас устроено: промпт в llm_prompt.py — это хардкод-инструкция с доменным глоссарием и парой ручных few-shot примеров (`ПРИМЕР 1`, `ПРИМЕР 2`), а «6 файлов» — это по сути синтетические кейсы из opus_cases для проверки. Моё мнение по фиче. ## Идея здравая, но есть важная развилка Реальные проверенные документы — это очень ценно. Синтетика (gen_opus.py) не воспроизводит грязь реальных договоров: кривые таблицы, объединённые ячейки, «мощность в составе названия», нестандартные формулировки ДС, опечатки в суммах. На 6 искусственных файлах легко получить иллюзию качества, которая рассыпется на первом же настоящем пакете. Но ключевой вопрос: **что именно строить из архива?** Тут смешаны две разные вещи. ## 1. Эталонный датасет (input → проверенный output) — да, делать Самое полезное. Но важно: «загрузить архив документов» недостаточно. Чтобы это работало, на каждый документ нужен **проверенный правильный результат** (итоговая спецификация / список ops). Без эталонного ответа это просто куча PDF — не на чем калибровать. То есть фича должна быть не «загрузи архив», а «загрузи архив + зафиксируй проверенную спецификацию как ground-truth». Тогда получаем regression-набор: прогнал промпт → сравнил с эталоном → видишь реальный процент ошибок. ## 2. Авто-генерация промпта из этих файлов — осторожно Полностью «формировать промпт» из загруженных файлов я бы не делал. Риски: - **Раздувание контекста** — если пихать реальные договоры целиком как few-shot, промпт пухнет, растёт цена/латентность, и модель начинает копировать формат конкретного образца. - **Переобучение под layout** — закладываешь форматы 3-х клиентов, ломается на четвёртом. - **Конфиденциальность** — реальные договоры с реквизитами/ценами уезжают в LLM на каждом запросе. Это надо явно проговорить с заказчиком. Лучше из архива **извлекать паттерны, а не сам текст**: новые единицы измерения, варианты названий услуг, типы формулировок ДС («изложить в новой редакции» и т.п.) — и обогащать ими глоссарий и правила в промпте. 1-2 самых показательных кейса можно добавить как few-shot, остальное — в eval-набор. ## Что я бы предложил по факту - **Разделить два контура**: (а) `golden dataset` из реальных пар вход→проверенный_выход для регрессии; (б) сам промпт — оставить компактным, обогащать его выводами из анализа датасета, а не сырыми файлами. - Фича «загрузка архива» → ведёт в **разметку/верификацию** (человек подтверждает правильный результат), а не сразу в промпт. - На этом наборе гонять метрику (precision/recall по ops), и менять промпт только если метрика растёт. Резюме: направление правильное — уходить от 6 синтетических файлов надо. Но ценность не в «архиве документов», а в **архиве проверенных пар документ↔результат**. И этим калибровать/тестировать, а не механически генерировать промпт. Это обсуждение — код не трогаю. Скажешь «делай» по конкретному пункту — приступлю. --- # ЧАСТЬ 2: Ответ про «типа обучение» (feedback-learning цикл) Да, это сильнее предыдущего варианта — и вот почему, плюс где подвох. ## Почему это лучше «загрузки архива» Главная проблема прошлого варианта была: откуда взять «проверенный правильный ответ». Здесь она решается сама собой — **интерфейс исправления ошибок и есть инструмент разметки**. Юзер не размечает абстрактно, а правит конкретный результат системы. На выходе автоматически копится то самое: пара «вход → проверенный выход». Это правильный способ построить эталонный датасет. И что важно — у вас уже есть подходящая структура под это. Результат LLM — это поток операций (`ADD` / `UPDATE` / `DELETE` / `UNRESOLVED`) в llm_prompt.py, а применяются они через событийную модель (apply_events.cfm). Значит правка юзера — это **исправленный поток операций**, а разница «что выдала LLM» vs «как поправил юзер» = чистый сигнал ошибки. Это удобно: не свободный текст «тут неверно», а конкретная дельта. ## Но «типа обучения» — это важная оговорка Само по себе это **не обучение модели**. Цикл «учится» только если правки где-то сохраняются и переиспользуются. Если правка живёт в рамках одной сессии и исчезает — система не умнеет, это просто ручная корректировка. Так что ключевое в фиче — не сам акт правки, а **накопление и переиспользование** правок. Дальше есть три уровня, и не надо их путать: 1. **Регрессия** (самое дешёвое и полезное сразу) — правки копятся как golden-набор, на нём гоняется промпт, видно реальный % ошибок. 2. **Prompt-learning** — из частых ошибок осознанно дополняется глоссарий/правила/few-shot в промпте. Это и есть практичное «обучение» в вашем случае. 3. **Fine-tuning модели** — реальное дообучение. Дорого, нужен объём (сотни-тысячи примеров), отдельная инфраструктура. На старте не нужно. ## Где подвох — не вкручивать правки в промпт автоматически Соблазн: «каждую правку юзера сразу добавлять в промпт». Так делать нельзя: - **переобучение** под конкретные форматы; - **рост контекста** и цены; - **конфликтующие правки** от разных юзеров → промпт деградирует. Правильнее: правки идут в накопитель → периодически человек/куратор смотрит агрегат → осознанно обновляет промпт → прогоняет регрессию. Batch, а не автоматом на каждый клик. ## Что бы я ещё заложил в дизайн - **Структурировать тип ошибки**, а не свободный текст: «пропущена строка» / «неверная цена» / «надо было UPDATE, а не ADD» / «ложный дубликат вместо UNRESOLVED». Тогда правки можно агрегировать и видеть системные слабости промпта, а не разбирать вручную. - Фиксировать **версию промпта**, на которой получен результат — иначе через месяц непонятно, актуальна ли ещё ошибка. - Учесть **конфиденциальность**: реальные договоры с реквизитами/ценами копятся в БД — проговорить с заказчиком. Резюме: режим «загрузил → система выдала → юзер поправил → правка сохранилась» — да, делать стоит, это естественный сборщик эталонных данных и ловушка системных ошибок. Только «обучение» держать осознанным (batch-обновление промпта + регрессия), а не авто-инъекцией каждой правки. Код не трогаю — это обсуждение. Скажешь «делай» — распишу конкретную реализацию. --- # ЧАСТЬ 3: Короткий вариант для заказчика Вот короткий вариант для заказчика: --- **Этап опытной эксплуатации (обучение системы)** На первое время предлагаем режим проверки: вы загружаете реальные документы, система выдаёт результат, а вы отмечаете ошибки — что распознано неверно (пропущена строка, неверная цена, неправильное сопоставление и т.п.). Эти отметки **в обезличенном виде** (без названий компаний, ФИО, реквизитов и иных персональных данных) накапливаются в базе и используются для дальнейшей настройки и обучения системы. Это позволит откалибровать сервис на ваших реальных договорах, а не на тестовых примерах, и системно повышать точность. --- Если нужно ещё короче (одним абзацем) или, наоборот, добавить пункт про согласие на обработку данных — скажи, поправлю. --- # ЧАСТЬ 4: Ответ про привязку замечаний к строкам таблицы Дай гляну, как сейчас выглядит вывод результата — чтобы предложить привязку замечаний к реальным элементам интерфейса, а не абстрактно. Read file:///home/naeel/nubes/contracts/contractor/deploy/services Read memory file:///home/naeel/.vscode-server/data/User/workspaceStorage/dc98ff3e658364e1826ee6585ebe4b5e/GitHub.copilot-chat/memory-tool/memories/repo/contracts-two-panels.md Searched for regex `target_id|r1|new_row|action.*ADD|операц|spec|строк` (`**/contractor/view.cfm`), no results Searched for files matching `**/contracts-app/site/templates/**`, no matches Не «у каждого абзаца» — это привязка не к тому объекту. Результат работы системы — это не текст-простыня, а **таблица спецификации**: набор строк-услуг, каждая получена операцией LLM (`ADD`/`UPDATE`/`DELETE`/`UNRESOLVED`) и имеет свой id (`r1`, `r2`…). Замечание логично цеплять **к строке результата**, а не к абзацу исходника. ## Как я бы это сделал **Уровень 1 — отметка на строке (основное).** У каждой строки итоговой таблицы — маленькая иконка «⚠ ошибка». Клик открывает мини-форму с **выбором типа ошибки** (не свободный текст): - неверная цена / объём / сумма - неверное наименование - лишняя строка (надо было удалить) - пропущена строка (есть в документе — нет в результате) - неверное сопоставление (надо UPDATE существующей, а не новая ADD; или наоборот) - неверный тип ДС / режим (partial vs новая редакция) Плюс необязательное поле «комментарий» свободным текстом. **Уровень 2 — отметка «пропущено».** Случай «строки вообще нет в результате» не покрывается иконкой на строке. Нужна отдельная кнопка «➕ Система пропустила позицию» над/под таблицей. **Уровень 3 — общая оценка (опционально).** Одна кнопка вверху: «Результат в целом верный / есть ошибки». Дёшево, но даёт быстрый сигнал по проценту удачных разборов. ## Почему так, а не поле у абзаца - **Структурированный фидбек агрегируется.** «Тип ошибки = неверное сопоставление встречается в 40% ДС» → понятно, что чинить в промпте. Свободный текст у абзацев придётся разбирать вручную, аналитики ноль. - **Замечание = дельта операции.** Юзер фактически правит строку → разница «что выдала LLM» vs «как должно быть» и есть обучающий сигнал. Это ложится на вашу событийную модель (apply_events.cfm) почти без натяжки. - **Меньше шума.** Поле ввода под каждым абзацем визуально перегружает и провоцирует расплывчатые комментарии. ## Что копится в базе (обезличенно) На каждое замечание: - тип ошибки + (опц.) комментарий - что выдала система по этой строке - как поправил юзер (правильное значение) - версия промпта - **без** названий компаний/ФИО/реквизитов — только структура и числа ## Как это сформулировать заказчику (1 фраза) > В режиме проверки у каждой строки результата будет отметка «ошибка» с выбором типа (неверная цена, пропущена позиция, неверное сопоставление и т.п.). Эти отметки в обезличенном виде копятся в базе и используются для настройки точности. Хочешь — распишу конкретную реализацию (поля в БД, эндпоинт сохранения, изменения в шаблоне вывода). Код пока не трогаю, жду «делай». --- # ЧАСТЬ 5: Ответ про UI с примером на допник-1-XXX002 Отлично, теперь предметно. Твой вывод — это карточка документа + **таблица операций** (`Действие / Услуга / Цена / Кол-во / Сумма / Дата`). Замечания цепляем именно к строкам этой таблицы. Вот как это выглядело бы на твоём примере: ``` ✓ допник-1-XXX002-01200_3.docx — 2 оп., partial (14с) [✓ всё верно] +2 ~0 -0 Действие Услуга Цена Кол-во Сумма Дата ⚠ ADD WAF: Positive Technologies, в составе… 104021.67 1 104021.67 2026-03-30 [⚠] ADD Облачный диск Valo Cloud, в составе… 68700 1 68700 2026-02-01 [⚠] [ ➕ Система пропустила позицию ] ``` **Клик по `[⚠]` в строке** → раскрывается мини-форма прямо под строкой: ``` Тип ошибки: ( ) неверная цена/сумма ( ) неверное кол-во ( ) неверное наименование услуги (•) лишняя строка — этой операции быть не должно ( ) неверное действие (должно быть UPDATE/DELETE, а не ADD) ( ) неверная дата Правильное значение: [_____________] (необязательно) Комментарий: [_____________] (необязательно) [ Сохранить ] ``` **Кнопка `[✓ всё верно]`** вверху карточки — если разбор документа корректен целиком. Один клик = положительный сигнал, ничего расписывать не надо. **Кнопка `➕ Система пропустила позицию`** — для случая, которого нет в таблице (услуга была в документе, но LLM её не извлекла). Открывает форму ввода пропущенной строки. ## Что уходит в базу по одному замечанию (обезличенно) На примере второй строки, если юзер укажет «неверная цена, правильно 68000»: ``` operation: ADD service: "Облачный диск Valo Cloud, в составе…" ← наименование услуги, без названия клиента field: price llm_value: 68700 correct: 68000 error_type: wrong_price prompt_version: v1.0.178 ``` Никаких «ЗАО ХХХ002», № договора, ФИО — только структура услуги и числа. ## Почему так, а не поле под каждым абзацем Твой реальный вывод подтверждает: это **таблица**, а не текст. Поле у «абзаца» некуда вешать — абзацев тут нет, есть операции. А привязка к операции даёт: - агрегацию по типу ошибки (видно, что чинить в промпте), - готовую дельту «LLM выдала X → правильно Y» как обучающий сигнал, - ровную укладку в вашу событийную модель (apply_events.cfm). Хочешь — распишу конкретику реализации (колонка в таблице вывода, эндпоинт сохранения, структура таблицы в БД). Код не трогаю, жду «делай». --- # ЧАСТЬ 6: ДЕТАЛЬНЫЙ ПЛАН — страница /teach Это план, не правка кода — изучу структуру, чтобы план лёг на реальный код, а не в воздух. Читаю ключевые файлы. Read file:///home/naeel/nubes/contracts/contractor/db.cfc Read file:///home/naeel/nubes/contracts/contractor/Application.cfc Read memory file:///home/naeel/.vscode-server/data/User/workspaceStorage/dc98ff3e658364e1826ee6585ebe4b5e/GitHub.copilot-chat/memory-tool/memories/repo/contracts-deploy.md Read memory file:///home/naeel/.vscode-server/data/User/workspaceStorage/dc98ff3e658364e1826ee6585ebe4b5e/GitHub.copilot-chat/memory-tool/memories/repo/contracts-lucee-test.md Изучил структуру. Lucee-приложение с файловой маршрутизацией (`teach.cfm` → `/teach`), PostgreSQL `baza`, есть событийная модель (`spec_events`, `spec_current`) и уже готовый read-only SQL-эндпоинт `/db/query?sql=…` в db.cfc — через него агент и будет читать БД. Вот детальный план. --- # План: страница `/teach` (опросник + обучение) ## 1. Принцип изоляции — что НЕ трогаем Маршрутизация в Lucee файловая → **новый путь = новый файл**, существующий код не редактируется вообще: | Не трогаем | Добавляем | |---|---| | `api.cfm`, `process*.cfm`, `apply_events.cfm`, db.cfc, Application.cfc, `upload.cfm`, `view.cfm` | `teach.cfm`, `teach_api.cfm`, `teach_schema.cfm` | | существующие таблицы (`spec_events`, `contracts`…) | **одна новая таблица** `feedback` | Никаких `ALTER` существующих таблиц, никаких правок в `db.cfc/schema()`. Только чтение чужих таблиц + запись в свою. ## 2. Новые файлы (3 шт.) 1. **`teach_schema.cfm`** — одноразовый: `CREATE TABLE IF NOT EXISTS feedback (…)`. Зашёл по URL один раз → таблица создана. 2. **`teach.cfm`** — сама страница: - список уже разобранных договоров/ДС (читает `contracts` + `supplements` + `spec_events`); - выбрал ДС → рисует ту же таблицу операций (`Действие/Услуга/Цена/Кол-во/Сумма/Дата`), что в твоём примере, но с колонкой `⚠` и кнопками `[✓ всё верно]`, `[➕ пропущена позиция]`; - JS отправляет замечание `POST`-ом на `teach_api.cfm`. 3. **`teach_api.cfm`** — приём замечания: **параметризованный** `INSERT` в `feedback` (никакого конкатенированного SQL — защита от инъекций). Возвращает JSON `{ok:true}`. > Источник данных для таблицы операций — `spec_events` (`action`, `new_values` JSONB, `comment`, `seq`). При реализации сверим, что карточка «2 оп., partial» строится именно отсюда. ## 3. Схема таблицы `feedback` (ядро — продумано под анализ агентом) Главные поля вынесены отдельными колонками (не в JSON), чтобы агент агрегировал простым SQL; значения — в JSONB. ```sql CREATE TABLE IF NOT EXISTS feedback ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), created_at TIMESTAMPTZ DEFAULT now(), -- привязка (внутренняя трассировка, в обучающий экспорт НЕ идёт) contract_id UUID, -- FK-логически на contracts, без жёсткого constraint supplement_id UUID, event_seq INTEGER, -- какая операция ДС (NULL для "пропущено"/"документ в целом") -- уровень и вердикт scope TEXT NOT NULL, -- 'row' | 'missed' | 'document' verdict TEXT, -- 'correct' | 'error' -- суть ошибки (для агрегации) error_type TEXT, -- wrong_price|wrong_qty|wrong_name|wrong_date| -- extra_row|missed_row|wrong_action|wrong_mode field TEXT, -- price|qty|sum|name|date_start|action|mode -- обучающий сигнал: что выдала система vs как правильно service_name TEXT, -- наименование услуги (обезличено) llm_value JSONB, -- что выдала LLM correct_value JSONB, -- что указал юзер -- контекст результата prompt_version TEXT, -- версия промпта на момент разбора doc_mode TEXT, -- partial | full_replace comment TEXT, -- свободный комментарий юзера (необяз.) reviewer TEXT -- обезличенный id сессии/юзера (необяз.) ); CREATE INDEX IF NOT EXISTS idx_feedback_error ON feedback(error_type); CREATE INDEX IF NOT EXISTS idx_feedback_prompt ON feedback(prompt_version); ``` ## 4. Что пишется по каждому типу замечания | Действие юзера | scope | verdict | error_type | llm_value → correct_value | |---|---|---|---|---| | `[✓ всё верно]` на карточке | `document` | `correct` | — | — | | `⚠` на строке: неверная цена | `row` | `error` | `wrong_price` | `{"price":68700}` → `{"price":68000}` | | `⚠`: лишняя строка | `row` | `error` | `extra_row` | вся операция → `null` | | `⚠`: должно быть UPDATE, а не ADD | `row` | `error` | `wrong_action` | `{"action":"ADD"}` → `{"action":"UPDATE"}` | | `[➕ пропущена позиция]` | `missed` | `error` | `missed_row` | `null` → `{name,price,qty,sum,date}` | | неверный режим ДС | `document` | `error` | `wrong_mode` | `{"mode":"partial"}` → `{"mode":"full_replace"}` | Каждая запись самодостаточна: видно **что было** и **как надо** → готовая обучающая пара. ## 5. Обезличивание - В `feedback` **не копируем** название клиента, № договора, ФИО, реквизиты. - Храним только: `service_name` (тип услуги — «WAF Positive Technologies»), числа, тип ошибки, версию промпта. - `contract_id/supplement_id` — это UUID (не имя), для внутренней трассировки. В **обучающий экспорт** агента эти id не включаются — только структурные поля. - Для заказчика формулировка: *«в обезличенном виде, без названий компаний и персональных данных»* — соответствует фактической схеме. ## 6. Как агент читает и анализирует (потом) Через уже существующий `/db/query?sql=…` (read-only) или напрямую psql. Примеры: **Системные слабости промпта:** ```sql SELECT error_type, field, count(*) AS n FROM feedback WHERE verdict='error' GROUP BY error_type, field ORDER BY n DESC; ``` **Качество по версии промпта (регрессия):** ```sql SELECT prompt_version, count(*) FILTER (WHERE verdict='correct') AS ok, count(*) FILTER (WHERE verdict='error') AS err FROM feedback GROUP BY prompt_version; ``` **Выгрузка обучающих пар (для правки промпта/few-shot):** ```sql SELECT service_name, error_type, llm_value, correct_value FROM feedback WHERE verdict='error' AND scope='row'; ``` Дальше агент: смотрит агрегат → предлагает правку промпта/глоссария → прогоняет на накопленных парах → сравнивает метрику до/после. **Никакой авто-инъекции** правок в промпт — только осознанное batch-обновление. ## 7. Версия промпта — нюанс Сейчас результат разбора **не штампуется** версией промпта. Варианты: - (минимум, без правки основного кода) `/teach` пишет в `prompt_version` **текущую активную** версию промпта на момент замечания — приблизительно, с оговоркой; - (правильно, отдельной задачей позже) при разборе сохранять `prompt_version` в результат — но это уже касается основного пайплайна, делать отдельно и по «делай». Флажок: на старте берём активную версию, точность привязки уточним позже. ## 8. Этапы внедрения 1. `teach_schema.cfm` → создать таблицу `feedback`. 2. `teach.cfm` (чтение разобранных ДС + рендер таблицы с `⚠`) — без сохранения, только UI. 3. `teach_api.cfm` (параметризованный INSERT) + подключить JS-отправку. 4. Проверка end-to-end на одном реальном ДС (как твой `допник-1-XXX002`). 5. 2–3 контрольных SQL-запроса агента → убедиться, что данные читаемы и агрегируются. 6. (позже, отдельно) первый цикл анализа → правка промпта → регрессия. ## 9. Открытые вопросы к тебе 1. **`/teach` работает только по уже разобранным** документам (из БД) — или нужна и загрузка-разбор прямо на странице? (загрузка-разбор = переиспользование основного пайплайна, сложнее). 2. **Доступ к `/teach`** — нужен ли пароль/ограничение, или dev-стенд открыт? 3. **`service_name`** — оставляем как есть (тип услуги) или дополнительно маскируем? 4. **Кто reviewer** — нужно различать разных проверяющих или достаточно анонимной сессии? --- Это план, код не трогал. Скажешь по каким пунктам «делай» (и ответишь на вопросы из §9) — начну с `teach_schema.cfm`. --- # ЧАСТЬ 7: Ответ про промпт для Опуса Текст для Опуса хороший — чёткие требования и правильные ограничения (не трогать пайплайн, без online-training, обезличенность). Но в нынешнем виде Опус ответит **обобщённо**, потому что в промпте нет контекста вашего стека и текущей схемы. По каждому из 5 вопросов это критично. Что я бы добавил, прежде чем отправлять: ## Чего не хватает Опусу для предметного ответа 1. **Стек и маршрутизация.** Lucee/CFML 6.0, файловая маршрутизация (`teach.cfm` → `/teach`), PostgreSQL `baza`. Без этого Опус не поймёт, как «не трогать пайплайн» технически (а ответ — просто новый файл, без правок существующих). 2. **Текущая схема — особенно для Q3.** У вас уже есть событийная модель: - `spec_events(contract_id, supplement_id, seq, action, target_hash, new_values JSONB, comment, status)` - `spec_current`, `contracts`, `supplements`. Связь с исходной операцией = `(contract_id, supplement_id, event_seq)`, и это **уже отвечает на Q3**: ничего в пайплайне трогать не надо, фидбек просто ссылается на `seq` логически, без FK-constraint. Опус должен это знать, иначе предложит схему, не совпадающую с вашей. 3. **Форма результата (для Q1/Q2).** Таблица операций `Действие / Услуга / Цена / Кол-во / Сумма / Дата`, режимы `partial | full_replace`. Дайте Опусу реальный пример вывода (твой `допник-1-XXX002`). 4. **Ключевой факт для Q4.** Сейчас результат разбора **не штампуется версией промпта** — этого в промпте нет, а это определяет весь ответ на Q4. Без упоминания Опус посоветует «просто сохраняйте prompt_version», не зная, что её сейчас негде взять, кроме «текущей активной». 5. **Как агент потом читает БД (для Q1).** Есть read-only эндпоинт `/db/query?sql=…` в db.cfc. Это аргумент за «плоские колонки `error_type/field/prompt_version` + значения в JSONB» — чтобы агрегировалось обычным SQL. ## Мелочи по формулировкам - Q5 стоит разнести: отдельно **PII** (имена/реквизиты/№ договора — не хранить) и отдельно **изоляция рабочих данных** (никаких `ALTER`/FK на боевые таблицы, только своя `feedback`). Сейчас они слиты в один вопрос. - Добавь явно: *«предложи минимальный набор `error_type` как enum-значения»* — иначе Опус может уйти в свободный текст ошибок, который потом не агрегируется. --- Хочешь — могу либо (а) переписать твой промпт для Опуса с этим контекстом, либо (б) сам ответить на все 5 вопросов по вашей реальной схеме (у меня он есть). Скажи, что полезнее. --- # ЧАСТЬ 8: Финальный предметный ответ по 5 вопросам Понял, отвечаю по существу — по вашей реальной схеме. ## 1. Минимальная схема `feedback` для MVP Плоские колонки для агрегации + JSONB для значений: ```sql CREATE TABLE IF NOT EXISTS feedback ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), created_at TIMESTAMPTZ DEFAULT now(), -- связь с исходной операцией (логическая, без FK) contract_id UUID, supplement_id UUID, event_seq INTEGER, -- NULL для missed/document scope TEXT NOT NULL, -- 'row' | 'missed' | 'document' verdict TEXT NOT NULL, -- 'correct' | 'error' error_type TEXT, -- enum-значения, см. ниже field TEXT, -- price|qty|sum|name|date_start|action|mode service_name TEXT, -- наименование услуги (обезличено) llm_value JSONB, -- что выдала система correct_value JSONB, -- как правильно prompt_version TEXT, doc_mode TEXT, -- partial | full_replace comment TEXT ); CREATE INDEX IF NOT EXISTS idx_feedback_error ON feedback(error_type); CREATE INDEX IF NOT EXISTS idx_feedback_prompt ON feedback(prompt_version); ``` Фиксированный набор `error_type` (enum как CHECK или просто документированный список): `wrong_price, wrong_qty, wrong_name, wrong_date, extra_row, missed_row, wrong_action, wrong_mode`. Всё. Ни истории версий записи, ни статусов модерации в MVP не нужно. ## 2. UI первой версии vs отложить **В MVP:** - колонка `⚠` у каждой строки таблицы операций → мини-форма: выбор `error_type` (радио) + опц. «правильное значение» + опц. комментарий; - кнопка `[✓ всё верно]` на карточке ДС (дешёвый положительный сигнал, `scope=document, verdict=correct`); - кнопка `[➕ пропущена позиция]` (`scope=missed`). **Отложить:** - инлайн-редактирование значений прямо в ячейке (пока хватает «правильное значение» в форме); - дашборд статистики на самой странице (агрегация — задача агента через SQL, не UI); - модерация/удаление чужих замечаний, роли, авторизация по пользователям; - разбор-на-лету (загрузка нового документа) — работаем по уже разобранным из БД. ## 3. Связь с исходной операцией без вмешательства в pipeline Ключ — **логическая ссылка `(contract_id, supplement_id, event_seq)`**, БЕЗ внешнего ключа на `spec_events`. У вас операции уже лежат в `spec_events(contract_id, supplement_id, seq, action, new_values…)` — `seq` стабилен в рамках договора. `feedback` просто хранит эти три значения как обычные поля. Почему без FK: - FK на `spec_events` = риск, что повторный разбор/перезапись событий уронит вставку фидбека или каскадом заденет данные — это и есть «вмешательство». Логическая ссылка развязывает таблицы полностью. - `feedback` — отдельная таблица, ничего в боевых не меняем (никаких `ALTER`, никаких триггеров). Если `seq` теоретически может переехать при reset/reparse — дополнительно дублируем в `llm_value` снимок операции (`action` + `new_values`), чтобы замечание оставалось интерпретируемым даже без живого `spec_events`. Этого достаточно. ## 4. prompt_version — хранить сразу, но честно Хранить **да** — без неё нельзя мерить регрессию (стало лучше/хуже после правки промпта). Но: сейчас результат разбора версией промпта **не штампуется**, взять её из результата негде. Поэтому для MVP, не трогая pipeline: - `/teach` на момент сохранения замечания читает **текущую активную** версию промпта из БД (ту, что Lucee отдаёт как активную) и пишет в `prompt_version`. - Это приближение: оно верно, если промпт не менялся между разбором и проверкой. Помечаем это как «версия на момент проверки», не «на момент разбора». Правильная привязка (штамповать `prompt_version` в результат при разборе) — отдельная задача по основному пайплайну, делается позже и по явному решению, не в этом изолированном flow. ## 5. Что обязательно исключить **PII / обезличивание — не хранить:** - название/ИНН клиента, № договора, ФИО, подписантов, реквизиты, email/телефоны; - оригинальный текст документа и байты файла. Храним только тип услуги (`service_name`), числа, тип ошибки, версию промпта. `contract_id/supplement_id` — это UUID (не имена), для внутренней трассировки; в обучающий **экспорт** агента они не попадают. **Изоляция рабочих данных — исключить из дизайна:** - любые `ALTER` существующих таблиц, FK-constraint'ы на боевые таблицы, триггеры; - запись в `spec_events / spec_current / contracts / supplements` — `/teach` только читает их, пишет исключительно в `feedback`; - никакого generic-SQL write-эндпоинта на публичной странице — только параметризованный INSERT в `teach_api.cfm` (защита от инъекций). --- Это ответ-проектирование, код не трогаю. Скажешь «делай» — начну с `teach_schema.cfm` (создание таблицы), затем `teach.cfm` и `teach_api.cfm`.