feat: implement neue SRS-Logik zur Berechnung von Intervallen und füge Diagnoseskript für fällige Items hinzu
All checks were successful
Deploy to production / deploy (push) Successful in 2m10s

This commit is contained in:
Torsten Schulz (local)
2026-06-03 17:13:50 +02:00
parent b5582045a9
commit bc8d63058a
2 changed files with 69 additions and 13 deletions

View File

@@ -0,0 +1,47 @@
#!/usr/bin/env node
import { sequelize } from '../utils/sequelize.js';
const run = async () => {
try {
await sequelize.authenticate();
console.log('✅ DB connection OK');
const queries = {
totalDue: `SELECT count(*)::int AS total_due FROM community.vocab_srs_item WHERE next_due_at <= now()`,
byStage: `SELECT stage, count(*)::int AS cnt FROM community.vocab_srs_item WHERE next_due_at <= now() GROUP BY stage ORDER BY stage`,
perDay30: `SELECT date(next_due_at) AS day, count(*)::int AS cnt FROM community.vocab_srs_item WHERE next_due_at <= now() GROUP BY day ORDER BY day DESC LIMIT 30`,
createdToday: `SELECT count(*)::int AS created_today FROM community.vocab_srs_item WHERE date(created_at) = current_date`,
recentNextDue: `SELECT item_key, course_id, stage, next_due_at, created_at FROM community.vocab_srs_item WHERE next_due_at > now() - interval '1 day' ORDER BY next_due_at ASC LIMIT 200`
};
const [totalRes] = await sequelize.query(queries.totalDue, { type: sequelize.QueryTypes.SELECT });
console.log('\nTotal due items: ', totalRes?.total_due ?? 'N/A');
const byStage = await sequelize.query(queries.byStage, { type: sequelize.QueryTypes.SELECT });
console.log('\nDue by stage:');
console.table(byStage);
const perDay = await sequelize.query(queries.perDay30, { type: sequelize.QueryTypes.SELECT });
console.log('\nDue per day (last 30):');
console.table(perDay);
const [createdRes] = await sequelize.query(queries.createdToday, { type: sequelize.QueryTypes.SELECT });
console.log('\nCreated today: ', createdRes?.created_today ?? 'N/A');
const recent = await sequelize.query(queries.recentNextDue, { type: sequelize.QueryTypes.SELECT });
console.log('\nExamples of items with next_due_at in the last 24h:');
console.table(recent.slice(0, 50));
// Summary recommendation
console.log('\nNotes:');
console.log('- If many items are in stage 0/1 and created_today > 0, check code paths that call _ensureSrsItems.');
console.log('- If many next_due_at are very recent, items may be created with next_due_at = now() causing large due counts.');
} catch (err) {
console.error('Error running diagnostics:', err);
process.exitCode = 2;
} finally {
try { await sequelize.close(); } catch (_) {}
}
};
run();

View File

@@ -127,7 +127,6 @@ export default class VocabService {
const previousInterval = Math.max(0, Number(item?.intervalDays) || 0); const previousInterval = Math.max(0, Number(item?.intervalDays) || 0);
const normalizedRating = String(rating || '').toLowerCase(); const normalizedRating = String(rating || '').toLowerCase();
const isCorrect = Boolean(correct) && normalizedRating !== 'again'; const isCorrect = Boolean(correct) && normalizedRating !== 'again';
if (!isCorrect) { if (!isCorrect) {
return { return {
stage: Math.max(0, previousStage - 1), stage: Math.max(0, previousStage - 1),
@@ -137,25 +136,35 @@ export default class VocabService {
}; };
} }
const intervals = [0, 1, 3, 7, 14, 30, 60, 120, 240]; // Neue einfache Policy:
let nextStage = Math.min(intervals.length - 1, previousStage + 1); // - 'easy' -> 7 Tage
// - 'good'/'normal' -> 4 Tage
if (normalizedRating === 'hard') { // - 'hard' -> 1 Tag
nextStage = Math.max(1, previousStage); // Außerdem: nextDueAt darf nicht mehr am gleichen Kalendertag liegen.
} let intervalDays;
if (normalizedRating === 'easy') { if (normalizedRating === 'easy') {
nextStage = Math.min(intervals.length - 1, previousStage + 2); intervalDays = 7;
} else if (normalizedRating === 'hard') {
intervalDays = 1;
} else {
// default / 'good' / unspecified
intervalDays = 4;
} }
let intervalDays = intervals[nextStage] ?? Math.max(1, previousInterval * 2); // Bestimme nextDueAt als Start des Tages (00:00) nach intervalDays
if (normalizedRating === 'hard') { const nextDueAt = new Date(now);
intervalDays = Math.max(1, Math.ceil(Math.max(previousInterval, 1) * 1.2)); // Setze auf Mitternacht heute
} nextDueAt.setHours(0, 0, 0, 0);
// Gehe vorwärts: morgen + (intervalDays - 1)
nextDueAt.setDate(nextDueAt.getDate() + 1 + Math.max(0, intervalDays - 1));
// Stage-Logik: einfache Fortschrittsstufe basierend auf intervalDays
let nextStage = Math.min(8, Math.max(0, Math.floor(Math.log2(intervalDays + 1))));
return { return {
stage: nextStage, stage: nextStage,
intervalDays, intervalDays,
nextDueAt: new Date(now.getTime() + intervalDays * 24 * 60 * 60 * 1000), nextDueAt,
lapseDelta: 0 lapseDelta: 0
}; };
} }