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>
106 lines
3.0 KiB
TypeScript
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();
|