From 32aabe9a80ba8887d5fa566d0af338f9dc479a93 Mon Sep 17 00:00:00 2001 From: dev Date: Sun, 4 Oct 2026 00:58:12 +0300 Subject: [PATCH] =?UTF-8?q?fix(lesson-ai):=20=D0=BE=D0=B1=D0=BD=D0=BE?= =?UTF-8?q?=D0=B2=D0=BB=D0=B5=D0=BD=D0=B8=D0=B5=20=D0=BF=D1=80=D0=BE=D0=BC?= =?UTF-8?q?=D0=BF=D1=82=D0=B0=20=D1=83=20=D1=81=D1=83=D1=89=D0=B5=D1=81?= =?UTF-8?q?=D1=82=D0=B2=D1=83=D1=8E=D1=89=D0=B8=D1=85=20=D0=91=D0=94=20+?= =?UTF-8?q?=20=D0=BF=D1=80=D0=B0=D0=B2=D0=B8=D0=BB=D0=BE=20=D0=B2=20AGENTS?= =?UTF-8?q?.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit INSERT ... ON CONFLICT (key) DO NOTHING вставляет значение только если ключа ещё нет, поэтому новый шаблон промпта доезжал лишь до свежих установок — у всех, кто уже пользовался разделом sec-lesson-ai, в settings оставался старый промпт. - в db/migration.sql добавлен идемпотентный UPDATE settings SET value = '<новый промпт>' WHERE key = 'lesson_ai_prompt' AND value IN ('<старый дефолт>') по образцу уже существующей миграции для ai_prompt - условие по value IN (...) обязательно: без него миграция затёрла бы промпт, отредактированный админом в UI - в db/init.sql UPDATE не добавляется: файл выполняется только на пустой БД - AGENTS.md, раздел 2: правило «Сид настроек» (DO NOTHING не обновляет существующие значения) и «Проверка сида» (прогон в транзакции с откатом) - AGENTS.md, раздел 3d: смена шаблона промпта — четыре правки, а не три; все текстовые копии должны быть побайтово идентичны LESSON_AI_DEFAULT_PROMPT Проверено на postgres:16 в транзакции с откатом, ON_ERROR_STOP=1 - старый дефолт (935 симв.) -> миграция -> новый (2348 симв.) - повторный прогон ничего не меняет (идемпотентно) - кастомный промпт админа миграцией не затрагивается --- AGENTS.md | 5 ++++- db/migration.sql | 56 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 60 insertions(+), 1 deletion(-) diff --git a/AGENTS.md b/AGENTS.md index 7f630fa..4eec531 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -60,6 +60,8 @@ This document defines how AI agents should work with the WhatIDo codebase. Follo ### 2. Database - **Schema**: Defined in `db/init.sql` (runs on first container start) - **Migrations**: `db/migration.sql` for existing DBs — update both when changing schema +- **Сид настроек**: `INSERT ... ON CONFLICT (key) DO NOTHING` вставляет значение только если ключа ещё нет. Если дефолт **изменился**, одного `INSERT` мало — на существующей БД останется старое значение. Обязательно добавляй в `db/migration.sql` идемпотентный `UPDATE settings SET value = '<новый>' WHERE key = '<ключ>' AND value IN ('<старый дефолт 1>', ...)`, как это сделано для `ai_prompt` и `lesson_ai_prompt`. Условие по `value IN (...)` обязательно: без него миграция затрёт промпт, который админ отредактировал в UI. В `db/init.sql` такой `UPDATE` не нужен — файл выполняется только на пустой БД +- **Проверка сида**: перед коммитом убедись, что новое значение реально доедет до существующих БД — прогони `db/migration.sql` в транзакции с откатом (`BEGIN;` + файл + `SELECT` + `ROLLBACK;`) и убедись, что `ON_ERROR_STOP=1` не дал ошибок - **Connection**: Single `Pool` from `pg`, `DATABASE_URL` from env - **Queries**: Parameterized only (`$1`, `$2`...), never string interpolation - **Transactions**: Use `client.query('BEGIN')` / `COMMIT` / `ROLLBACK` for multi-statement ops @@ -111,7 +113,8 @@ This document defines how AI agents should work with the WhatIDo codebase. Follo ### 3d. Отчёты о занятии и проверка по шаблону (`server.js`, `worker.js`, `public/`) - **Таблицы**: `lesson_reports` (+ `text_original`, `text_ai`, `ai_status`, `ai_checked_at`, `ai_error`) и `lesson_report_versions` (история версий: `text`, `source` = `manual` | `ai` | `restore`). Схема — в `db/init.sql`, `db/migration.sql` и `ensureLessonReportsTable()` -- **Настройки**: `lesson_ai_enabled` (`'true'` / `'false'` — общий выключатель) и `lesson_ai_prompt` (шаблон делового сообщения + правила, пример вставляется в `db/init.sql`, `db/migration.sql` и в `LESSON_AI_DEFAULT_PROMPT` в `server.js`). Раздел в UI — `sec-lesson-ai` на `public/settings.html` +- **Настройки**: `lesson_ai_enabled` (`'true'` / `'false'` — общий выклюжатель) и `lesson_ai_prompt` (шаблон делового сообщения + правила, пример вставляется в `db/init.sql`, `db/migration.sql` и в `LESSON_AI_DEFAULT_PROMPT` в `server.js`). Раздел в UI — `sec-lesson-ai` на `public/settings.html` +- **Смена шаблона промпта — это четыре правки, а не три**: новое значение в `LESSON_AI_DEFAULT_PROMPT` (`server.js`), `db/init.sql`, `db/migration.sql` (там же `INSERT` для свежих БД) и **обязательно** `UPDATE settings SET value = '<новый>' WHERE key = 'lesson_ai_prompt' AND value IN ('<старый дефолт>')` в `db/migration.sql` — без него правка в SQL-файлах действует только на свежие установки, а у всех, кто уже пользовался разделом `sec-lesson-ai`, в `settings` останется старый промпт (см. «Сид настроек» в разделе 2). Все три текстовые копии должны быть побайтово идентичны `LESSON_AI_DEFAULT_PROMPT` - **Флаг из UI**: чекбокс `#lessonAiCheck` в модалке `#lessonModal` — включён при создании, выключен при редактировании (`resetLessonModalFields` / `fillLessonModalFromReport`). Уходит в теле как `ai_check` - **Роут не ждёт модель**: `POST`/`PUT /api/lesson-reports` при `ai_check: true` сохраняют отчёт как есть и ставят `ai_status = 'pending'`, затем `wakeLessonAiWorker()`. Ответ возвращается сразу — не блокируйте HTTP-запрос вызовом модели - **Воркер**: `createLessonReportChecker` в `worker.js` забирает `pending` через `FOR UPDATE OF lr SKIP LOCKED`, шлёт в модель текст + контекст (группа, дата, время), результат: без изменений → `skipped`, переписан → `done` (новый текст в `text` и `text_ai`), сбой → до 3 попыток, затем `error` diff --git a/db/migration.sql b/db/migration.sql index f2ec23f..43ce1bd 100644 --- a/db/migration.sql +++ b/db/migration.sql @@ -372,4 +372,60 @@ INSERT INTO settings (key, value) VALUES ('lesson_ai_prompt', 'Ты — реда ГЛАВНОЕ ПРАВИЛО: Отрабатывай ровно по этому исходному тексту. Ничего из примера выше в свой текст не переноси: тема, программа, персонажи и детали из примера не твои.') ON CONFLICT (key) DO NOTHING; +UPDATE settings +SET value = 'Ты — редактор сообщений тьютора детской IT-школы. + +Переработай исходный текст занятия так, чтобы он звучал естественно и грамотно, как будто его написал живой тьютор родителю, а не нейросеть. + +ГЛАВНОЕ: +- Не добавляй информацию, которой нет в исходном тексте. Сохрани все факты и смысл. +- Исправь ошибки, повторы и неудачные формулировки. Убери канцелярит, шаблонные фразы и «ИИ-язык». +- Не используй чрезмерную похвалу и не превращай обычное занятие в достижение мирового масштала. + +СТИЛЬ И СТРУКТУРА: +- Один цельный абзац, 2–4 предложения, от третьего лица. +- Первое предложение начинай со слов «На занятии ребята …». +- Если в исходном тексте описана практическая часть, начни её со слов «В конце занятия …» или «Затем …». Нет практики в исходнике — не придумывай её. +- Вместо «они» пиши «каждый». Обращение на «вы» не используй. +- Коротко скажи, с чем познакомились или что изучали; затем — что конкретно делали; в конце — что сделал самостоятельно. +- Сократи перечисления, объединяй их через «и», «а также», не повторяй одно и то же разными словами. +- Убери разговорные и оценочные обороты: «было весело», «очень», «классно». + +ФОРМАТ: +- Без заголовков, списков, markdown, подписей и пояснений. +- Не начинай со слов «Сегодня», «Вчера», «Дата», «Группа», «Время» и вообще не упоминай группу, дату и время занятия. +- Не начинай с «На данном занятии» или «В рамках занятия». +- Не оборачивай ответ в кавычки. +- Не добавляй лишних предложений: если исходный текст уже написан нормально, не переписывай его ради переписывания. + +ПРИМЕР ПРЕОБРАЗОВАНИЯ (бери отсюда только формулировки, тему и факты примера в свой текст не переноси): +Исходный текст: +«Ребята познакомились с программой Scratch Jr, научились выбирать фон, добавлять, изменять и создавать своих персонажей. В завершении занятия они выполнили практическое индивидуальное задание по созданию собственной анимации и небольшой программы». +Хороший результат: +«На занятии ребята познакомились со Scratch Jr: научились выбирать фон, добавлять и изменять персонажей, а также создавать своих героев. В конце занятия каждый самостоятельно выполнил небольшое практическое задание — придумал свою анимацию и собрал простую программу». + +ГЛАВНОЕ ПРАВИЛО: +Отрабатывай ровно по этому исходному тексту. Ничего из примера выше в свой текст не переноси: тема, программа, персонажи и детали из примера не твои.' +WHERE key = 'lesson_ai_prompt' AND value IN ( + 'Ты — редактор деловых отчётов образовательного центра. + +Твоя задача — привести текст отчёта о занятии, написанный тьютором, к деловому стилю по шаблону ниже. + +ШАБЛОН ДЕЛОВОГО СООБЩЕНИЯ: +Отчёт о проведённом занятии +Дата: <дата занятия> +Группа: <название группы> +Темы: <перечень тем> +Практика: <задания> +Домашнее задание: <что задано> + +ПРАВИЛА: +1. Сначала сравни исходный текст с шаблоном. Если текст уже соответствует шаблону (та же структура, порядок и стиль) — верни его БЕЗ ИЗМЕНЕНИЙ, дословно. +2. Если текст не соответствует шаблону — перепиши его по шаблону, сохранив весь смысл и факты. +3. НЕ выдумывай тем, дат, заданий и оценок, которых нет в исходном тексте. Если данных нет — не добавляй раздел. +4. Обращение на «вы», без эмодзи и без восклицательных знаков, кратко и по делу. +5. Не добавляй приветствия, подписи и какие-либо пояснения. +6. Верни ТОЛЬКО итоговый текст отчёта — без кавычек, без markdown и без названия формата.' +); + INSERT INTO settings (key, value) VALUES ('notify_lesson_ai_formatted', 'false') ON CONFLICT (key) DO NOTHING;