Back to .md Directory

Data Models

This document describes the core data structures used throughout the system.

May 2, 2026
0 downloads
0 views
ai rag
View source

Data Models

This document describes the core data structures used throughout the system.

Core Models

PromoContext

Represents the contextual environment for promotional planning.

interface PromoContext {
  geo: string;  // e.g., "DE", "UA"
  date_range: {
    start: string;  // ISO date
    end: string;
  };
  events: Event[];
  seasonality_factors: SeasonalityFactor[];
  weekend_pattern: {
    weekend_days: string[];  // ["SAT", "SUN"]
  };
  weather_profile: {
    location: string;  // Geographic code (e.g., "DE", "UA")
    coordinates?: {
      latitude: number;
      longitude: number;
      name: string;
    };
    summary: string;  // "rainy_weekend, with 2 rainy day(s), avg 12.5°C"
    daily: DailyWeather[];
  };
}

interface Event {
  name: string;
  date: string;  // ISO date
  type: "global_sale" | "public_holiday" | "local_event" | "payday";
  impact?: "high" | "medium" | "low";
}

interface SeasonalityFactor {
  department: string;
  month: number;
  factor: number;  // Multiplier (1.1 = 10% above average)
}

interface DailyWeather {
  date: string;
  condition: "sun" | "cloud" | "rain" | "snow" | "storm";
  temp_max: number;  // Celsius
  temp_min: number;  // Celsius
  temp_avg: number;  // Celsius
  rain_prob: number;  // 0-100
  rain_sum: number;  // mm
  cloud_cover?: number;  // 0-100
}

PromoBrief

Structured brief extracted from user input.

interface PromoBrief {
  month: string;  // "2024-10"
  promo_date_range: {
    start: string;
    end: string;
  };
  gap_to_target: {
    sales_value: number;  // Negative = below target
    margin_points: number;
  };
  objectives: {
    sales_value_target: number;
    margin_floor_pct: number;
    ebit_target?: number;
  };
  focus_departments: string[];  // ["TV", "Gaming"]
  channels: ("online" | "offline")[];
  risk_appetite: "low" | "medium" | "high";
  loyalty_focus: boolean;
  constraints: {
    max_discount_pct: Record<string, number>;  // By department
    min_margin_pct: number;
    excluded_brands?: string[];
  };
}

PromoOpportunity

Identified opportunity for a promotional campaign.

interface PromoOpportunity {
  id: string;
  title: string;
  promo_date_range: {
    start: string;
    end: string;
  };
  description: string;
  focus_departments: string[];
  estimated_potential: {
    sales_value: number;
    margin_impact: number;  // Percentage points
  };
  context_snapshot: PromoContext;
  priority: "high" | "medium" | "low";
}

PromoScenario

A specific promotional scenario with mechanics.

interface PromoScenario {
  id: string;
  label: string;  // "Conservative", "Balanced", "Aggressive"
  source_opportunity_id?: string;
  date_range: {
    start: string;
    end: string;
  };
  mechanics: PromoMechanic[];
  scenario_type?: "flash" | "best_deal" | "coupon" | "member_only";
}

interface PromoMechanic {
  department: string;
  channel: "online" | "offline" | "both";
  discount_pct: number;
  segments: string[];  // ["ALL"] or ["LOYAL_HIGH_VALUE"]
  notes?: string;
  product_focus?: string[];  // Specific products/brands
}

ScenarioKPI

Key performance indicators for a scenario.

interface ScenarioKPI {
  scenario_id: string;
  period: string;  // "2024-10-22..2024-10-27"
  total: {
    sales_value: number;
    margin_value: number;
    margin_pct: number;
    ebit: number;
    units: number;
  };
  vs_baseline: {
    sales_value_delta: number;
    margin_value_delta: number;
    ebit_delta: number;
    units_delta: number;
  };
  by_channel: ChannelKPI[];
  by_department: DepartmentKPI[];
  by_segment: SegmentKPI[];
}

interface ChannelKPI {
  channel: "online" | "offline";
  sales_value: number;
  margin_pct: number;
  units: number;
}

interface DepartmentKPI {
  department: string;
  sales_value: number;
  margin_pct: number;
  units: number;
}

interface SegmentKPI {
  segment_id: string;
  sales_value: number;
  margin_pct: number;
  units: number;
  comment?: string;
}

ValidationReport

Validation results for a scenario.

interface ValidationReport {
  scenario_id: string;
  status: "PASS" | "WARN" | "BLOCK";
  issues: ValidationIssue[];
  overall_score?: number;  // 0-100
}

