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/`)
|
||||
- **Таблицы**: `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`
|
||||
- **Флаг из 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`
|
||||
- **Воркер**: `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`
|
||||
- **История версий**: `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`
|
||||
|
||||
Reference in New Issue
Block a user