Files
cannaiq/backend/docs/ANALYTICS_V2_EXAMPLES.md
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

12 KiB

Analytics V2 API Examples

Overview

All endpoints are prefixed with /api/analytics/v2

Filtering Options

Time Windows:

  • ?window=7d - Last 7 days
  • ?window=30d - Last 30 days (default)
  • ?window=90d - Last 90 days

Legal Type Filtering:

  • ?legalType=recreational - Recreational states only
  • ?legalType=medical_only - Medical-only states (not recreational)
  • ?legalType=no_program - States with no cannabis program

1. Price Analytics

GET /price/product/:id

Get price trends for a specific store product.

Request:

GET /api/analytics/v2/price/product/12345?window=30d

Response:

{
  "store_product_id": 12345,
  "product_name": "Blue Dream 3.5g",
  "brand_name": "Cookies",
  "category": "Flower",
  "dispensary_id": 101,
  "dispensary_name": "Green Leaf Dispensary",
  "state_code": "AZ",
  "data_points": [
    {
      "date": "2024-11-06",
      "price_rec": 45.00,
      "price_med": 40.00,
      "price_rec_special": null,
      "price_med_special": null,
      "is_on_special": false
    },
    {
      "date": "2024-11-07",
      "price_rec": 42.00,
      "price_med": 38.00,
      "price_rec_special": null,
      "price_med_special": null,
      "is_on_special": false
    }
  ],
  "summary": {
    "current_price": 42.00,
    "min_price": 40.00,
    "max_price": 48.00,
    "avg_price": 43.50,
    "price_change_count": 3,
    "volatility_percent": 8.2
  }
}

GET /price/rec-vs-med

Get recreational vs medical-only price comparison by category.

Request:

GET /api/analytics/v2/price/rec-vs-med?category=Flower

Response:

[
  {
    "category": "Flower",
    "rec_avg": 38.50,
    "rec_median": 35.00,
    "med_avg": 42.00,
    "med_median": 40.00
  },
  {
    "category": "Concentrates",
    "rec_avg": 45.00,
    "rec_median": 42.00,
    "med_avg": 48.00,
    "med_median": 45.00
  }
]

2. Brand Analytics

GET /brand/:name/penetration

Get brand penetration metrics with state breakdown.

Request:

GET /api/analytics/v2/brand/Cookies/penetration?window=30d

Response:

{
  "brand_name": "Cookies",
  "total_dispensaries": 125,
  "total_skus": 450,
  "avg_skus_per_dispensary": 3.6,
  "states_present": ["AZ", "CA", "CO", "NV", "MI"],
  "state_breakdown": [
    {
      "state_code": "CA",
      "state_name": "California",
      "legal_type": "recreational",
      "dispensary_count": 45,
      "sku_count": 180,
      "avg_skus_per_dispensary": 4.0,
      "market_share_percent": 12.5
    },
    {
      "state_code": "AZ",
      "state_name": "Arizona",
      "legal_type": "recreational",
      "dispensary_count": 32,
      "sku_count": 128,
      "avg_skus_per_dispensary": 4.0,
      "market_share_percent": 15.2
    }
  ],
  "penetration_trend": [
    {
      "date": "2024-11-01",
      "dispensary_count": 120,
      "new_dispensaries": 0,
      "dropped_dispensaries": 0
    },
    {
      "date": "2024-11-08",
      "dispensary_count": 123,
      "new_dispensaries": 3,
      "dropped_dispensaries": 0
    },
    {
      "date": "2024-11-15",
      "dispensary_count": 125,
      "new_dispensaries": 2,
      "dropped_dispensaries": 0
    }
  ]
}

GET /brand/:name/rec-vs-med

Get brand presence in recreational vs medical-only states.

Request:

GET /api/analytics/v2/brand/Cookies/rec-vs-med

Response:

{
  "brand_name": "Cookies",
  "rec_states_count": 4,
  "rec_states": ["AZ", "CA", "CO", "NV"],
  "rec_dispensary_count": 110,
  "rec_avg_skus": 3.8,
  "med_only_states_count": 2,
  "med_only_states": ["FL", "OH"],
  "med_only_dispensary_count": 15,
  "med_only_avg_skus": 2.5
}

3. Category Analytics

GET /category/:name/growth

Get category growth metrics with state breakdown.

Request:

GET /api/analytics/v2/category/Flower/growth?window=30d

Response:

