← back to Nationalrealestate
auto-save: 2026-07-21T15:38:16 (1 files) — src/jobs/broker_rotate.ts
fabf9ec0dbea458a69122b3fcbd8fb7b4fcc70e2 · 2026-07-21 15:38:17 -0700 · Steve Abrams
Files touched
A src/jobs/broker_rotate.ts
Diff
commit fabf9ec0dbea458a69122b3fcbd8fb7b4fcc70e2
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Tue Jul 21 15:38:17 2026 -0700
auto-save: 2026-07-21T15:38:16 (1 files) — src/jobs/broker_rotate.ts
---
src/jobs/broker_rotate.ts | 51 +++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 51 insertions(+)
diff --git a/src/jobs/broker_rotate.ts b/src/jobs/broker_rotate.ts
new file mode 100644
index 0000000..0ecec2c
--- /dev/null
+++ b/src/jobs/broker_rotate.ts
@@ -0,0 +1,51 @@
+/**
+ * Broker-roster rotation: each run refreshes the STALEST state's licensing
+ * roster, so a daily schedule loops through every registered state continuously
+ * through the month (5 states ≈ every 5 days each; more states = wider loop —
+ * new wave-2 adapters join the rotation automatically once they've run once).
+ * Spawns the engine CLI so this file never touches engine.ts (agent territory).
+ * `--state xx` overrides selection (testing).
+ */
+import { execFileSync } from 'node:child_process';
+import { join, dirname } from 'node:path';
+import { fileURLToPath } from 'node:url';
+import { query, pool } from '../../db/pool.ts';
+
+const __dirname = dirname(fileURLToPath(import.meta.url));
+const ROOT = join(__dirname, '..', '..');
+
+async function pickStalest(): Promise<{ source: string; stateArg: string; lastOk: string | null } | null> {
+ // every adapter that has EVER ingested brokers is in the rotation; engine arg = prefix before '_'
+ const r = await query<{ source: string; last_ok: string | null }>(
+ `SELECT b.source, MAX(ir.started_at) FILTER (WHERE ir.status = 'ok')::text AS last_ok
+ FROM (SELECT DISTINCT source FROM broker) b
+ LEFT JOIN ingest_runs ir ON ir.source = b.source
+ GROUP BY b.source
+ ORDER BY MAX(ir.started_at) FILTER (WHERE ir.status = 'ok') ASC NULLS FIRST
+ LIMIT 1`,
+ );
+ if (!r.rows.length) return null;
+ const source = r.rows[0].source;
+ return { source, stateArg: source.split('_')[0], lastOk: r.rows[0].last_ok };
+}
+
+async function main() {
+ const argIdx = process.argv.indexOf('--state');
+ let stateArg: string;
+ if (argIdx > -1 && process.argv[argIdx + 1]) {
+ stateArg = process.argv[argIdx + 1];
+ console.log(`[broker-rotate] override: ${stateArg}`);
+ } else {
+ const pick = await pickStalest();
+ if (!pick) { console.log('[broker-rotate] no broker sources registered yet'); await pool.end(); return; }
+ stateArg = pick.stateArg;
+ console.log(`[broker-rotate] stalest = ${pick.source} (last ok: ${pick.lastOk ?? 'never'}) -> refreshing '${stateArg}'`);
+ }
+ await pool.end(); // engine child owns its own pool
+ execFileSync('npx', ['tsx', 'src/ingest/brokers/engine.ts', stateArg], {
+ cwd: ROOT, stdio: 'inherit', timeout: 60 * 60_000,
+ });
+ console.log(`[broker-rotate] done: ${stateArg}`);
+}
+
+main().catch(e => { console.error('[broker-rotate] FAILED:', e); process.exit(1); });
← 511984e Perf: 5-min in-process cache on /api/markets (1.8s scan -> m
·
back to Nationalrealestate
·
States tab on rankings (51 states, raw metrics), national in ae4b420 →