Files
cannaiq/backend/src/scripts/run-backfill.ts
Kelly b4a2fb7d03 feat: Add v2 architecture with multi-state support and orchestrator services
Major additions:
- Multi-state expansion: states table, StateSelector, NationalDashboard, StateHeatmap, CrossStateCompare
- Orchestrator services: trace service, error taxonomy, retry manager, proxy rotator
- Discovery system: dutchie discovery service, geo validation, city seeding scripts
- Analytics infrastructure: analytics v2 routes, brand/pricing/stores intelligence pages
- Local development: setup-local.sh starts all 5 services (postgres, backend, cannaiq, findadispo, findagram)
- Migrations 037-056: crawler profiles, states, analytics indexes, worker metadata

Frontend pages added:
- Discovery, ChainsDashboard, IntelligenceBrands, IntelligencePricing, IntelligenceStores
- StateHeatmap, CrossStateCompare, SyncInfoPanel

Components added:
- StateSelector, OrchestratorTraceModal, WorkflowStepper

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-07 11:30:57 -07:00

106 lines
3.0 KiB
TypeScript

#!/usr/bin/env npx tsx
/**
* Run Backfill CLI
*
* Import historical payloads from existing data sources.
*
* Usage:
* npx tsx src/scripts/run-backfill.ts [options]
*
* Options:
* --source SOURCE Source to backfill from:
* - dutchie_products (default)
* - snapshots
* - cache_files
* - all
* --dry-run Print changes without modifying DB
* --limit N Max payloads to create (default: unlimited)
* --dispensary ID Only backfill specific dispensary
* --cache-path PATH Path to cache files (default: ./cache/payloads)
*/
import { Pool } from 'pg';
import { runBackfill, BackfillOptions } from '../hydration';
async function main() {
const args = process.argv.slice(2);
const dryRun = args.includes('--dry-run');
let source: BackfillOptions['source'] = 'dutchie_products';
const sourceIdx = args.indexOf('--source');
if (sourceIdx !== -1 && args[sourceIdx + 1]) {
source = args[sourceIdx + 1] as BackfillOptions['source'];
}
let limit: number | undefined;
const limitIdx = args.indexOf('--limit');
if (limitIdx !== -1 && args[limitIdx + 1]) {
limit = parseInt(args[limitIdx + 1], 10);
}
let dispensaryId: number | undefined;
const dispIdx = args.indexOf('--dispensary');
if (dispIdx !== -1 && args[dispIdx + 1]) {
dispensaryId = parseInt(args[dispIdx + 1], 10);
}
let cachePath: string | undefined;
const cacheIdx = args.indexOf('--cache-path');
if (cacheIdx !== -1 && args[cacheIdx + 1]) {
cachePath = args[cacheIdx + 1];
}
const pool = new Pool({
connectionString: process.env.DATABASE_URL,
});
try {
console.log('='.repeat(60));
console.log('BACKFILL RUNNER');
console.log('='.repeat(60));
console.log(`Source: ${source}`);
console.log(`Dry run: ${dryRun}`);
if (limit) console.log(`Limit: ${limit}`);
if (dispensaryId) console.log(`Dispensary: ${dispensaryId}`);
if (cachePath) console.log(`Cache path: ${cachePath}`);
console.log('');
const results = await runBackfill(pool, {
dryRun,
source,
limit,
dispensaryId,
cachePath,
});
console.log('\nBackfill Results:');
console.log('='.repeat(40));
for (const result of results) {
console.log(`\n${result.source}:`);
console.log(` Payloads created: ${result.payloadsCreated}`);
console.log(` Skipped: ${result.skipped}`);
console.log(` Errors: ${result.errors.length}`);
console.log(` Duration: ${result.durationMs}ms`);
if (result.errors.length > 0) {
console.log(' First 5 errors:');
for (const err of result.errors.slice(0, 5)) {
console.log(` - ${err}`);
}
}
}
const totalCreated = results.reduce((sum, r) => sum + r.payloadsCreated, 0);
console.log(`\nTotal payloads created: ${totalCreated}`);
} catch (error: any) {
console.error('Backfill error:', error.message);
process.exit(1);
} finally {
await pool.end();
}
}
main();