{
  "category": "Flower",
  "current_sku_count": 5200,
  "current_dispensary_count": 320,
  "avg_price": 38.50,
  "growth_data": [
    {
      "date": "2024-11-01",
      "sku_count": 4800,
      "dispensary_count": 310,
      "avg_price": 39.00
    },
    {
      "date": "2024-11-15",
      "sku_count": 5000,
      "dispensary_count": 315,
      "avg_price": 38.75
    },
    {
      "date": "2024-12-01",
      "sku_count": 5200,
      "dispensary_count": 320,
      "avg_price": 38.50
    }
  ],
  "state_breakdown": [
    {
      "state_code": "CA",
      "state_name": "California",
      "legal_type": "recreational",
      "sku_count": 2100,
      "dispensary_count": 145,
      "avg_price": 36.00
    },
    {
      "state_code": "AZ",
      "state_name": "Arizona",
      "legal_type": "recreational",
      "sku_count": 950,
      "dispensary_count": 85,
      "avg_price": 40.00
    }
  ]
}

GET /category/rec-vs-med

Get category comparison between recreational and medical-only states.

Request:

GET /api/analytics/v2/category/rec-vs-med

Response:

[
  {
    "category": "Flower",
    "recreational": {
      "state_count": 15,
      "dispensary_count": 650,
      "sku_count": 12500,
      "avg_price": 35.50,
      "median_price": 32.00
    },
    "medical_only": {
      "state_count": 8,
      "dispensary_count": 220,
      "sku_count": 4200,
      "avg_price": 42.00,
      "median_price": 40.00
    },
    "price_diff_percent": -15.48
  },
  {
    "category": "Concentrates",
    "recreational": {
      "state_count": 15,
      "dispensary_count": 600,
      "sku_count": 8500,
      "avg_price": 42.00,
      "median_price": 40.00
    },
    "medical_only": {
      "state_count": 8,
      "dispensary_count": 200,
      "sku_count": 3100,
      "avg_price": 48.00,
      "median_price": 45.00
    },
    "price_diff_percent": -12.50
  }
]

4. Store Analytics

GET /store/:id/summary

Get change summary for a store over a time window.

Request:

GET /api/analytics/v2/store/101/summary?window=30d

Response:

{
  "dispensary_id": 101,
  "dispensary_name": "Green Leaf Dispensary",
  "state_code": "AZ",
  "window": "30d",
  "products_added": 45,
  "products_dropped": 12,
  "brands_added": ["Alien Labs", "Connected"],
  "brands_dropped": ["House Brand"],
  "price_changes": 156,
  "avg_price_change_percent": 3.2,
  "stock_in_events": 89,
  "stock_out_events": 34,
  "current_product_count": 512,
  "current_in_stock_count": 478
}

GET /store/:id/events

Get recent product change events for a store.

Request:

GET /api/analytics/v2/store/101/events?window=7d&limit=50

Response:

[
  {
    "store_product_id": 12345,
    "product_name": "Blue Dream 3.5g",
    "brand_name": "Cookies",
    "category": "Flower",
    "event_type": "price_change",
    "event_date": "2024-12-05T14:30:00.000Z",
    "old_value": "45.00",
    "new_value": "42.00"
  },
  {
    "store_product_id": 12346,
    "product_name": "OG Kush 1g",
    "brand_name": "Alien Labs",
    "category": "Flower",
    "event_type": "added",
    "event_date": "2024-12-04T10:00:00.000Z",
    "old_value": null,
    "new_value": null
  },
  {
    "store_product_id": 12300,
    "product_name": "Sour Diesel Cart",
    "brand_name": "Select",
    "category": "Vaporizers",
    "event_type": "stock_out",
    "event_date": "2024-12-03T16:45:00.000Z",
    "old_value": "true",
    "new_value": "false"
  }
]

5. State Analytics

GET /state/:code/summary

Get market summary for a specific state with rec/med breakdown.

Request:

GET /api/analytics/v2/state/AZ/summary

Response:

{
  "state_code": "AZ",
  "state_name": "Arizona",
  "legal_status": {
    "recreational_legal": true,
    "rec_year": 2020,
    "medical_legal": true,
    "med_year": 2010
  },
  "coverage": {
    "dispensary_count": 145,
    "product_count": 18500,
    "brand_count": 320,
    "category_count": 12,
    "snapshot_count": 2450000,
    "last_crawl_at": "2024-12-06T02:30:00.000Z"
  },
  "pricing": {
    "avg_price": 42.50,
    "median_price": 38.00,
    "min_price": 5.00,
    "max_price": 250.00
  },
  "top_categories": [
    { "category": "Flower", "count": 5200 },
    { "category": "Concentrates", "count": 3800 },
    { "category": "Vaporizers", "count": 2950 },
    { "category": "Edibles", "count": 2400 },
    { "category": "Pre-Rolls", "count": 1850 }
  ],
  "top_brands": [
    { "brand": "Cookies", "count": 450 },
    { "brand": "Alien Labs", "count": 380 },
    { "brand": "Connected", "count": 320 },
    { "brand": "Stiiizy", "count": 290 },
    { "brand": "Raw Garden", "count": 275 }
  ]
}

