feat(autogen): ротация 4 из 8 категорий по дням

Каждый день генерируем 4 из 8 категорий по скользящему окну:
  День 1: cat[0..3], День 2: cat[1..4], ..., День 8: cat[7,0,1,2]

Логика: dayOfYear % totalCategories = сдвиг окна.
За 8 дней каждая категория выходит 4 раза, каждый день — новый набор.

getAutogenStatus теперь возвращает today_active=true/false — входит
ли категория в сегодняшнюю ротацию.

Слоты публикации откатаны к 4 (08:11/12:11/16:11/20:11).
This commit is contained in:
Aleksei Pavlov
2026-06-21 21:55:32 +03:00
parent 174c3a17c1
commit 1f25adff00
+75 -7
View File
@@ -202,21 +202,70 @@ async function runAutogen({ forceCategory = null } = {}) {
params = [currentHour, currentMinute - 5, currentMinute + 5]; params = [currentHour, currentMinute - 5, currentMinute + 5];
} }
const { rows: settings } = await query( // Сначала берём ВСЕ активные категории (независимо от времени),
`SELECT * FROM autogen_settings ${whereClause} ORDER BY category`, // затем применяем ротацию — выбираем 4 из 8 по дню года.
params const { rows: allEnabled } = await query(
`SELECT * FROM autogen_settings WHERE enabled=true ORDER BY sort_order, category`,
[]
); );
// Ротация: скользящее окно из 4 категорий сдвигается на 1 каждый день.
// Это гарантирует что за 8 дней каждая категория выйдет минимум 4 раза,
// и каждый день читатель видит другой набор.
const DAILY_COUNT = 4;
const total = allEnabled.length;
let categoriesForToday;
if (total <= DAILY_COUNT) {
// Категорий меньше или равно 4 — берём все
categoriesForToday = allEnabled.map(s => s.category);
} else {
// День года (0..364) определяет сдвиг окна
const now = new Date();
const start = Date.UTC(now.getUTCFullYear(), 0, 0);
const dayOfYear = Math.floor((now - start) / 86400000);
const offset = dayOfYear % total;
// Берём 4 категории начиная со сдвига (с wrap-around)
categoriesForToday = Array.from({ length: DAILY_COUNT }, (_, i) =>
allEnabled[(offset + i) % total].category
);
console.log(
'[Autogen] Ротация дня ' + dayOfYear + ' (offset=' + offset + '): ' +
categoriesForToday.join(', ')
);
}
// Теперь фильтруем по расписанию (если не forceCategory) — категория
// должна быть в списке дня И соответствовать текущему времени.
const { rows: allSettings } = await query(
`SELECT * FROM autogen_settings WHERE enabled=true ORDER BY run_hour, run_minute`,
[]
);
let settings;
if (forceCategory) {
settings = allSettings.filter(s => s.category === forceCategory);
} else {
// Время окна ±5 мин уже применено в whereClause — переиспользуем
const { rows: timeFiltered } = await query(
`SELECT * FROM autogen_settings ${whereClause} ORDER BY run_hour, run_minute`,
params
);
// Оставляем только категории дня из сработавших по времени
const todaySet = new Set(categoriesForToday);
settings = timeFiltered.filter(s => todaySet.has(s.category));
}
if (!settings.length) { if (!settings.length) {
console.log('[Autogen] Nothing to generate at this time'); console.log('[Autogen] Nothing to generate at this time');
return { processed: 0, results: [] }; return { processed: 0, results: [] };
} }
const results = []; const results = [];
for (const s of settings) { for (let i = 0; i < settings.length; i++) {
const s = settings[i];
const result = await runAutogenForCategory(s.category); const result = await runAutogenForCategory(s.category);
results.push({ category: s.category, ...result }); results.push({ category: s.category, ...result });
if (settings.indexOf(s) < settings.length - 1) { if (i < settings.length - 1) {
await new Promise(r => setTimeout(r, 5000)); await new Promise(r => setTimeout(r, 5000));
} }
} }
@@ -227,6 +276,20 @@ async function runAutogen({ forceCategory = null } = {}) {
/** /**
* Получить статус автогенерации. * Получить статус автогенерации.
*/ */
/**
* Возвращает категории которые активны сегодня по ротации (4 из 8).
*/
function getTodayCategories(allCategories, dailyCount = 4) {
if (allCategories.length <= dailyCount) return allCategories.map(c => c.category || c);
const now = new Date();
const start = Date.UTC(now.getUTCFullYear(), 0, 0);
const dayOfYear = Math.floor((now - start) / 86400000);
const offset = dayOfYear % allCategories.length;
return Array.from({ length: dailyCount }, (_, i) =>
(allCategories[(offset + i) % allCategories.length].category || allCategories[(offset + i) % allCategories.length])
);
}
async function getAutogenStatus() { async function getAutogenStatus() {
const { rows: settings } = await query( const { rows: settings } = await query(
`SELECT s.*, c.name as cat_name, c.icon as cat_icon, c.color as cat_color, `SELECT s.*, c.name as cat_name, c.icon as cat_icon, c.color as cat_color,
@@ -252,7 +315,12 @@ async function getAutogenStatus() {
LEFT JOIN categories c ON c.slug=s.category LEFT JOIN categories c ON c.slug=s.category
ORDER BY s.run_hour, s.category` ORDER BY s.run_hour, s.category`
); );
return settings; // Добавим флаг today_active — входит ли категория в сегодняшнюю ротацию
const todaySet = new Set(getTodayCategories(settings));
return settings.map(s => ({
...s,
today_active: todaySet.has(s.category),
}));
} }
module.exports = { runAutogen, runAutogenForCategory, getAutogenStatus, TOPIC_BANK }; module.exports = { runAutogen, runAutogenForCategory, getAutogenStatus, getTodayCategories, TOPIC_BANK };