// tools/runFaceClusteringScheduler.js const db = require('../db/knex'); const { spawn } = require('child_process'); let running = false; async function getUsersNeedingClustering() { const result = await db.raw(` SELECT p.user FROM faces f JOIN photos p ON p.id = f.photo_id LEFT JOIN ( SELECT user, MAX(completed_at) AS last_cluster FROM face_cluster_runs GROUP BY user ) r ON r.user = p.user GROUP BY p.user HAVING MAX(f.created_at) > COALESCE( MAX(r.last_cluster), '1970-01-01' ) `); return Array.isArray(result) ? result : result?.rows || []; } function clusterUser(user) { return new Promise((resolve, reject) => { console.log( '[clusterScheduler] start:', user, ); const child = spawn( process.execPath, [ 'tools/runFaceClustering.js', '--user', user, '--persist', ], { stdio: 'inherit', }, ); child.on('exit', (code) => { if (code === 0) { console.log( '[clusterScheduler] done:', user, ); resolve(); return; } reject( new Error( `Clustering exit code ${code}`, ), ); }); child.on('error', reject); }); } async function clusterUserIfNeeded(user) { const result = await db.raw( ` SELECT MAX(f.created_at) AS last_face, ( SELECT MAX(completed_at) FROM face_cluster_runs WHERE user = ? ) AS last_cluster FROM faces f JOIN photos p ON p.id = f.photo_id WHERE p.user = ? `, [user, user], ); const rows = Array.isArray(result) ? result : result?.rows || []; const row = rows[0]; if (!row) { return false; } const lastFace = row.last_face; const lastCluster = row.last_cluster; if (!lastFace) { return false; } if ( lastCluster && new Date(lastFace) <= new Date(lastCluster) ) { console.log( '[clusterScheduler] already up to date:', user, ); return false; } console.log( '[clusterScheduler] clustering required:', user, ); await clusterUser(user); return true; } async function runCycle() { if (running) { return; } running = true; try { const users = await getUsersNeedingClustering(); if (!users.length) { console.log( '[clusterScheduler] no users to cluster', ); return; } for (const row of users) { const user = row.user || row.USER; if (!user) { continue; } await clusterUser(user); } } finally { running = false; } } module.exports = { runCycle, clusterUserIfNeeded, };