Files
cannaiq/backend/docs/BRAND_INTELLIGENCE_API.md
Kelly 9fff0ba430 feat(analytics): Add Brand Intelligence API endpoint
New endpoint: GET /api/analytics/v2/brand/:name/intelligence

Returns comprehensive brand analytics payload including:
- Performance snapshot (active SKUs, revenue, stores, market share)
- Alerts (lost stores, delisted SKUs, competitor takeovers)
- SKU performance (velocity, status, stock levels)
- Retail footprint (penetration by region, whitespace opportunities)
- Competitive landscape (price positioning, head-to-head comparisons)
- Inventory health (days of stock, risk levels, overstock alerts)
- Promotion effectiveness (baseline vs promo velocity, lift, ROI)

Supports time windows (7d/30d/90d), state filtering, and category filtering.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-10 14:53:35 -07:00

10 KiB
Raw Blame History

Brand Intelligence API

Endpoint

GET /api/analytics/v2/brand/:name/intelligence

Query Parameters

Param Type Default Description
window 7d|30d|90d 30d Time window for trend calculations
state string - Filter by state code (e.g., AZ)
category string - Filter by category (e.g., Flower)

Response Payload Schema

interface BrandIntelligenceResult {
  brand_name: string;
  window: '7d' | '30d' | '90d';
  generated_at: string;  // ISO timestamp when data was computed

  performance_snapshot: PerformanceSnapshot;
  alerts: Alerts;
  sku_performance: SkuPerformance[];
  retail_footprint: RetailFootprint;
  competitive_landscape: CompetitiveLandscape;
  inventory_health: InventoryHealth;
  promo_performance: PromoPerformance;
}

Section 1: Performance Snapshot

Summary cards with key brand metrics.

interface PerformanceSnapshot {
  active_skus: number;              // Total products in catalog
  total_revenue_30d: number | null; // Estimated from qty × price
  total_stores: number;             // Active retail partners
  new_stores_30d: number;           // New distribution in window
  market_share: number | null;      // % of category SKUs
  avg_wholesale_price: number | null;
  price_position: 'premium' | 'value' | 'competitive';
}

UI Label Mapping:

Field User-Facing Label Helper Text
active_skus Active Products X total in catalog
total_revenue_30d Monthly Revenue Estimated from sales
total_stores Retail Distribution Active retail partners
new_stores_30d New Opportunities X new in last 30 days
market_share Category Position % of category
avg_wholesale_price Avg Wholesale Per unit
price_position Pricing Tier Premium/Value/Market Rate

Section 2: Alerts

Issues requiring attention.

interface Alerts {
  lost_stores_30d_count: number;
  lost_skus_30d_count: number;
  competitor_takeover_count: number;
  avg_oos_duration_days: number | null;
  avg_reorder_lag_days: number | null;
  items: AlertItem[];
}

interface AlertItem {
  type: 'lost_store' | 'delisted_sku' | 'shelf_loss' | 'extended_oos';
  severity: 'critical' | 'warning';
  store_name?: string;
  product_name?: string;
  competitor_brand?: string;
  days_since?: number;
  state_code?: string;
}

UI Label Mapping:

Field User-Facing Label
lost_stores_30d_count Accounts at Risk
lost_skus_30d_count Delisted SKUs
competitor_takeover_count Shelf Losses
avg_oos_duration_days Avg Stockout Length
avg_reorder_lag_days Avg Restock Time
severity: critical Urgent
severity: warning Watch

Section 3: SKU Performance (Product Velocity)

How fast each SKU sells.

interface SkuPerformance {
  store_product_id: number;
  product_name: string;
  category: string | null;
  daily_velocity: number;        // Units/day estimate
  velocity_status: 'hot' | 'steady' | 'slow' | 'stale';
  retail_price: number | null;
  on_sale: boolean;
  stores_carrying: number;
  stock_status: 'in_stock' | 'low_stock' | 'out_of_stock';
}

UI Label Mapping:

Field User-Facing Label
daily_velocity Daily Rate
velocity_status Momentum
velocity_status: hot Hot
velocity_status: steady Steady
velocity_status: slow Slow
velocity_status: stale Stale
retail_price Retail Price
on_sale Promo (badge)

Velocity Thresholds:

  • hot: >= 5 units/day
  • steady: >= 1 unit/day
  • slow: >= 0.1 units/day
  • stale: < 0.1 units/day

Section 4: Retail Footprint

Store placement and coverage.

interface RetailFootprint {
  total_stores: number;
  in_stock_count: number;
  out_of_stock_count: number;
  penetration_by_region: RegionPenetration[];
  whitespace_stores: WhitespaceStore[];
}

interface RegionPenetration {
  state_code: string;
  store_count: number;
  percent_reached: number;    // % of state's dispensaries
  in_stock: number;
  out_of_stock: number;
}

interface WhitespaceStore {
  store_id: number;
  store_name: string;
  state_code: string;
  city: string | null;
  category_fit: number;       // How many competing brands they carry
  competitor_brands: string[];
}

UI Label Mapping:

Field User-Facing Label
penetration_by_region Market Coverage by Region
percent_reached X% reached
in_stock X stocked
out_of_stock X out
whitespace_stores Expansion Opportunities
category_fit X fit

Section 5: Competitive Landscape

