Files
contracts/History/llm-analysis/opus-teach-full-responses.md
T

516 lines
44 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.
# Полные ответы Опуса — изолированная страница /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`.