- Add migrations 021-023 for dispensary_crawl_schedule tables and views - Add dispensary-orchestrator service and bootstrap-discovery script - Update schedule routes with dispensary endpoints (/api/schedule/dispensaries) - Switch frontend scheduler to use canonical dispensaries table (182 AZDHS entries) - Add dispensary schedule API methods to frontend api.ts - Remove "Unmapped" badge logic - all dispensaries now linked properly - Add proper URL linking to dispensary detail pages (/dispensaries/:state/:city/:slug) - Update Jobs table to show dispensary_name 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
78 lines
2.5 KiB
SQL
78 lines
2.5 KiB
SQL
-- =====================================================
|
|
-- Add dispensary slug/state to crawl_schedule_status view
|
|
-- =====================================================
|
|
-- Enables proper linking from scheduler to dispensary detail page
|
|
-- which uses route /dispensaries/:state/:city/:slug
|
|
|
|
DROP VIEW IF EXISTS crawl_schedule_status;
|
|
CREATE OR REPLACE VIEW crawl_schedule_status AS
|
|
SELECT
|
|
s.id AS store_id,
|
|
s.name AS store_name,
|
|
s.slug AS store_slug,
|
|
s.timezone,
|
|
s.active,
|
|
s.scrape_enabled,
|
|
s.last_scraped_at,
|
|
|
|
-- Dispensary info (master record)
|
|
s.dispensary_id,
|
|
d.name AS dispensary_name,
|
|
d.company_name AS dispensary_company,
|
|
d.city AS dispensary_city,
|
|
d.state AS dispensary_state,
|
|
d.slug AS dispensary_slug,
|
|
d.address AS dispensary_address,
|
|
d.menu_url AS dispensary_menu_url,
|
|
|
|
-- Provider intelligence from dispensary (if linked)
|
|
d.product_provider,
|
|
d.product_confidence,
|
|
d.product_crawler_mode,
|
|
|
|
-- Schedule settings (use store override or global)
|
|
COALESCE(scs.enabled, TRUE) AS schedule_enabled,
|
|
COALESCE(scs.interval_hours, cs_global.interval_hours, 4) AS interval_hours,
|
|
COALESCE(scs.daily_special_enabled, TRUE) AS daily_special_enabled,
|
|
COALESCE(scs.daily_special_time, '00:01'::TIME) AS daily_special_time,
|
|
COALESCE(scs.priority, 0) AS priority,
|
|
|
|
-- Orchestrator status
|
|
scs.last_status,
|
|
scs.last_summary,
|
|
scs.last_run_at AS schedule_last_run,
|
|
scs.last_error,
|
|
|
|
-- Next scheduled run calculation
|
|
CASE
|
|
WHEN s.last_scraped_at IS NULL THEN NOW()
|
|
ELSE s.last_scraped_at + (COALESCE(scs.interval_hours, cs_global.interval_hours, 4) || ' hours')::INTERVAL
|
|
END AS next_scheduled_run,
|
|
|
|
-- Latest job info
|
|
cj.id AS latest_job_id,
|
|
cj.status AS latest_job_status,
|
|
cj.job_type AS latest_job_type,
|
|
cj.trigger_type AS latest_job_trigger,
|
|
cj.started_at AS latest_job_started,
|
|
cj.completed_at AS latest_job_completed,
|
|
cj.products_found AS latest_products_found,
|
|
cj.products_new AS latest_products_new,
|
|
cj.products_updated AS latest_products_updated,
|
|
cj.error_message AS latest_job_error
|
|
|
|
FROM stores s
|
|
LEFT JOIN dispensaries d ON d.id = s.dispensary_id
|
|
LEFT JOIN store_crawl_schedule scs ON scs.store_id = s.id
|
|
LEFT JOIN crawler_schedule cs_global ON cs_global.schedule_type = 'global_interval'
|
|
LEFT JOIN LATERAL (
|
|
SELECT * FROM crawl_jobs cj2
|
|
WHERE cj2.store_id = s.id
|
|
ORDER BY cj2.created_at DESC
|
|
LIMIT 1
|
|
) cj ON TRUE
|
|
WHERE s.active = TRUE;
|
|
|
|
-- Grant permissions
|
|
GRANT SELECT ON crawl_schedule_status TO dutchie;
|