interface ValidationIssue {
  type: 
    | "margin_threshold"
    | "discount_limit"
    | "kpi_plausibility"
    | "brand_compliance"
    | "cannibalization_risk";
  severity: "low" | "medium" | "high" | "critical";
  message: string;
  suggested_fix?: string;
  affected_department?: string;
}

PostMortemReport

Post-campaign analysis report.

interface PostMortemReport {
  scenario_id: string;
  period: string;
  forecast_kpi: ScenarioKPI;
  actual_kpi: {
    total: {
      sales_value: number;
      margin_value: number;
      margin_pct: number;
      ebit: number;
      units: number;
    };
  };
  vs_forecast: {
    sales_value_error_pct: number;  // Negative = under-forecast
    margin_value_error_pct: number;
    ebit_error_pct: number;
    units_error_pct: number;
  };
  post_promo_dip?: {
    period: string;
    sales_vs_baseline_delta: number;
  };
  cannibalization_signals: CannibalizationSignal[];
  insights: string[];
  learning_points: string[];
}

interface CannibalizationSignal {
  department: string;
  effect: "negative" | "positive" | "neutral";
  sales_vs_baseline_delta_pct: number;
  comment: string;
}

CreativeBrief

Creative brief for campaign assets.

interface CreativeBrief {
  scenario_id: string;
  objectives: {
    primary: string;
    secondary: string[];
  };
  target_audience: {
    segments: string[];
    demographics?: string;
    psychographics?: string;
  };
  key_messages: string[];
  tone: string;  // "energetic", "trustworthy", "urgent"
  mandatory_elements: {
    legal: string[];
    brand: string[];
  };
  assets: AssetSpec[];
}

interface AssetSpec {
  type: 
    | "homepage_hero"
    | "category_banner"
    | "in_store_sheet"
    | "email_hero"
    | "video_storyboard";
  title: string;
  headline: string;
  subheadline?: string;
  cta: string;
  product_focus: string[];
  layout_hints?: string;
  segment_specific?: boolean;
  target_segment?: string;
}

Segment

Customer segment from CDP.

interface Segment {
  segment_id: string;
  name: string;
  share_of_customers: number;  // 0-1
  share_of_revenue: number;  // 0-1
  avg_basket_value: number;
  fav_categories: string[];
  discount_sensitivity: "low" | "medium" | "high";
  purchase_frequency?: number;
  last_purchase_days_ago?: number;
}

UpliftModel

Uplift coefficients for forecasting.

interface UpliftModel {
  coefficients: UpliftCoefficient[];
  last_updated: string;
  version: string;
}

interface UpliftCoefficient {
  department: string;
  channel: "online" | "offline";
  discount_band: string;  // "0-10", "10-20", "20-30", "30+"
  uplift_sales_pct: number;  // Percentage uplift
  uplift_units_pct: number;
  margin_impact_pct: number;  // Change in margin percentage points
  confidence?: number;  // 0-1
  sample_size?: number;
}

BaselineForecast

Baseline forecast without promotions.

interface BaselineForecast {
  period: {
    start: string;
    end: string;
  };
  daily: DailyForecast[];
  totals: {
    sales_value: number;
    margin_value: number;
    margin_pct: number;
    units: number;
  };
  by_channel: ChannelForecast[];
  by_department: DepartmentForecast[];
}

interface DailyForecast {
  date: string;
  sales_value: number;
  margin_value: number;
  units: number;
  day_of_week: string;
}

interface ChannelForecast {
  channel: "online" | "offline";
  sales_value: number;
  margin_pct: number;
}

interface DepartmentForecast {
  department: string;
  sales_value: number;
  margin_pct: number;
}

Database Schema (PostgreSQL)

Tables

-- Promotional scenarios
CREATE TABLE promo_scenarios (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    label VARCHAR(100) NOT NULL,
    source_opportunity_id UUID,
    date_range_start DATE NOT NULL,
    date_range_end DATE NOT NULL,
    scenario_type VARCHAR(50),
    mechanics JSONB NOT NULL,
    created_at TIMESTAMP DEFAULT NOW(),
    updated_at TIMESTAMP DEFAULT NOW()
);

-- Scenario KPIs
CREATE TABLE scenario_kpis (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    scenario_id UUID REFERENCES promo_scenarios(id),
    period_start DATE NOT NULL,
    period_end DATE NOT NULL,
    total_sales_value NUMERIC(15,2),
    total_margin_value NUMERIC(15,2),
    total_margin_pct NUMERIC(5,2),
    total_ebit NUMERIC(15,2),
    total_units INTEGER,
    kpi_breakdown JSONB,  -- by_channel, by_department, by_segment
    created_at TIMESTAMP DEFAULT NOW()
);

