Files
cannaiq/backend/src/dutchie-az/discovery/discovery-dt-locations-from-cities.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

114 lines
4.2 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env npx tsx
/**
* Discovery Entrypoint: Dutchie Locations (From Cities)
*
* Reads from dutchie_discovery_cities (crawl_enabled = true)
* and discovers store locations for each city.
*
* Geo coordinates are captured when available from Dutchie's payloads.
*
* Usage:
* npm run discovery:dt:locations
* npm run discovery:dt:locations -- --limit=10
* npm run discovery:dt:locations -- --delay=3000
* DATABASE_URL="..." npx tsx src/dutchie-az/discovery/discovery-dt-locations-from-cities.ts
*
* Options:
* --limit=N Only process N cities (default: all)
* --delay=N Delay between cities in ms (default: 2000)
*/
import { Pool } from 'pg';
import { DtLocationDiscoveryService } from './DtLocationDiscoveryService';
const DB_URL = process.env.DATABASE_URL || process.env.CANNAIQ_DB_URL ||
'postgresql://dutchie:dutchie_local_pass@localhost:54320/dutchie_menus';
function parseArgs(): { limit?: number; delay?: number } {
const args: { limit?: number; delay?: number } = {};
for (const arg of process.argv.slice(2)) {
const limitMatch = arg.match(/--limit=(\d+)/);
if (limitMatch) args.limit = parseInt(limitMatch[1], 10);
const delayMatch = arg.match(/--delay=(\d+)/);
if (delayMatch) args.delay = parseInt(delayMatch[1], 10);
}
return args;
}
async function main() {
const args = parseArgs();
console.log('╔══════════════════════════════════════════════════╗');
console.log('║ Dutchie Location Discovery (From Cities) ║');
console.log('║ Reads crawl_enabled cities, discovers stores ║');
console.log('╚══════════════════════════════════════════════════╝');
console.log(`\nDatabase: ${DB_URL.replace(/:[^:@]+@/, ':****@')}`);
if (args.limit) console.log(`City limit: ${args.limit}`);
if (args.delay) console.log(`Delay: ${args.delay}ms`);
const pool = new Pool({ connectionString: DB_URL });
try {
const { rows } = await pool.query('SELECT NOW() as time');
console.log(`Connected at: ${rows[0].time}\n`);
const service = new DtLocationDiscoveryService(pool);
const result = await service.discoverAllEnabled({
limit: args.limit,
delayMs: args.delay ?? 2000,
});
console.log('\n' + '═'.repeat(50));
console.log('SUMMARY');
console.log('═'.repeat(50));
console.log(`Cities processed: ${result.totalCities}`);
console.log(`Locations found: ${result.totalLocationsFound}`);
console.log(`Locations inserted: ${result.totalInserted}`);
console.log(`Locations updated: ${result.totalUpdated}`);
console.log(`Locations skipped: ${result.totalSkipped} (protected status)`);
console.log(`Errors: ${result.errors.length}`);
console.log(`Duration: ${(result.durationMs / 1000).toFixed(1)}s`);
if (result.errors.length > 0) {
console.log('\nErrors (first 10):');
result.errors.slice(0, 10).forEach((e, i) => console.log(` ${i + 1}. ${e}`));
if (result.errors.length > 10) {
console.log(` ... and ${result.errors.length - 10} more`);
}
}
// Get location stats including coordinates
const stats = await service.getStats();
console.log('\nCurrent Database Stats:');
console.log(` Total locations: ${stats.total}`);
console.log(` With coordinates: ${stats.withCoordinates}`);
console.log(` By status:`);
stats.byStatus.forEach(s => console.log(` ${s.status}: ${s.count}`));
if (result.totalCities === 0) {
console.log('\n⚠ No crawl-enabled cities found.');
console.log(' Seed cities first:');
console.log(' npm run discovery:dt:cities:manual -- --city-slug=ny-hudson --city-name=Hudson --state-code=NY');
process.exit(1);
}
if (result.errors.length > 0) {
console.log('\n⚠ Completed with errors');
process.exit(1);
}
console.log('\n✅ Location discovery completed successfully');
process.exit(0);
} catch (error: any) {
console.error('\n❌ Location discovery failed:', error.message);
process.exit(1);
} finally {
await pool.end();
}
}
main();