openvibers/openvibe.live Commits
The Today card has a version: an older stored card is regenerated (#832)
Changes
@@@ -28,6 +28,7 @@ const CHECK_MS = 5 * 60 * 1000; // how often the job looks for a due dayconst MIN_TAKES = 1; // one take is a day: the card says what was said, and grows with the dayconst MAX_TAKES = 30; // the workflow takes at most 30 takesconst MAX_FETCH = 400; // a very busy day is sampled, not read whole++const CARD_VERSION = 2; // 2: the day's takes are listed for the model, and a card that denies them is droppedconst KEY = (date) => `rundown_today:${date}`;const utcDay = (now = Date.now()) => new Date(now).toISOString().slice(0, 10);@@@ -78,7 +79,7 @@ function cardInput(date, items) {*/function isDue(stored, { now = Date.now(), total = 0, latest_take_id = null } = {}) {if (Number(total) < MIN_TAKES) return false;-- if (!stored || !Number(stored.generated_at)) return true;++ if (!stored || stored.version !== CARD_VERSION || !Number(stored.generated_at)) return true;if (now - Number(stored.generated_at) < REFRESH_MS) return false;const seen = Number(stored.latest_take_id || 0);return latest_take_id != null && Number(latest_take_id) > seen;@@@ -126,7 +127,7 @@ async function get(date) {try {const raw = await db.getSetting(KEY(date));const card = raw ? JSON.parse(raw) : null;-- return card && card.status === 'ready' && card.date === date ? card : null;++ return card && card.status === 'ready' && card.version === CARD_VERSION && card.date === date ? card : null;} catch { return null; }}@@@ -157,7 +158,7 @@ async function refresh(date, { now = Date.now(), force = false, ai = null } = {}catch { out = null; }const text = cardText(out);if (!text) return null;-- const card = { status: 'ready', date, ...enrich(text, sample, total), generated_at: now, latest_take_id: latest, take_count: total, take_sample: sample.length };++ const card = { status: 'ready', version: CARD_VERSION, date, ...enrich(text, sample, total), generated_at: now, latest_take_id: latest, take_count: total, take_sample: sample.length };try { await db.setSetting(KEY(date), JSON.stringify(card)); } catch { return null; }return card;}@@@ -195,5 +196,5 @@ function ssrHtml(card) {module.exports = {refresh, get, tick, start, stop, ssrHtml, isDue, sampleTakes, cardInput, cardText, takesFor, utcDay,-- REFRESH_MS, CHECK_MS, MIN_TAKES, MAX_TAKES, KEY,++ REFRESH_MS, CHECK_MS, MIN_TAKES, MAX_TAKES, CARD_VERSION, KEY,};@@@ -15,10 +15,11 @@ const today = require('../server/rundown/today');// ── Cadence: never before three takes, then 30 minutes apart with a new take ──assert.strictEqual(today.isDue(null, { now, total: 0, latest_take_id: null }), false, 'a day with no takes has no card');assert.strictEqual(today.isDue(null, { now, total: 1, latest_take_id: 9 }), true, 'the first take makes a card');-- assert.strictEqual(today.isDue({ generated_at: now - 29 * 60000, latest_take_id: 9 }, { now, total: 10, latest_take_id: 10 }), false, 'not before 30 minutes');-- assert.strictEqual(today.isDue({ generated_at: now - 31 * 60000, latest_take_id: 9 }, { now, total: 10, latest_take_id: 9 }), false, 'no new takes, no write');-- assert.strictEqual(today.isDue({ generated_at: now - 31 * 60000, latest_take_id: 9 }, { now, total: 10, latest_take_id: 10 }), true, 'a new take after 30 minutes');-- assert.strictEqual(today.isDue({ generated_at: now - 31 * 60000, latest_take_id: 9 }, { now, total: 10, latest_take_id: 8 }), false, 'an older cursor never rewrites');++ assert.strictEqual(today.isDue({ version: today.CARD_VERSION, generated_at: now - 29 * 60000, latest_take_id: 9 }, { now, total: 10, latest_take_id: 10 }), false, 'not before 30 minutes');++ assert.strictEqual(today.isDue({ version: today.CARD_VERSION, generated_at: now - 31 * 60000, latest_take_id: 9 }, { now, total: 10, latest_take_id: 9 }), false, 'no new takes, no write');++ assert.strictEqual(today.isDue({ version: today.CARD_VERSION, generated_at: now - 31 * 60000, latest_take_id: 9 }, { now, total: 10, latest_take_id: 10 }), true, 'a new take after 30 minutes');++ assert.strictEqual(today.isDue({ version: today.CARD_VERSION, generated_at: now - 31 * 60000, latest_take_id: 9 }, { now, total: 10, latest_take_id: 8 }), false, 'an older cursor never rewrites');++ assert.strictEqual(today.isDue({ generated_at: now - 1000, latest_take_id: 9 }, { now, total: 10, latest_take_id: 9 }), true, 'a card from before this version is not a card');// ── Sampling: first and last of the day, no duplicates, up to 30 ──const items = Array.from({ length: 100 }, (_, i) => ({ id: i + 1, title: `take ${i + 1}` }));@@@ -76,6 +77,7 @@ const today = require('../server/rundown/today');}) });assert.ok(card, 'the first card is written');assert.strictEqual(card.status, 'ready');++ assert.strictEqual(card.version, today.CARD_VERSION);assert.strictEqual(card.take_count, 3);assert.strictEqual(card.highlights.length, 1, 'only a real take becomes a highlight');assert.deepStrictEqual(card.highlights[0], {
@@ -28,6 +28,7 @@ const CHECK_MS = 5 * 60 * 1000; // how often the job looks for a due day
@@ -28,6 +28,7 @@ const CHECK_MS = 5 * 60 * 1000; // how often the job looks for a due day
const MIN_TAKES = 1; // one take is a day: the card says what was said, and grows with the day
const MIN_TAKES = 1; // one take is a day: the card says what was said, and grows with the day
const MAX_TAKES = 30; // the workflow takes at most 30 takes
const MAX_TAKES = 30; // the workflow takes at most 30 takes
const MAX_FETCH = 400; // a very busy day is sampled, not read whole
const MAX_FETCH = 400; // a very busy day is sampled, not read whole
+const CARD_VERSION = 2; // 2: the day's takes are listed for the model, and a card that denies them is dropped
const KEY = (date) => `rundown_today:${date}`;
const KEY = (date) => `rundown_today:${date}`;
const utcDay = (now = Date.now()) => new Date(now).toISOString().slice(0, 10);
const utcDay = (now = Date.now()) => new Date(now).toISOString().slice(0, 10);
@@ -78,7 +79,7 @@ function cardInput(date, items) {
@@ -78,7 +79,7 @@ function cardInput(date, items) {
*/
*/
function isDue(stored, { now = Date.now(), total = 0, latest_take_id = null } = {}) {
function isDue(stored, { now = Date.now(), total = 0, latest_take_id = null } = {}) {
if (Number(total) < MIN_TAKES) return false;
if (Number(total) < MIN_TAKES) return false;
- if (!stored || !Number(stored.generated_at)) return true;
+ if (!stored || stored.version !== CARD_VERSION || !Number(stored.generated_at)) return true;
if (now - Number(stored.generated_at) < REFRESH_MS) return false;
if (now - Number(stored.generated_at) < REFRESH_MS) return false;
const seen = Number(stored.latest_take_id || 0);
const seen = Number(stored.latest_take_id || 0);
return latest_take_id != null && Number(latest_take_id) > seen;
return latest_take_id != null && Number(latest_take_id) > seen;
@@ -126,7 +127,7 @@ async function get(date) {
@@ -126,7 +127,7 @@ async function get(date) {
try {
try {
const raw = await db.getSetting(KEY(date));
const raw = await db.getSetting(KEY(date));
const card = raw ? JSON.parse(raw) : null;
const card = raw ? JSON.parse(raw) : null;
- return card && card.status === 'ready' && card.date === date ? card : null;
+ return card && card.status === 'ready' && card.version === CARD_VERSION && card.date === date ? card : null;
} catch { return null; }
} catch { return null; }
}
}
@@ -157,7 +158,7 @@ async function refresh(date, { now = Date.now(), force = false, ai = null } = {}
@@ -157,7 +158,7 @@ async function refresh(date, { now = Date.now(), force = false, ai = null } = {}
catch { out = null; }
catch { out = null; }
const text = cardText(out);
const text = cardText(out);
if (!text) return null;
if (!text) return null;
- const card = { status: 'ready', date, ...enrich(text, sample, total), generated_at: now, latest_take_id: latest, take_count: total, take_sample: sample.length };
+ const card = { status: 'ready', version: CARD_VERSION, date, ...enrich(text, sample, total), generated_at: now, latest_take_id: latest, take_count: total, take_sample: sample.length };
try { await db.setSetting(KEY(date), JSON.stringify(card)); } catch { return null; }
try { await db.setSetting(KEY(date), JSON.stringify(card)); } catch { return null; }
return card;
return card;
}
}
@@ -195,5 +196,5 @@ function ssrHtml(card) {
@@ -195,5 +196,5 @@ function ssrHtml(card) {
module.exports = {
module.exports = {
refresh, get, tick, start, stop, ssrHtml, isDue, sampleTakes, cardInput, cardText, takesFor, utcDay,
refresh, get, tick, start, stop, ssrHtml, isDue, sampleTakes, cardInput, cardText, takesFor, utcDay,
- REFRESH_MS, CHECK_MS, MIN_TAKES, MAX_TAKES, KEY,
+ REFRESH_MS, CHECK_MS, MIN_TAKES, MAX_TAKES, CARD_VERSION, KEY,
};
};
@@ -15,10 +15,11 @@ const today = require('../server/rundown/today');
@@ -15,10 +15,11 @@ const today = require('../server/rundown/today');
// ── Cadence: never before three takes, then 30 minutes apart with a new take ──
// ── Cadence: never before three takes, then 30 minutes apart with a new take ──
assert.strictEqual(today.isDue(null, { now, total: 0, latest_take_id: null }), false, 'a day with no takes has no card');
assert.strictEqual(today.isDue(null, { now, total: 0, latest_take_id: null }), false, 'a day with no takes has no card');
assert.strictEqual(today.isDue(null, { now, total: 1, latest_take_id: 9 }), true, 'the first take makes a card');
assert.strictEqual(today.isDue(null, { now, total: 1, latest_take_id: 9 }), true, 'the first take makes a card');
- assert.strictEqual(today.isDue({ generated_at: now - 29 * 60000, latest_take_id: 9 }, { now, total: 10, latest_take_id: 10 }), false, 'not before 30 minutes');
- assert.strictEqual(today.isDue({ generated_at: now - 31 * 60000, latest_take_id: 9 }, { now, total: 10, latest_take_id: 9 }), false, 'no new takes, no write');
- assert.strictEqual(today.isDue({ generated_at: now - 31 * 60000, latest_take_id: 9 }, { now, total: 10, latest_take_id: 10 }), true, 'a new take after 30 minutes');
- assert.strictEqual(today.isDue({ generated_at: now - 31 * 60000, latest_take_id: 9 }, { now, total: 10, latest_take_id: 8 }), false, 'an older cursor never rewrites');
+ assert.strictEqual(today.isDue({ version: today.CARD_VERSION, generated_at: now - 29 * 60000, latest_take_id: 9 }, { now, total: 10, latest_take_id: 10 }), false, 'not before 30 minutes');
+ assert.strictEqual(today.isDue({ version: today.CARD_VERSION, generated_at: now - 31 * 60000, latest_take_id: 9 }, { now, total: 10, latest_take_id: 9 }), false, 'no new takes, no write');
+ assert.strictEqual(today.isDue({ version: today.CARD_VERSION, generated_at: now - 31 * 60000, latest_take_id: 9 }, { now, total: 10, latest_take_id: 10 }), true, 'a new take after 30 minutes');
+ assert.strictEqual(today.isDue({ version: today.CARD_VERSION, generated_at: now - 31 * 60000, latest_take_id: 9 }, { now, total: 10, latest_take_id: 8 }), false, 'an older cursor never rewrites');
+ assert.strictEqual(today.isDue({ generated_at: now - 1000, latest_take_id: 9 }, { now, total: 10, latest_take_id: 9 }), true, 'a card from before this version is not a card');
// ── Sampling: first and last of the day, no duplicates, up to 30 ──
// ── Sampling: first and last of the day, no duplicates, up to 30 ──
const items = Array.from({ length: 100 }, (_, i) => ({ id: i + 1, title: `take ${i + 1}` }));
const items = Array.from({ length: 100 }, (_, i) => ({ id: i + 1, title: `take ${i + 1}` }));
@@ -76,6 +77,7 @@ const today = require('../server/rundown/today');
@@ -76,6 +77,7 @@ const today = require('../server/rundown/today');
}) });
}) });
assert.ok(card, 'the first card is written');
assert.ok(card, 'the first card is written');
assert.strictEqual(card.status, 'ready');
assert.strictEqual(card.status, 'ready');
+ assert.strictEqual(card.version, today.CARD_VERSION);
assert.strictEqual(card.take_count, 3);
assert.strictEqual(card.take_count, 3);
assert.strictEqual(card.highlights.length, 1, 'only a real take becomes a highlight');
assert.strictEqual(card.highlights.length, 1, 'only a real take becomes a highlight');
assert.deepStrictEqual(card.highlights[0], {
assert.deepStrictEqual(card.highlights[0], {