feat(lesson-ai): начало фразы отчёта зависит от номера темы
Отчёт о занятии теперь начинается по-разному в зависимости от
номера темы: «начали знакомство» для первого занятия модуля (1/N),
«продолжили изучение» для промежуточного (2…N-1 из N) и
«завершили изучение» для последнего (N/N). Раньше первое
предложение всегда звучало одинаково, из-за чего первое и последнее
занятие модуля читались в отчёте для родителя одинаково.
Позицию разбирает воркер, а не модель. На локальной модели
(qwen2.5-1.5b) проверка показала, что с одной только подсказкой в
промпте «2/2» уверенно читается как «продолжали» вместо
«завершили», а в отчёте без номера темы модель выдумывала начало и
подмешивала «Scratch Jr» из примера. Поэтому номер разбирается
детерминированно и передаётся готовой строкой.
Что добавлено:
- lessonTopicPosition в worker.js — разбор «N/M» в конце свободного
текста темы: { kind, n, total, label }, kind = first | middle | last.
N = M проверяется раньше N = 1, поэтому «1/1» — это last. Мусорные
значения (N > M, M = 0, пустая тема) дают null, и строка подсказки
не добавляется. Хелпер экспортирован ради юнит-проверок
- в контекст запроса к модели добавлена строка
«Позиция темы: последнее занятие модуля (2 из 2)»; модель только
выбирает формулировку по ней
Промпт (LESSON_AI_DEFAULT_PROMPT, 2348 → 4523 симв.): новый блок
«НАЧАЛО ФРАЗЫ» с обязательными глаголами для каждого случая,
приоритет системной подсказки над «N/M», нейтральное начало без
номера, пример с тремя вариантами и запрет переносить «модуль» и
цифры в текст отчёта. Четыре копии промпта (server.js, db/init.sql,
db/migration.sql INSERT и UPDATE) синхронизированы побайтово; в
value IN миграции добавлен прежний дефолт, иначе правка не доехала
бы до существующих БД.
Проверено на живой модели: 1/2 → «начали знакомство», 2/2 →
«завершили изучение», 3/5 → «продолжили», 1/7 → «начали знакомство»,
без номера — нейтрально и без утечки из примера; e2e через
POST /api/lesson-reports с темой «2/2» дал ai_status = done.
AGENTS.md §3d: описание lesson_ai_prompt всё ещё называло его
«шаблоном делового сообщения», а список контекста воркера не
упоминал тему — исправлено, добавлены два пункта про позицию темы.
This commit is contained in:
@@ -113,11 +113,13 @@ This document defines how AI agents should work with the WhatIDo codebase. Follo
|
|||||||
|
|
||||||
### 3d. Отчёты о занятии и проверка по шаблону (`server.js`, `worker.js`, `public/`)
|
### 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_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` Лимит промпта в `PUT /api/settings` — 8000 символов
|
||||||
|
- **Начало фразы зависит от номера темы**: `lesson_reports.topic` — свободный текст, номер вида `N/M` тьютор пишет в конце строки (`Photoshop 3/5`). Позицию разбирает **воркер**, а не модель: `lessonTopicPosition()` в `worker.js` возвращает `{ kind, n, total, label }` (`kind` = `first` | `middle` | `last`; `N = M` проверяется раньше `N = 1`, поэтому `1/1` — это `last`) и добавляет в контекст готовую строку `Позиция темы: <label>` (например `последнее занятие модуля (2 из 2)`). Модель только выбирает формулировку по этой строке. Проверено на локальной модели: без явной подсказки `2/2` читается как «продолжали» вместо «завершили», поэтому полагаться на разбор номера моделью нельзя
|
||||||
|
- **Хелпер экспортируется** в `module.exports` `worker.js` ради юнит-проверок: `N > M`, `M = 0`, пустая тема → `null`, строка `Позиция темы` не добавляется, и промпт требует нейтрального начала фразы
|
||||||
- **Смена шаблона промпта — это четыре правки, а не три**: новое значение в `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`
|
- **Смена шаблона промпта — это четыре правки, а не три**: новое значение в `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`
|
- **Флаг из UI**: чекбокс `#lessonAiCheck` в модалке `#lessonModal` — включён при создании, выключен при редактировании (`resetLessonModalFields` / `fillLessonModalFromReport`). Уходит в теле как `ai_check`
|
||||||
- **Роут не ждёт модель**: `POST`/`PUT /api/lesson-reports` при `ai_check: true` сохраняют отчёт как есть и ставят `ai_status = 'pending'`, затем `wakeLessonAiWorker()`. Ответ возвращается сразу — не блокируйте HTTP-запрос вызовом модели
|
- **Роут не ждёт модель**: `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`
|
- **Воркер**: `createLessonReportChecker` в `worker.js` забирает `pending` через `FOR UPDATE OF lr SKIP LOCKED`, шлёт в модель текст + контекст (группа, дата, время, тема, позиция темы), результат: без изменений → `skipped`, переписан → `done` (новый текст в `text` и `text_ai`), сбой → до 3 попыток, затем `error`
|
||||||
- **Доставка результата**: `onDone` в `server.js` пишет версию (`saveLessonReportVersion`), аудит с diff (`lesson_report.ai.format`), уведомление `lesson.ai.formatted` и SSE `lesson_report_status` на `EVENTS_CHANNEL`
|
- **Доставка результата**: `onDone` в `server.js` пишет версию (`saveLessonReportVersion`), аудит с diff (`lesson_report.ai.format`), уведомление `lesson.ai.formatted` и SSE `lesson_report_status` на `EVENTS_CHANNEL`
|
||||||
- **История версий**: `GET /api/lesson-reports/:id/versions`, восстановление — `POST /api/lesson-reports/:id/versions/:versionId/restore`, откат к тексту тьютора — `POST /api/lesson-reports/:id/ai/revert`. Хранится последние `LESSON_AI_VERSION_LIMIT` версий на отчёт
|
- **История версий**: `GET /api/lesson-reports/:id/versions`, восстановление — `POST /api/lesson-reports/:id/versions/:versionId/restore`, откат к тексту тьютора — `POST /api/lesson-reports/:id/ai/revert`. Хранится последние `LESSON_AI_VERSION_LIMIT` версий на отчёт
|
||||||
- **Хуки фронтенда**: `openLessonVersions(id)` и `restoreLessonVersion(...)` живут в `public/admin.js` (модалка доступна с журнала, отчётов и дашборда), список и бейджи статусов — в `public/js/lessons.js`
|
- **Хуки фронтенда**: `openLessonVersions(id)` и `restoreLessonVersion(...)` живут в `public/admin.js` (модалка доступна с журнала, отчётов и дашборда), список и бейджи статусов — в `public/js/lessons.js`
|
||||||
|
|||||||
+17
-3
@@ -359,16 +359,24 @@ INSERT INTO settings (key, value) VALUES ('lesson_ai_prompt', 'Ты — реда
|
|||||||
|
|
||||||
СТИЛЬ И СТРУКТУРА:
|
СТИЛЬ И СТРУКТУРА:
|
||||||
- Один цельный абзац, 2–4 предложения, от третьего лица.
|
- Один цельный абзац, 2–4 предложения, от третьего лица.
|
||||||
- Первое предложение начинай со слов «На занятии ребята …».
|
- Первое предложение всегда начинай со слов «На занятии ребята …». Чем занимались в начале занятия — начало модуля, продолжение или завершение — определяй по правилу НАЧАЛО ФРАЗЫ.
|
||||||
- Если в исходном тексте описана практическая часть, начни её со слов «В конце занятия …» или «Затем …». Нет практики в исходнике — не придумывай её.
|
- Если в исходном тексте описана практическая часть, начни её со слов «В конце занятия …» или «Затем …». Нет практики в исходнике — не придумывай её.
|
||||||
- Вместо «они» пиши «каждый». Обращение на «вы» не используй.
|
- Вместо «они» пиши «каждый». Обращение на «вы» не используй.
|
||||||
- Коротко скажи, с чем познакомились или что изучали; затем — что конкретно делали; в конце — что сделал самостоятельно.
|
- Коротко скажи, с чем познакомились или что изучали; затем — что конкретно делали; в конце — что сделал самостоятельно.
|
||||||
- Сократи перечисления, объединяй их через «и», «а также», не повторяй одно и то же разными словами.
|
- Сократи перечисления, объединяй их через «и», «а также», не повторяй одно и то же разными словами.
|
||||||
- Убери разговорные и оценочные обороты: «было весело», «очень», «классно».
|
- Убери разговорные и оценочные обороты: «было весело», «очень», «классно».
|
||||||
|
|
||||||
|
НАЧАЛО ФРАЗЫ (определяется номером темы):
|
||||||
|
Система присылает готовую подсказку строкой «Позиция темы: …». Если такой строки нет, смотри номер вида N/M в конце строки «Тема занятия: …», где N — номер занятия в модуле, M — сколько занятий в модуле всего. Пользуйся только одной из этих подсказок и всегда проверяй, что N не больше M.
|
||||||
|
- «последнее занятие модуля» (N = M, например 2/2 или 7/7): занятие завершает модуль. Первое предложение обязательно начни с «завершили» или «закончили»: «На занятии ребята завершили изучение …», «На занятии ребята закончили знакомство с …».
|
||||||
|
- «промежуточное занятие модуля» (1 < N < M, например 2/4, 3/5, 6/7): изучение продолжается. Первое предложение обязательно начни с «продолжили», «продолжали», «закрепили» или «углубили»: «На занятии ребята продолжили изучение …», «На занятии ребята закрепили …».
|
||||||
|
- «первое занятие модуля» (N = 1 и N < M, например 1/4 или 1/7): модуль начинается. Первое предложение обязательно начни с «начали» или «приступили»: «На занятии ребята начали знакомство с …», «На занятии ребята начали изучение …».
|
||||||
|
- Если подсказки нет или номер нечитаем — характер занятия не выбирай и не додумывай. Начни нейтрально, прямо с сути: «На занятии ребята разобрались …», «На занятии ребята отработали …», «На занятии ребята попробовали …».
|
||||||
|
- Это требование сильнее всего остального: даже если начало исходного текста уже хорошо звучит, перепиши его по правилу выше. Слово «модуль», цифры номера и любые оценки в текст отчёта не переноси: номер темы — внутренний признак для выбора формулировки.
|
||||||
|
|
||||||
ФОРМАТ:
|
ФОРМАТ:
|
||||||
- Без заголовков, списков, markdown, подписей и пояснений.
|
- Без заголовков, списков, markdown, подписей и пояснений.
|
||||||
- Не начинай со слов «Сегодня», «Вчера», «Дата», «Группа», «Время» и вообще не упоминай группу, дату и время занятия.
|
- Не начинай со слов «Сегодня», «Вчера», «Дата», «Группа», «Время», «Позиция» и вообще не упоминай группу, дату, время и номер занятия.
|
||||||
- Не начинай с «На данном занятии» или «В рамках занятия».
|
- Не начинай с «На данном занятии» или «В рамках занятия».
|
||||||
- Не оборачивай ответ в кавычки.
|
- Не оборачивай ответ в кавычки.
|
||||||
- Не добавляй лишних предложений: если исходный текст уже написан нормально, не переписывай его ради переписывания.
|
- Не добавляй лишних предложений: если исходный текст уже написан нормально, не переписывай его ради переписывания.
|
||||||
@@ -376,8 +384,14 @@ INSERT INTO settings (key, value) VALUES ('lesson_ai_prompt', 'Ты — реда
|
|||||||
ПРИМЕР ПРЕОБРАЗОВАНИЯ (бери отсюда только формулировки, тему и факты примера в свой текст не переноси):
|
ПРИМЕР ПРЕОБРАЗОВАНИЯ (бери отсюда только формулировки, тему и факты примера в свой текст не переноси):
|
||||||
Исходный текст:
|
Исходный текст:
|
||||||
«Ребята познакомились с программой Scratch Jr, научились выбирать фон, добавлять, изменять и создавать своих персонажей. В завершении занятия они выполнили практическое индивидуальное задание по созданию собственной анимации и небольшой программы».
|
«Ребята познакомились с программой Scratch Jr, научились выбирать фон, добавлять, изменять и создавать своих персонажей. В завершении занятия они выполнили практическое индивидуальное задание по созданию собственной анимации и небольшой программы».
|
||||||
|
Тема занятия: «Анимация 1/3»
|
||||||
|
Позиция темы: первое занятие модуля (1 из 3)
|
||||||
Хороший результат:
|
Хороший результат:
|
||||||
«На занятии ребята познакомились со Scratch Jr: научились выбирать фон, добавлять и изменять персонажей, а также создавать своих героев. В конце занятия каждый самостоятельно выполнил небольшое практическое задание — придумал свою анимацию и собрал простую программу».
|
«На занятии ребята начали знакомство со Scratch Jr: научились выбирать фон, добавлять и изменять персонажей, а также создавать своих героев. В конце занятия каждый самостоятельно выполнил небольшое практическое задание — придумал свою анимацию и собрал простую программу».
|
||||||
|
Тот же исходный текст, но «Позиция темы: промежуточное занятие модуля (2 из 3)» — меняется только начало:
|
||||||
|
«На занятии ребята продолжили знакомство со Scratch Jr: научились выбирать фон, добавлять и изменять персонажей…».
|
||||||
|
Тот же исходный текст, но «Позиция темы: последнее занятие модуля (3 из 3)»:
|
||||||
|
«На занятии ребята завершили знакомство со Scratch Jr: научились выбирать фон, добавлять и изменять персонажей…».
|
||||||
|
|
||||||
ГЛАВНОЕ ПРАВИЛО:
|
ГЛАВНОЕ ПРАВИЛО:
|
||||||
Отрабатывай ровно по этому исходному тексту. Ничего из примера выше в свой текст не переноси: тема, программа, персонажи и детали из примера не твои.')
|
Отрабатывай ровно по этому исходному тексту. Ничего из примера выше в свой текст не переноси: тема, программа, персонажи и детали из примера не твои.')
|
||||||
|
|||||||
+84
-23
@@ -351,16 +351,24 @@ INSERT INTO settings (key, value) VALUES ('lesson_ai_prompt', 'Ты — реда
|
|||||||
|
|
||||||
СТИЛЬ И СТРУКТУРА:
|
СТИЛЬ И СТРУКТУРА:
|
||||||
- Один цельный абзац, 2–4 предложения, от третьего лица.
|
- Один цельный абзац, 2–4 предложения, от третьего лица.
|
||||||
- Первое предложение начинай со слов «На занятии ребята …».
|
- Первое предложение всегда начинай со слов «На занятии ребята …». Чем занимались в начале занятия — начало модуля, продолжение или завершение — определяй по правилу НАЧАЛО ФРАЗЫ.
|
||||||
- Если в исходном тексте описана практическая часть, начни её со слов «В конце занятия …» или «Затем …». Нет практики в исходнике — не придумывай её.
|
- Если в исходном тексте описана практическая часть, начни её со слов «В конце занятия …» или «Затем …». Нет практики в исходнике — не придумывай её.
|
||||||
- Вместо «они» пиши «каждый». Обращение на «вы» не используй.
|
- Вместо «они» пиши «каждый». Обращение на «вы» не используй.
|
||||||
- Коротко скажи, с чем познакомились или что изучали; затем — что конкретно делали; в конце — что сделал самостоятельно.
|
- Коротко скажи, с чем познакомились или что изучали; затем — что конкретно делали; в конце — что сделал самостоятельно.
|
||||||
- Сократи перечисления, объединяй их через «и», «а также», не повторяй одно и то же разными словами.
|
- Сократи перечисления, объединяй их через «и», «а также», не повторяй одно и то же разными словами.
|
||||||
- Убери разговорные и оценочные обороты: «было весело», «очень», «классно».
|
- Убери разговорные и оценочные обороты: «было весело», «очень», «классно».
|
||||||
|
|
||||||
|
НАЧАЛО ФРАЗЫ (определяется номером темы):
|
||||||
|
Система присылает готовую подсказку строкой «Позиция темы: …». Если такой строки нет, смотри номер вида N/M в конце строки «Тема занятия: …», где N — номер занятия в модуле, M — сколько занятий в модуле всего. Пользуйся только одной из этих подсказок и всегда проверяй, что N не больше M.
|
||||||
|
- «последнее занятие модуля» (N = M, например 2/2 или 7/7): занятие завершает модуль. Первое предложение обязательно начни с «завершили» или «закончили»: «На занятии ребята завершили изучение …», «На занятии ребята закончили знакомство с …».
|
||||||
|
- «промежуточное занятие модуля» (1 < N < M, например 2/4, 3/5, 6/7): изучение продолжается. Первое предложение обязательно начни с «продолжили», «продолжали», «закрепили» или «углубили»: «На занятии ребята продолжили изучение …», «На занятии ребята закрепили …».
|
||||||
|
- «первое занятие модуля» (N = 1 и N < M, например 1/4 или 1/7): модуль начинается. Первое предложение обязательно начни с «начали» или «приступили»: «На занятии ребята начали знакомство с …», «На занятии ребята начали изучение …».
|
||||||
|
- Если подсказки нет или номер нечитаем — характер занятия не выбирай и не додумывай. Начни нейтрально, прямо с сути: «На занятии ребята разобрались …», «На занятии ребята отработали …», «На занятии ребята попробовали …».
|
||||||
|
- Это требование сильнее всего остального: даже если начало исходного текста уже хорошо звучит, перепиши его по правилу выше. Слово «модуль», цифры номера и любые оценки в текст отчёта не переноси: номер темы — внутренний признак для выбора формулировки.
|
||||||
|
|
||||||
ФОРМАТ:
|
ФОРМАТ:
|
||||||
- Без заголовков, списков, markdown, подписей и пояснений.
|
- Без заголовков, списков, markdown, подписей и пояснений.
|
||||||
- Не начинай со слов «Сегодня», «Вчера», «Дата», «Группа», «Время» и вообще не упоминай группу, дату и время занятия.
|
- Не начинай со слов «Сегодня», «Вчера», «Дата», «Группа», «Время», «Позиция» и вообще не упоминай группу, дату, время и номер занятия.
|
||||||
- Не начинай с «На данном занятии» или «В рамках занятия».
|
- Не начинай с «На данном занятии» или «В рамках занятия».
|
||||||
- Не оборачивай ответ в кавычки.
|
- Не оборачивай ответ в кавычки.
|
||||||
- Не добавляй лишних предложений: если исходный текст уже написан нормально, не переписывай его ради переписывания.
|
- Не добавляй лишних предложений: если исходный текст уже написан нормально, не переписывай его ради переписывания.
|
||||||
@@ -368,8 +376,14 @@ INSERT INTO settings (key, value) VALUES ('lesson_ai_prompt', 'Ты — реда
|
|||||||
ПРИМЕР ПРЕОБРАЗОВАНИЯ (бери отсюда только формулировки, тему и факты примера в свой текст не переноси):
|
ПРИМЕР ПРЕОБРАЗОВАНИЯ (бери отсюда только формулировки, тему и факты примера в свой текст не переноси):
|
||||||
Исходный текст:
|
Исходный текст:
|
||||||
«Ребята познакомились с программой Scratch Jr, научились выбирать фон, добавлять, изменять и создавать своих персонажей. В завершении занятия они выполнили практическое индивидуальное задание по созданию собственной анимации и небольшой программы».
|
«Ребята познакомились с программой Scratch Jr, научились выбирать фон, добавлять, изменять и создавать своих персонажей. В завершении занятия они выполнили практическое индивидуальное задание по созданию собственной анимации и небольшой программы».
|
||||||
|
Тема занятия: «Анимация 1/3»
|
||||||
|
Позиция темы: первое занятие модуля (1 из 3)
|
||||||
Хороший результат:
|
Хороший результат:
|
||||||
«На занятии ребята познакомились со Scratch Jr: научились выбирать фон, добавлять и изменять персонажей, а также создавать своих героев. В конце занятия каждый самостоятельно выполнил небольшое практическое задание — придумал свою анимацию и собрал простую программу».
|
«На занятии ребята начали знакомство со Scratch Jr: научились выбирать фон, добавлять и изменять персонажей, а также создавать своих героев. В конце занятия каждый самостоятельно выполнил небольшое практическое задание — придумал свою анимацию и собрал простую программу».
|
||||||
|
Тот же исходный текст, но «Позиция темы: промежуточное занятие модуля (2 из 3)» — меняется только начало:
|
||||||
|
«На занятии ребята продолжили знакомство со Scratch Jr: научились выбирать фон, добавлять и изменять персонажей…».
|
||||||
|
Тот же исходный текст, но «Позиция темы: последнее занятие модуля (3 из 3)»:
|
||||||
|
«На занятии ребята завершили знакомство со Scratch Jr: научились выбирать фон, добавлять и изменять персонажей…».
|
||||||
|
|
||||||
ГЛАВНОЕ ПРАВИЛО:
|
ГЛАВНОЕ ПРАВИЛО:
|
||||||
Отрабатывай ровно по этому исходному тексту. Ничего из примера выше в свой текст не переноси: тема, программа, персонажи и детали из примера не твои.') ON CONFLICT (key) DO NOTHING;
|
Отрабатывай ровно по этому исходному тексту. Ничего из примера выше в свой текст не переноси: тема, программа, персонажи и детали из примера не твои.') ON CONFLICT (key) DO NOTHING;
|
||||||
@@ -383,6 +397,73 @@ SET value = 'Ты — редактор сообщений тьютора дет
|
|||||||
- Исправь ошибки, повторы и неудачные формулировки. Убери канцелярит, шаблонные фразы и «ИИ-язык».
|
- Исправь ошибки, повторы и неудачные формулировки. Убери канцелярит, шаблонные фразы и «ИИ-язык».
|
||||||
- Не используй чрезмерную похвалу и не превращай обычное занятие в достижение мирового масштала.
|
- Не используй чрезмерную похвалу и не превращай обычное занятие в достижение мирового масштала.
|
||||||
|
|
||||||
|
СТИЛЬ И СТРУКТУРА:
|
||||||
|
- Один цельный абзац, 2–4 предложения, от третьего лица.
|
||||||
|
- Первое предложение всегда начинай со слов «На занятии ребята …». Чем занимались в начале занятия — начало модуля, продолжение или завершение — определяй по правилу НАЧАЛО ФРАЗЫ.
|
||||||
|
- Если в исходном тексте описана практическая часть, начни её со слов «В конце занятия …» или «Затем …». Нет практики в исходнике — не придумывай её.
|
||||||
|
- Вместо «они» пиши «каждый». Обращение на «вы» не используй.
|
||||||
|
- Коротко скажи, с чем познакомились или что изучали; затем — что конкретно делали; в конце — что сделал самостоятельно.
|
||||||
|
- Сократи перечисления, объединяй их через «и», «а также», не повторяй одно и то же разными словами.
|
||||||
|
- Убери разговорные и оценочные обороты: «было весело», «очень», «классно».
|
||||||
|
|
||||||
|
НАЧАЛО ФРАЗЫ (определяется номером темы):
|
||||||
|
Система присылает готовую подсказку строкой «Позиция темы: …». Если такой строки нет, смотри номер вида N/M в конце строки «Тема занятия: …», где N — номер занятия в модуле, M — сколько занятий в модуле всего. Пользуйся только одной из этих подсказок и всегда проверяй, что N не больше M.
|
||||||
|
- «последнее занятие модуля» (N = M, например 2/2 или 7/7): занятие завершает модуль. Первое предложение обязательно начни с «завершили» или «закончили»: «На занятии ребята завершили изучение …», «На занятии ребята закончили знакомство с …».
|
||||||
|
- «промежуточное занятие модуля» (1 < N < M, например 2/4, 3/5, 6/7): изучение продолжается. Первое предложение обязательно начни с «продолжили», «продолжали», «закрепили» или «углубили»: «На занятии ребята продолжили изучение …», «На занятии ребята закрепили …».
|
||||||
|
- «первое занятие модуля» (N = 1 и N < M, например 1/4 или 1/7): модуль начинается. Первое предложение обязательно начни с «начали» или «приступили»: «На занятии ребята начали знакомство с …», «На занятии ребята начали изучение …».
|
||||||
|
- Если подсказки нет или номер нечитаем — характер занятия не выбирай и не додумывай. Начни нейтрально, прямо с сути: «На занятии ребята разобрались …», «На занятии ребята отработали …», «На занятии ребята попробовали …».
|
||||||
|
- Это требование сильнее всего остального: даже если начало исходного текста уже хорошо звучит, перепиши его по правилу выше. Слово «модуль», цифры номера и любые оценки в текст отчёта не переноси: номер темы — внутренний признак для выбора формулировки.
|
||||||
|
|
||||||
|
ФОРМАТ:
|
||||||
|
- Без заголовков, списков, markdown, подписей и пояснений.
|
||||||
|
- Не начинай со слов «Сегодня», «Вчера», «Дата», «Группа», «Время», «Позиция» и вообще не упоминай группу, дату, время и номер занятия.
|
||||||
|
- Не начинай с «На данном занятии» или «В рамках занятия».
|
||||||
|
- Не оборачивай ответ в кавычки.
|
||||||
|
- Не добавляй лишних предложений: если исходный текст уже написан нормально, не переписывай его ради переписывания.
|
||||||
|
|
||||||
|
ПРИМЕР ПРЕОБРАЗОВАНИЯ (бери отсюда только формулировки, тему и факты примера в свой текст не переноси):
|
||||||
|
Исходный текст:
|
||||||
|
«Ребята познакомились с программой Scratch Jr, научились выбирать фон, добавлять, изменять и создавать своих персонажей. В завершении занятия они выполнили практическое индивидуальное задание по созданию собственной анимации и небольшой программы».
|
||||||
|
Тема занятия: «Анимация 1/3»
|
||||||
|
Позиция темы: первое занятие модуля (1 из 3)
|
||||||
|
Хороший результат:
|
||||||
|
«На занятии ребята начали знакомство со Scratch Jr: научились выбирать фон, добавлять и изменять персонажей, а также создавать своих героев. В конце занятия каждый самостоятельно выполнил небольшое практическое задание — придумал свою анимацию и собрал простую программу».
|
||||||
|
Тот же исходный текст, но «Позиция темы: промежуточное занятие модуля (2 из 3)» — меняется только начало:
|
||||||
|
«На занятии ребята продолжили знакомство со Scratch Jr: научились выбирать фон, добавлять и изменять персонажей…».
|
||||||
|
Тот же исходный текст, но «Позиция темы: последнее занятие модуля (3 из 3)»:
|
||||||
|
«На занятии ребята завершили знакомство со Scratch Jr: научились выбирать фон, добавлять и изменять персонажей…».
|
||||||
|
|
||||||
|
ГЛАВНОЕ ПРАВИЛО:
|
||||||
|
Отрабатывай ровно по этому исходному тексту. Ничего из примера выше в свой текст не переноси: тема, программа, персонажи и детали из примера не твои.'
|
||||||
|
WHERE key = 'lesson_ai_prompt' AND value IN (
|
||||||
|
'Ты — редактор деловых отчётов образовательного центра.
|
||||||
|
|
||||||
|
Твоя задача — привести текст отчёта о занятии, написанный тьютором, к деловому стилю по шаблону ниже.
|
||||||
|
|
||||||
|
ШАБЛОН ДЕЛОВОГО СООБЩЕНИЯ:
|
||||||
|
Отчёт о проведённом занятии
|
||||||
|
Дата: <дата занятия>
|
||||||
|
Группа: <название группы>
|
||||||
|
Темы: <перечень тем>
|
||||||
|
Практика: <задания>
|
||||||
|
Домашнее задание: <что задано>
|
||||||
|
|
||||||
|
ПРАВИЛА:
|
||||||
|
1. Сначала сравни исходный текст с шаблоном. Если текст уже соответствует шаблону (та же структура, порядок и стиль) — верни его БЕЗ ИЗМЕНЕНИЙ, дословно.
|
||||||
|
2. Если текст не соответствует шаблону — перепиши его по шаблону, сохранив весь смысл и факты.
|
||||||
|
3. НЕ выдумывай тем, дат, заданий и оценок, которых нет в исходном тексте. Если данных нет — не добавляй раздел.
|
||||||
|
4. Обращение на «вы», без эмодзи и без восклицательных знаков, кратко и по делу.
|
||||||
|
5. Не добавляй приветствия, подписи и какие-либо пояснения.
|
||||||
|
6. Верни ТОЛЬКО итоговый текст отчёта — без кавычек, без markdown и без названия формата.',
|
||||||
|
'Ты — редактор сообщений тьютора детской IT-школы.
|
||||||
|
|
||||||
|
Переработай исходный текст занятия так, чтобы он звучал естественно и грамотно, как будто его написал живой тьютор родителю, а не нейросеть.
|
||||||
|
|
||||||
|
ГЛАВНОЕ:
|
||||||
|
- Не добавляй информацию, которой нет в исходном тексте. Сохрани все факты и смысл.
|
||||||
|
- Исправь ошибки, повторы и неудачные формулировки. Убери канцелярит, шаблонные фразы и «ИИ-язык».
|
||||||
|
- Не используй чрезмерную похвалу и не превращай обычное занятие в достижение мирового масштала.
|
||||||
|
|
||||||
СТИЛЬ И СТРУКТУРА:
|
СТИЛЬ И СТРУКТУРА:
|
||||||
- Один цельный абзац, 2–4 предложения, от третьего лица.
|
- Один цельный абзац, 2–4 предложения, от третьего лица.
|
||||||
- Первое предложение начинай со слов «На занятии ребята …».
|
- Первое предложение начинай со слов «На занятии ребята …».
|
||||||
@@ -407,26 +488,6 @@ SET value = 'Ты — редактор сообщений тьютора дет
|
|||||||
|
|
||||||
ГЛАВНОЕ ПРАВИЛО:
|
ГЛАВНОЕ ПРАВИЛО:
|
||||||
Отрабатывай ровно по этому исходному тексту. Ничего из примера выше в свой текст не переноси: тема, программа, персонажи и детали из примера не твои.'
|
Отрабатывай ровно по этому исходному тексту. Ничего из примера выше в свой текст не переноси: тема, программа, персонажи и детали из примера не твои.'
|
||||||
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;
|
INSERT INTO settings (key, value) VALUES ('notify_lesson_ai_formatted', 'false') ON CONFLICT (key) DO NOTHING;
|
||||||
|
|||||||
@@ -3520,16 +3520,24 @@ const LESSON_AI_DEFAULT_PROMPT = [
|
|||||||
'',
|
'',
|
||||||
'СТИЛЬ И СТРУКТУРА:',
|
'СТИЛЬ И СТРУКТУРА:',
|
||||||
'- Один цельный абзац, 2–4 предложения, от третьего лица.',
|
'- Один цельный абзац, 2–4 предложения, от третьего лица.',
|
||||||
'- Первое предложение начинай со слов «На занятии ребята …».',
|
'- Первое предложение всегда начинай со слов «На занятии ребята …». Чем занимались в начале занятия — начало модуля, продолжение или завершение — определяй по правилу НАЧАЛО ФРАЗЫ.',
|
||||||
'- Если в исходном тексте описана практическая часть, начни её со слов «В конце занятия …» или «Затем …». Нет практики в исходнике — не придумывай её.',
|
'- Если в исходном тексте описана практическая часть, начни её со слов «В конце занятия …» или «Затем …». Нет практики в исходнике — не придумывай её.',
|
||||||
'- Вместо «они» пиши «каждый». Обращение на «вы» не используй.',
|
'- Вместо «они» пиши «каждый». Обращение на «вы» не используй.',
|
||||||
'- Коротко скажи, с чем познакомились или что изучали; затем — что конкретно делали; в конце — что сделал самостоятельно.',
|
'- Коротко скажи, с чем познакомились или что изучали; затем — что конкретно делали; в конце — что сделал самостоятельно.',
|
||||||
'- Сократи перечисления, объединяй их через «и», «а также», не повторяй одно и то же разными словами.',
|
'- Сократи перечисления, объединяй их через «и», «а также», не повторяй одно и то же разными словами.',
|
||||||
'- Убери разговорные и оценочные обороты: «было весело», «очень», «классно».',
|
'- Убери разговорные и оценочные обороты: «было весело», «очень», «классно».',
|
||||||
'',
|
'',
|
||||||
|
'НАЧАЛО ФРАЗЫ (определяется номером темы):',
|
||||||
|
'Система присылает готовую подсказку строкой «Позиция темы: …». Если такой строки нет, смотри номер вида N/M в конце строки «Тема занятия: …», где N — номер занятия в модуле, M — сколько занятий в модуле всего. Пользуйся только одной из этих подсказок и всегда проверяй, что N не больше M.',
|
||||||
|
'- «последнее занятие модуля» (N = M, например 2/2 или 7/7): занятие завершает модуль. Первое предложение обязательно начни с «завершили» или «закончили»: «На занятии ребята завершили изучение …», «На занятии ребята закончили знакомство с …».',
|
||||||
|
'- «промежуточное занятие модуля» (1 < N < M, например 2/4, 3/5, 6/7): изучение продолжается. Первое предложение обязательно начни с «продолжили», «продолжали», «закрепили» или «углубили»: «На занятии ребята продолжили изучение …», «На занятии ребята закрепили …».',
|
||||||
|
'- «первое занятие модуля» (N = 1 и N < M, например 1/4 или 1/7): модуль начинается. Первое предложение обязательно начни с «начали» или «приступили»: «На занятии ребята начали знакомство с …», «На занятии ребята начали изучение …».',
|
||||||
|
'- Если подсказки нет или номер нечитаем — характер занятия не выбирай и не додумывай. Начни нейтрально, прямо с сути: «На занятии ребята разобрались …», «На занятии ребята отработали …», «На занятии ребята попробовали …».',
|
||||||
|
'- Это требование сильнее всего остального: даже если начало исходного текста уже хорошо звучит, перепиши его по правилу выше. Слово «модуль», цифры номера и любые оценки в текст отчёта не переноси: номер темы — внутренний признак для выбора формулировки.',
|
||||||
|
'',
|
||||||
'ФОРМАТ:',
|
'ФОРМАТ:',
|
||||||
'- Без заголовков, списков, markdown, подписей и пояснений.',
|
'- Без заголовков, списков, markdown, подписей и пояснений.',
|
||||||
'- Не начинай со слов «Сегодня», «Вчера», «Дата», «Группа», «Время» и вообще не упоминай группу, дату и время занятия.',
|
'- Не начинай со слов «Сегодня», «Вчера», «Дата», «Группа», «Время», «Позиция» и вообще не упоминай группу, дату, время и номер занятия.',
|
||||||
'- Не начинай с «На данном занятии» или «В рамках занятия».',
|
'- Не начинай с «На данном занятии» или «В рамках занятия».',
|
||||||
'- Не оборачивай ответ в кавычки.',
|
'- Не оборачивай ответ в кавычки.',
|
||||||
'- Не добавляй лишних предложений: если исходный текст уже написан нормально, не переписывай его ради переписывания.',
|
'- Не добавляй лишних предложений: если исходный текст уже написан нормально, не переписывай его ради переписывания.',
|
||||||
@@ -3537,8 +3545,14 @@ const LESSON_AI_DEFAULT_PROMPT = [
|
|||||||
'ПРИМЕР ПРЕОБРАЗОВАНИЯ (бери отсюда только формулировки, тему и факты примера в свой текст не переноси):',
|
'ПРИМЕР ПРЕОБРАЗОВАНИЯ (бери отсюда только формулировки, тему и факты примера в свой текст не переноси):',
|
||||||
'Исходный текст:',
|
'Исходный текст:',
|
||||||
'«Ребята познакомились с программой Scratch Jr, научились выбирать фон, добавлять, изменять и создавать своих персонажей. В завершении занятия они выполнили практическое индивидуальное задание по созданию собственной анимации и небольшой программы».',
|
'«Ребята познакомились с программой Scratch Jr, научились выбирать фон, добавлять, изменять и создавать своих персонажей. В завершении занятия они выполнили практическое индивидуальное задание по созданию собственной анимации и небольшой программы».',
|
||||||
|
'Тема занятия: «Анимация 1/3»',
|
||||||
|
'Позиция темы: первое занятие модуля (1 из 3)',
|
||||||
'Хороший результат:',
|
'Хороший результат:',
|
||||||
'«На занятии ребята познакомились со Scratch Jr: научились выбирать фон, добавлять и изменять персонажей, а также создавать своих героев. В конце занятия каждый самостоятельно выполнил небольшое практическое задание — придумал свою анимацию и собрал простую программу».',
|
'«На занятии ребята начали знакомство со Scratch Jr: научились выбирать фон, добавлять и изменять персонажей, а также создавать своих героев. В конце занятия каждый самостоятельно выполнил небольшое практическое задание — придумал свою анимацию и собрал простую программу».',
|
||||||
|
'Тот же исходный текст, но «Позиция темы: промежуточное занятие модуля (2 из 3)» — меняется только начало:',
|
||||||
|
'«На занятии ребята продолжили знакомство со Scratch Jr: научились выбирать фон, добавлять и изменять персонажей…».',
|
||||||
|
'Тот же исходный текст, но «Позиция темы: последнее занятие модуля (3 из 3)»:',
|
||||||
|
'«На занятии ребята завершили знакомство со Scratch Jr: научились выбирать фон, добавлять и изменять персонажей…».',
|
||||||
'',
|
'',
|
||||||
'ГЛАВНОЕ ПРАВИЛО:',
|
'ГЛАВНОЕ ПРАВИЛО:',
|
||||||
'Отрабатывай ровно по этому исходному тексту. Ничего из примера выше в свой текст не переноси: тема, программа, персонажи и детали из примера не твои.',
|
'Отрабатывай ровно по этому исходному тексту. Ничего из примера выше в свой текст не переноси: тема, программа, персонажи и детали из примера не твои.',
|
||||||
|
|||||||
@@ -766,6 +766,22 @@ function createEntryAutoChecker({ pool, getSetting, logAudit, aiUrl, defaultProm
|
|||||||
return { start, notify, getStats, getInfo };
|
return { start, notify, getStats, getInfo };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const LESSON_TOPIC_POSITION_LABELS = {
|
||||||
|
first: 'первое занятие модуля',
|
||||||
|
middle: 'промежуточное занятие модуля',
|
||||||
|
last: 'последнее занятие модуля',
|
||||||
|
};
|
||||||
|
|
||||||
|
function lessonTopicPosition(topic) {
|
||||||
|
const m = /(\d+)\s*\/\s*(\d+)\s*$/.exec(String(topic || '').trim());
|
||||||
|
if (!m) return null;
|
||||||
|
const n = parseInt(m[1], 10);
|
||||||
|
const total = parseInt(m[2], 10);
|
||||||
|
if (!Number.isInteger(n) || !Number.isInteger(total) || total < 1 || n < 1 || n > total) return null;
|
||||||
|
const kind = n === total ? 'last' : n === 1 ? 'first' : 'middle';
|
||||||
|
return { kind, n, total, label: `${LESSON_TOPIC_POSITION_LABELS[kind]} (${n} из ${total})` };
|
||||||
|
}
|
||||||
|
|
||||||
function createLessonReportChecker({ pool, getSetting, logAudit, aiUrl, model, defaultPrompt, bus, onDone }) {
|
function createLessonReportChecker({ pool, getSetting, logAudit, aiUrl, model, defaultPrompt, bus, onDone }) {
|
||||||
const AI_URL = aiUrl || process.env.AI_URL || 'http://text-corrector:8080';
|
const AI_URL = aiUrl || process.env.AI_URL || 'http://text-corrector:8080';
|
||||||
const MODEL = model || process.env.AI_MODEL || 'qwen2.5-1.5b-instruct-q4_k_m.gguf';
|
const MODEL = model || process.env.AI_MODEL || 'qwen2.5-1.5b-instruct-q4_k_m.gguf';
|
||||||
@@ -929,11 +945,13 @@ async function finish(row, status, text, error, aiText) {
|
|||||||
const time = row.lesson_time ? String(row.lesson_time).slice(0, 5) : '';
|
const time = row.lesson_time ? String(row.lesson_time).slice(0, 5) : '';
|
||||||
const input = original.length > MAX_INPUT_CHARS ? original.slice(0, MAX_INPUT_CHARS) : original;
|
const input = original.length > MAX_INPUT_CHARS ? original.slice(0, MAX_INPUT_CHARS) : original;
|
||||||
const topic = String(row.topic || '').trim();
|
const topic = String(row.topic || '').trim();
|
||||||
|
const position = lessonTopicPosition(topic);
|
||||||
const context = [
|
const context = [
|
||||||
`Группа: ${row.group_name || '—'}`,
|
`Группа: ${row.group_name || '—'}`,
|
||||||
`Дата занятия: ${date}`,
|
`Дата занятия: ${date}`,
|
||||||
time ? `Время занятия: ${time}` : '',
|
time ? `Время занятия: ${time}` : '',
|
||||||
topic ? `Тема занятия: ${topic}` : '',
|
topic ? `Тема занятия: ${topic}` : '',
|
||||||
|
position ? `Позиция темы: ${position.label}` : '',
|
||||||
'',
|
'',
|
||||||
'Текст отчёта:',
|
'Текст отчёта:',
|
||||||
input,
|
input,
|
||||||
@@ -1033,4 +1051,4 @@ async function loop() {
|
|||||||
return { start, stop, notify, getInfo };
|
return { start, stop, notify, getInfo };
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = { createEntryAutoChecker, createPhotoEnhanceWorker, createLessonReportChecker };
|
module.exports = { createEntryAutoChecker, createPhotoEnhanceWorker, createLessonReportChecker, lessonTopicPosition };
|
||||||
|
|||||||
Reference in New Issue
Block a user