feat(lesson-ai): проверка отчёта о занятии по шаблону + история версий
Галочка «Проверить по шаблону» в окне отчёта отправляет текст модели: совпал с шаблоном — остаётся как есть (skipped), не совпал — переписывается в деловом виде (done). Обработка идёт в фоне, HTTP-запрос не ждёт модель, оригинал тьютора сохраняется в text_original. - схема: text_original/text_ai/ai_status/ai_checked_at/ai_error в lesson_reports, таблица lesson_report_versions, ensureLessonReportsTable() - настройки lesson_ai_enabled и lesson_ai_prompt (раздел sec-lesson-ai), значения только 'true'/'false' - worker.js: createLessonReportChecker (FOR UPDATE OF lr SKIP LOCKED, до 3 попыток), хук назовён notifyEvent — notify в createPhotoEnhanceWorker уже занят будильником - server.js: wakeLessonAiWorker, onLessonAiDone (версия, аудит с diff, уведомление lesson.ai.formatted, SSE lesson_report_status), маршруты /versions, /versions/:id/restore и /ai/revert - aiComplete вместо aiCorrectText: общий вызов модели с таймаутом - бэкап/восстановление: lesson_reports и lesson_report_versions в payload - фронтенд: openLessonVersions/restoreLessonVersion в admin.js, бейджи статусов в lessons.js, лейблы аудита, renderAuditPager - docs: раздел 3d в AGENTS.md и Agent Workflow, пункт в README - тесты: контракт lesson-report и настройки уведомления в api.smoketest.js
This commit is contained in:
@@ -210,6 +210,49 @@ async function main() {
|
||||
body: enhanceAi.data,
|
||||
});
|
||||
|
||||
// Отчёты о занятии: контракт проверки по шаблону и истории версий.
|
||||
// Модель здесь не дёргаем (долго и нужен сервис) — проверяем постановку в очередь
|
||||
// и то, что при ai_check:false текст остаётся нетронутым.
|
||||
const lrGroups = await api('/api/groups', { token });
|
||||
const gid = lrGroups.data[0] && lrGroups.data[0].id;
|
||||
if (gid) {
|
||||
const date = '2019-05-17';
|
||||
await api(`/api/lesson-reports?group_id=${gid}&date_from=${date}&date_to=${date}`, { token })
|
||||
.then(r => (r.data.items || []).forEach(i => api(`/api/lesson-reports/${i.id}`, { token, method: 'DELETE' })));
|
||||
|
||||
const plainText = 'Текст отчёта без проверки ИИ для смоук-теста.';
|
||||
const plain = await api('/api/lesson-reports', {
|
||||
token, method: 'POST',
|
||||
body: { group_id: gid, lesson_date: date, lesson_time: '10:00', text: plainText, ai_check: false },
|
||||
});
|
||||
ok('lesson-report: создание без ai_check -> ai_status=none, text_original=null',
|
||||
plain.status === 201 && plain.data.ai_status === 'none' && plain.data.text_original === null,
|
||||
{ status: plain.status, ai_status: plain.data.ai_status });
|
||||
|
||||
const versions = await api(`/api/lesson-reports/${plain.data.id}/versions`, { token });
|
||||
ok('lesson-report: история версий содержит исходный текст',
|
||||
versions.status === 200 && Array.isArray(versions.data.items) && versions.data.items.length >= 1
|
||||
&& versions.data.items.some(v => v.text === plainText && v.source === 'manual'),
|
||||
{ status: versions.status, items: (versions.data.items || []).length });
|
||||
|
||||
const badRestore = await api(`/api/lesson-reports/${plain.data.id}/versions/99999999/restore`, { token, method: 'POST' });
|
||||
ok('lesson-report: восстановление несуществующей версии -> 404', badRestore.status === 404, badRestore.status);
|
||||
|
||||
const noOrig = await api(`/api/lesson-reports/${plain.data.id}/ai/revert`, { token, method: 'POST' });
|
||||
ok('lesson-report: откат без оригинала -> 400', noOrig.status === 400, noOrig.status);
|
||||
|
||||
const del = await api(`/api/lesson-reports/${plain.data.id}`, { token, method: 'DELETE' });
|
||||
ok('lesson-report: удаление', del.status === 200, del.status);
|
||||
} else {
|
||||
ok('lesson-report: есть группа для проверки', false, 'no groups');
|
||||
}
|
||||
|
||||
const lessonNotifyMeta = await api('/api/notifications/meta', { token });
|
||||
// /api/notifications/meta отдаёт ключи настроек (notify_<тип>), а не сами типы
|
||||
ok('notifications: настройка lesson.ai.formatted заведена',
|
||||
(lessonNotifyMeta.data.types || []).some(t => t.key === 'notify_lesson_ai_formatted'),
|
||||
(lessonNotifyMeta.data.types || []).map(t => t.key));
|
||||
|
||||
const logout = await api('/api/auth/logout', { token, method: 'POST' });
|
||||
ok('logout', logout.status === 200, logout.status);
|
||||
const afterLogout = await api('/api/auth/me', { token });
|
||||
|
||||
Reference in New Issue
Block a user