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>
This commit is contained in:
Kelly
2025-12-07 11:30:57 -07:00
parent 8ac64ba077
commit b4a2fb7d03
248 changed files with 60714 additions and 666 deletions

View File

@@ -0,0 +1,118 @@
/**
* Trulieve Scottsdale - Per-Store Dutchie Crawler
*
* Store ID: 101
* Profile Key: trulieve-scottsdale
* Platform Dispensary ID: 5eaf489fa8a61801212577cc
*
* Phase 1: Identity implementation - no overrides, just uses base Dutchie logic.
* Future: Add store-specific selectors, timing, or custom logic as needed.
*/
import {
BaseDutchieCrawler,
StoreCrawlOptions,
CrawlResult,
DutchieSelectors,
crawlProducts as baseCrawlProducts,
} from '../../base/base-dutchie';
import { Dispensary } from '../../../dutchie-az/types';
// Re-export CrawlResult for the orchestrator
export { CrawlResult };
// ============================================================
// STORE CONFIGURATION
// ============================================================
/**
* Store-specific configuration
* These can be used to customize crawler behavior for this store
*/
export const STORE_CONFIG = {
storeId: 101,
profileKey: 'trulieve-scottsdale',
name: 'Trulieve of Scottsdale Dispensary',
platformDispensaryId: '5eaf489fa8a61801212577cc',
// Store-specific overrides (none for Phase 1)
customOptions: {
// Example future overrides:
// pricingType: 'rec',
// useBothModes: true,
// customHeaders: {},
// maxRetries: 3,
},
};
// ============================================================
// STORE CRAWLER CLASS
// ============================================================
/**
* TrulieveScottsdaleCrawler - Per-store crawler for Trulieve Scottsdale
*
* Phase 1: Identity implementation - extends BaseDutchieCrawler with no overrides.
* Future phases can override methods like:
* - getCName() for custom slug handling
* - crawlProducts() for completely custom logic
* - Add hooks for pre/post processing
*/
export class TrulieveScottsdaleCrawler extends BaseDutchieCrawler {
constructor(dispensary: Dispensary, options: StoreCrawlOptions = {}) {
// Merge store-specific options with provided options
const mergedOptions: StoreCrawlOptions = {
...STORE_CONFIG.customOptions,
...options,
};
super(dispensary, mergedOptions);
}
// Phase 1: No overrides - use base implementation
// Future phases can add overrides here:
//
// async crawlProducts(): Promise<CrawlResult> {
// // Custom pre-processing
// // ...
// const result = await super.crawlProducts();
// // Custom post-processing
// // ...
// return result;
// }
}
// ============================================================
// EXPORTED CRAWL FUNCTION
// ============================================================
/**
* Main entry point for the orchestrator
*
* The orchestrator calls: mod.crawlProducts(dispensary, options)
* This function creates a TrulieveScottsdaleCrawler and runs it.
*/
export async function crawlProducts(
dispensary: Dispensary,
options: StoreCrawlOptions = {}
): Promise<CrawlResult> {
console.log(`[TrulieveScottsdale] Using per-store crawler for ${dispensary.name}`);
const crawler = new TrulieveScottsdaleCrawler(dispensary, options);
return crawler.crawlProducts();
}
// ============================================================
// FACTORY FUNCTION (alternative API)
// ============================================================
/**
* Create a crawler instance without running it
* Useful for testing or when you need to configure before running
*/
export function createCrawler(
dispensary: Dispensary,
options: StoreCrawlOptions = {}
): TrulieveScottsdaleCrawler {
return new TrulieveScottsdaleCrawler(dispensary, options);
}