GET /state/legal-breakdown

Get breakdown by legal status (recreational, medical-only, no program).

Request:

GET /api/analytics/v2/state/legal-breakdown

Response:

{
  "recreational_states": {
    "count": 24,
    "dispensary_count": 850,
    "product_count": 125000,
    "snapshot_count": 15000000,
    "states": [
      { "code": "CA", "name": "California", "dispensary_count": 250 },
      { "code": "CO", "name": "Colorado", "dispensary_count": 150 },
      { "code": "AZ", "name": "Arizona", "dispensary_count": 145 },
      { "code": "MI", "name": "Michigan", "dispensary_count": 120 }
    ]
  },
  "medical_only_states": {
    "count": 18,
    "dispensary_count": 320,
    "product_count": 45000,
    "snapshot_count": 5000000,
    "states": [
      { "code": "FL", "name": "Florida", "dispensary_count": 120 },
      { "code": "OH", "name": "Ohio", "dispensary_count": 85 },
      { "code": "PA", "name": "Pennsylvania", "dispensary_count": 75 }
    ]
  },
  "no_program_states": {
    "count": 9,
    "states": [
      { "code": "ID", "name": "Idaho" },
      { "code": "WY", "name": "Wyoming" },
      { "code": "KS", "name": "Kansas" }
    ]
  }
}

GET /state/recreational

Get list of recreational state codes.

Request:

GET /api/analytics/v2/state/recreational

Response:

{
  "legal_type": "recreational",
  "states": ["AK", "AZ", "CA", "CO", "CT", "DE", "IL", "MA", "MD", "ME", "MI", "MN", "MO", "MT", "NJ", "NM", "NV", "NY", "OH", "OR", "RI", "VA", "VT", "WA"],
  "count": 24
}

GET /state/medical-only

Get list of medical-only state codes (not recreational).

Request:

GET /api/analytics/v2/state/medical-only

Response:

{
  "legal_type": "medical_only",
  "states": ["AR", "FL", "HI", "LA", "MS", "ND", "NH", "OK", "PA", "SD", "UT", "WV"],
  "count": 12
}

GET /state/rec-vs-med-pricing

Get rec vs med price comparison by category.

Request:

GET /api/analytics/v2/state/rec-vs-med-pricing?category=Flower

Response:

[
  {
    "category": "Flower",
    "recreational": {
      "state_count": 15,
      "product_count": 12500,
      "avg_price": 35.50,
      "median_price": 32.00
    },
    "medical_only": {
      "state_count": 8,
      "product_count": 5200,
      "avg_price": 42.00,
      "median_price": 40.00
    },
    "price_diff_percent": -15.48
  }
]

How These Endpoints Support Portals

Brand Portal Use Cases

  1. Track brand penetration: Use /brand/:name/penetration to see how many stores carry the brand
  2. Compare rec vs med markets: Use /brand/:name/rec-vs-med to understand footprint by legal status
  3. Identify expansion opportunities: Use /state/coverage-gaps to find underserved markets
  4. Monitor pricing: Use /price/brand/:brand to track pricing by state

Buyer Portal Use Cases

  1. Compare stores: Use /store/:id/summary to see activity levels
  2. Track price changes: Use /store/:id/events to monitor competitor pricing
  3. Analyze categories: Use /category/:name/growth to identify trending products
  4. State-level insights: Use /state/:code/summary for market overview

Time Window Filtering

All time-based endpoints support the window query parameter:

Value Description
7d Last 7 days
30d Last 30 days (default)
90d Last 90 days

The window affects:

  • store_product_snapshots.captured_at for historical data
  • store_products.first_seen_at / last_seen_at for product lifecycle
  • crawl_runs.started_at for crawl-based metrics

Rec/Med Segmentation

All state-level endpoints automatically segment by:

  • Recreational: states.recreational_legal = TRUE
  • Medical-only: states.medical_legal = TRUE AND states.recreational_legal = FALSE
  • No program: Both flags are FALSE or NULL

This segmentation appears in:

  • legal_type field in responses
  • State breakdown arrays
  • Price comparison endpoints