-- Validation reports
CREATE TABLE validation_reports (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    scenario_id UUID REFERENCES promo_scenarios(id),
    status VARCHAR(20) NOT NULL,
    issues JSONB,
    overall_score INTEGER,
    created_at TIMESTAMP DEFAULT NOW()
);

-- Post-mortem reports
CREATE TABLE post_mortem_reports (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    scenario_id UUID REFERENCES promo_scenarios(id),
    period_start DATE NOT NULL,
    period_end DATE NOT NULL,
    forecast_kpi_id UUID REFERENCES scenario_kpis(id),
    actual_kpi JSONB NOT NULL,
    vs_forecast JSONB,
    insights TEXT[],
    created_at TIMESTAMP DEFAULT NOW()
);

-- Creative briefs
CREATE TABLE creative_briefs (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    scenario_id UUID REFERENCES promo_scenarios(id),
    brief JSONB NOT NULL,
    assets JSONB,
    created_at TIMESTAMP DEFAULT NOW()
);

-- Historical sales data (aggregated)
CREATE TABLE sales_aggregated (
    date DATE NOT NULL,
    channel VARCHAR(20) NOT NULL,
    department VARCHAR(50) NOT NULL,
    promo_flag BOOLEAN DEFAULT FALSE,
    discount_pct NUMERIC(5,2),
    sales_value NUMERIC(15,2),
    margin_value NUMERIC(15,2),
    units INTEGER,
    PRIMARY KEY (date, channel, department)
);

-- Customer segments (from CDP)
CREATE TABLE segments (
    segment_id VARCHAR(50) PRIMARY KEY,
    name VARCHAR(100) NOT NULL,
    share_of_customers NUMERIC(5,4),
    share_of_revenue NUMERIC(5,4),
    avg_basket_value NUMERIC(10,2),
    fav_categories TEXT[],
    discount_sensitivity VARCHAR(20),
    updated_at TIMESTAMP DEFAULT NOW()
);

-- Uplift model coefficients
CREATE TABLE uplift_coefficients (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    department VARCHAR(50) NOT NULL,
    channel VARCHAR(20) NOT NULL,
    discount_band VARCHAR(20) NOT NULL,
    uplift_sales_pct NUMERIC(6,2),
    uplift_units_pct NUMERIC(6,2),
    margin_impact_pct NUMERIC(6,2),
    confidence NUMERIC(3,2),
    sample_size INTEGER,
    model_version VARCHAR(20),
    created_at TIMESTAMP DEFAULT NOW(),
    UNIQUE (department, channel, discount_band, model_version)
);

API Request/Response Models

Create Scenario Request

interface CreateScenarioRequest {
  brief: PromoBrief;
  scenario_type?: "conservative" | "balanced" | "aggressive";
  custom_parameters?: Record<string, any>;
}

Evaluate Scenario Response

interface EvaluateScenarioResponse {
  scenario: PromoScenario;
  kpi: ScenarioKPI;
  validation: ValidationReport;
  baseline_comparison: {
    sales_delta: number;
    margin_delta: number;
    ebit_delta: number;
  };
}

Optimize Scenarios Request

interface OptimizeScenariosRequest {
  brief: PromoBrief;
  objectives: {
    maximize: "sales" | "margin" | "ebit";
    constraints: {
      min_margin_pct?: number;
      max_discount_pct?: number;
    };
  };
  num_scenarios?: number;  // Default: 3
}

Generate Creative Request

interface GenerateCreativeRequest {
  scenario_id: string;
  asset_types?: string[];  // If omitted, generates all
  target_segments?: string[];
}

Data Validation

All models should be validated using:

  • Python: Pydantic models
  • TypeScript: Zod schemas
  • API: FastAPI automatic validation

Example Pydantic model:

from pydantic import BaseModel, Field, validator
from typing import List, Optional
from datetime import date

class PromoBrief(BaseModel):
    month: str = Field(..., regex=r"^\d{4}-\d{2}$")
    promo_date_range: dict
    gap_to_target: dict
    objectives: dict
    focus_departments: List[str]
    channels: List[str]
    risk_appetite: str = Field(..., regex="^(low|medium|high)$")
    loyalty_focus: bool
    constraints: dict
    
    @validator('channels')
    def validate_channels(cls, v):
        allowed = ['online', 'offline']
        if not all(c in allowed for c in v):
            raise ValueError('Invalid channel')
        return v

Related Documents