Market positioning vs competitors.

interface CompetitiveLandscape {
  brand_price_position: 'premium' | 'value' | 'competitive';
  market_share_trend: MarketSharePoint[];
  competitors: Competitor[];
  head_to_head_skus: HeadToHead[];
}

interface MarketSharePoint {
  date: string;
  share_percent: number;
}

interface Competitor {
  brand_name: string;
  store_overlap_percent: number;
  price_position: 'premium' | 'value' | 'competitive';
  avg_price: number | null;
  sku_count: number;
}

interface HeadToHead {
  product_name: string;
  brand_price: number;
  competitor_brand: string;
  competitor_price: number;
  price_diff_percent: number;
}

UI Label Mapping:

Field User-Facing Label
price_position: premium Premium Tier
price_position: value Value Leader
price_position: competitive Market Rate
market_share_trend Share of Shelf Trend
head_to_head_skus Price Comparison
store_overlap_percent X% store overlap

Section 6: Inventory Health

Stock projections and risk levels.

interface InventoryHealth {
  critical_count: number;      // <7 days stock
  warning_count: number;       // 7-14 days stock
  healthy_count: number;       // 14-90 days stock
  overstocked_count: number;   // >90 days stock
  skus: InventorySku[];
  overstock_alert: OverstockItem[];
}

interface InventorySku {
  store_product_id: number;
  product_name: string;
  store_name: string;
  days_of_stock: number | null;
  risk_level: 'critical' | 'elevated' | 'moderate' | 'healthy';
  current_quantity: number | null;
  daily_sell_rate: number | null;
}

interface OverstockItem {
  product_name: string;
  store_name: string;
  excess_units: number;
  days_of_stock: number;
}

UI Label Mapping:

Field User-Facing Label
risk_level: critical Reorder Now
risk_level: elevated Low Stock
risk_level: moderate Monitor
risk_level: healthy Healthy
critical_count Urgent (<7 days)
warning_count Low (7-14 days)
overstocked_count Excess (>90 days)
days_of_stock X days remaining
overstock_alert Overstock Alert
excess_units X excess units

Section 7: Promotion Effectiveness

How promotions impact sales.

interface PromoPerformance {
  avg_baseline_velocity: number | null;
  avg_promo_velocity: number | null;
  avg_velocity_lift: number | null;     // % increase during promo
  avg_efficiency_score: number | null;  // ROI proxy
  promotions: Promotion[];
}

interface Promotion {
  product_name: string;
  store_name: string;
  status: 'active' | 'scheduled' | 'ended';
  start_date: string;
  end_date: string | null;
  regular_price: number;
  promo_price: number;
  discount_percent: number;
  baseline_velocity: number | null;
  promo_velocity: number | null;
  velocity_lift: number | null;
  efficiency_score: number | null;
}

UI Label Mapping:

Field User-Facing Label
avg_baseline_velocity Normal Rate
avg_promo_velocity During Promos
avg_velocity_lift Avg Sales Lift
avg_efficiency_score ROI Score
velocity_lift Sales Lift
efficiency_score ROI Score
status: active Live
status: scheduled Scheduled
status: ended Ended

Example Queries

Get full payload

const response = await fetch('/api/analytics/v2/brand/Wyld/intelligence?window=30d');
const data = await response.json();

Extract summary cards (flattened)

const { performance_snapshot: ps, alerts } = data;

const summaryCards = {
  activeProducts: ps.active_skus,
  monthlyRevenue: ps.total_revenue_30d,
  retailDistribution: ps.total_stores,
  newOpportunities: ps.new_stores_30d,
  categoryPosition: ps.market_share,
  avgWholesale: ps.avg_wholesale_price,
  pricingTier: ps.price_position,
  accountsAtRisk: alerts.lost_stores_30d_count,
  delistedSkus: alerts.lost_skus_30d_count,
  shelfLosses: alerts.competitor_takeover_count,
};

Get top 10 fastest selling SKUs

const topSkus = data.sku_performance
  .filter(sku => sku.velocity_status === 'hot' || sku.velocity_status === 'steady')
  .sort((a, b) => b.daily_velocity - a.daily_velocity)
  .slice(0, 10);

Get critical inventory alerts only

const criticalInventory = data.inventory_health.skus
  .filter(sku => sku.risk_level === 'critical');

Get states with <50% penetration

const underPenetrated = data.retail_footprint.penetration_by_region
  .filter(region => region.percent_reached < 50)
  .sort((a, b) => a.percent_reached - b.percent_reached);

Get active promotions with positive lift

const effectivePromos = data.promo_performance.promotions
  .filter(p => p.status === 'active' && p.velocity_lift > 0)
  .sort((a, b) => b.velocity_lift - a.velocity_lift);

Build chart data for market share trend

const chartData = data.competitive_landscape.market_share_trend.map(point => ({
  x: new Date(point.date),
  y: point.share_percent,
}));

Notes for Frontend Implementation

  1. All fields are snake_case - transform to camelCase if needed
  2. Null values are possible - handle gracefully in UI
  3. Arrays may be empty - show appropriate empty states
  4. Timestamps are ISO format - parse with new Date()
  5. Percentages are already computed - no need to multiply by 100
  6. The window parameter affects trend calculations - 7d/30d/90d