diff --git a/backend/scripts/fix-srs-immediate-due.js b/backend/scripts/fix-srs-immediate-due.js index f735f65..e3f8c7f 100644 --- a/backend/scripts/fix-srs-immediate-due.js +++ b/backend/scripts/fix-srs-immediate-due.js @@ -10,20 +10,25 @@ import { sequelize } from '../utils/sequelize.js'; */ const DRY_RUN = process.env.DRY_RUN !== 'false'; +const LOOKBACK_DAYS = parseInt(process.env.LOOKBACK_DAYS || '365', 10); +const WINDOW_SECONDS = parseInt(process.env.WINDOW_SECONDS || '10', 10); const run = async () => { try { await sequelize.authenticate(); console.log('✅ DB connection OK'); - // Conservative selector: items created in the last 3 days where next_due_at is within 2 minutes of created_at + // Selector: items created in the last LOOKBACK_DAYS where next_due_at is within WINDOW_SECONDS of created_at const selector = ` SELECT id, item_key, course_id, stage, next_due_at, created_at FROM community.vocab_srs_item - WHERE created_at >= now() - interval '3 days' - AND abs(extract(epoch from (next_due_at - created_at))) < 120 + WHERE created_at >= now() - interval '${LOOKBACK_DAYS} days' + AND ( + next_due_at = created_at + OR abs(extract(epoch from (next_due_at - created_at))) <= ${WINDOW_SECONDS} + ) ORDER BY created_at ASC - LIMIT 500 + LIMIT 1000 `; const recent = await sequelize.query(selector, { type: sequelize.QueryTypes.SELECT }); @@ -31,7 +36,7 @@ const run = async () => { console.table(recent.slice(0, 20)); if (recent.length === 0) { - console.log('\nNo candidate rows found — nothing to do.'); + console.log(`\nNo candidate rows found for lookback ${LOOKBACK_DAYS} days and window ${WINDOW_SECONDS}s — nothing to do.`); return; } @@ -40,8 +45,11 @@ const run = async () => { const updateQuery = ` UPDATE community.vocab_srs_item SET next_due_at = date_trunc('day', created_at + interval '1 day') + interval '8 hours' - WHERE created_at >= now() - interval '3 days' - AND abs(extract(epoch from (next_due_at - created_at))) < 120 + WHERE created_at >= now() - interval '${LOOKBACK_DAYS} days' + AND ( + next_due_at = created_at + OR abs(extract(epoch from (next_due_at - created_at))) <= ${WINDOW_SECONDS} + ) RETURNING id, item_key, course_id, stage, next_due_at, created_at; `; diff --git a/backend/scripts/fix-srs-queries-plain.sql b/backend/scripts/fix-srs-queries-plain.sql new file mode 100644 index 0000000..ab9a968 --- /dev/null +++ b/backend/scripts/fix-srs-queries-plain.sql @@ -0,0 +1,36 @@ +-- Plain SQL version of fix-srs-queries.sql for clients that don't support psql meta-commands +-- Configure the two variables below before running (edit them in the file): +-- set lookback_days = 365; set window_seconds = 10; + +-- Edit these values as needed +-- Configure the two variables below before running (edit them in the file): +-- set lookback_days = 365; set window_seconds = 10; + +-- Preview rows to be changed (edit values in the CTE if needed) +WITH vars AS ( + SELECT 365::int AS lookback_days, 10::int AS window_seconds +) +SELECT id, item_key, course_id, stage, next_due_at, created_at +FROM community.vocab_srs_item +WHERE created_at >= now() - make_interval(days => (SELECT lookback_days FROM vars)) + AND ( + next_due_at = created_at + OR abs(EXTRACT(EPOCH FROM (next_due_at - created_at))) <= (SELECT window_seconds FROM vars) + ) +ORDER BY created_at ASC +LIMIT 200; + +-- Conservative update: set next_due_at to next calendar day at 08:00 +WITH vars AS ( + SELECT 365::int AS lookback_days, 10::int AS window_seconds +) +UPDATE community.vocab_srs_item +SET next_due_at = date_trunc('day', created_at + interval '1 day') + interval '8 hours' +WHERE created_at >= now() - make_interval(days => (SELECT lookback_days FROM vars)) + AND ( + next_due_at = created_at + OR abs(EXTRACT(EPOCH FROM (next_due_at - created_at))) <= (SELECT window_seconds FROM vars) + ) +RETURNING id, item_key, course_id, stage, next_due_at, created_at; + +-- After running, re-run `node backend/scripts/diag-srs-stats.js` to validate counts. diff --git a/backend/scripts/fix-srs-queries.sql b/backend/scripts/fix-srs-queries.sql index d1ce4bc..01baa3a 100644 --- a/backend/scripts/fix-srs-queries.sql +++ b/backend/scripts/fix-srs-queries.sql @@ -1,23 +1,34 @@ --- Fix items that were created with next_due_at ~= created_at (within 2 minutes) --- Run this after a backup. Adjust the WHERE clause if you want a different window. + +-- Fix items that were created with next_due_at ~= created_at +-- Run this after a backup. Edit the two variables below to widen/narrow the search. + +-- CONFIG: adjust as needed +\set LOOKBACK_DAYS 365 +\set WINDOW_SECONDS 10 BEGIN; -- Preview rows to be changed SELECT id, item_key, course_id, stage, next_due_at, created_at FROM community.vocab_srs_item -WHERE created_at >= now() - interval '3 days' - AND abs(EXTRACT(EPOCH FROM (next_due_at - created_at))) < 120 +WHERE created_at >= now() - make_interval(days => :LOOKBACK_DAYS) + AND ( + next_due_at = created_at + OR abs(EXTRACT(EPOCH FROM (next_due_at - created_at))) <= :WINDOW_SECONDS + ) ORDER BY created_at ASC LIMIT 200; -- Conservative update: set next_due_at to next calendar day at 08:00 UPDATE community.vocab_srs_item SET next_due_at = date_trunc('day', created_at + interval '1 day') + interval '8 hours' -WHERE created_at >= now() - interval '3 days' - AND abs(EXTRACT(EPOCH FROM (next_due_at - created_at))) < 120 +WHERE created_at >= now() - make_interval(days => :LOOKBACK_DAYS) + AND ( + next_due_at = created_at + OR abs(EXTRACT(EPOCH FROM (next_due_at - created_at))) <= :WINDOW_SECONDS + ) RETURNING id, item_key, course_id, stage, next_due_at, created_at; COMMIT; --- After running, re-run backend/scripts/diag-srs-stats.js to validate counts. +-- After running, re-run `node backend/scripts/diag-srs-stats.js` to validate counts.