# ShiftOps Data Structures Documentation


**Last Updated:** 2025-11-20

Complete reference for all data structures used in the ShiftOps tool, including request/response formats, internal data structures, and localStorage formats.

## Table of Contents

- [API Request Structures](#api-request-structures)
- [API Response Structures](#api-response-structures)
- [Internal Data Structures](#internal-data-structures)
- [localStorage Structures](#localstorage-structures)
- [Component Data Structures](#component-data-structures)

## API Request Structures

### POST `/v2/api/shiftops.php` Request

```typescript
interface ShiftOpsRequest {
  mode: "essential" | "enhanced";
  place_id: string; // Required: Google Places place_id
  name: string; // Required: Business name

  // Google Places Data (optional but improves accuracy)
  formatted_address?: string;
  website?: string;
  types?: string[]; // e.g., ['restaurant', 'food', 'point_of_interest']
  rating?: number; // 0.0 - 5.0
  photos?: Photo[];
  geometry?: {
    location: {
      lat: number;
      lng: number;
    };
  };
  user_ratings_total?: number;
  opening_hours?: {
    periods?: Period[];
    weekday_text?: string[];
    open_now?: boolean;
  };
  business_status?: "OPERATIONAL" | "CLOSED_TEMPORARILY" | "CLOSED_PERMANENTLY";
  price_level?: number; // 0-4
  wheelchair_accessible_entrance?: boolean;
  service_options?: {
    delivery?: boolean;
    dine_in?: boolean;
    takeout?: boolean;
    reservable?: boolean;
    serves_breakfast?: boolean;
    serves_lunch?: boolean;
    serves_dinner?: boolean;
  };
  reviews?: Review[];
  utc_offset?: number; // Minutes from UTC
  address_components?: AddressComponent[];
}

interface Photo {
  height: number;
  width: number;
  photo_reference: string;
  html_attributions?: string[];
}

interface Period {
  open: {
    day: number; // 0-6 (Sunday-Saturday)
    time: string; // HHMM format
  };
  close?: {
    day: number;
    time: string;
  };
}

interface Review {
  author_name: string;
  author_url?: string;
  language: string;
  profile_photo_url?: string;
  rating: number;
  relative_time_description: string;
  text: string;
  time: number; // Unix timestamp
}

interface AddressComponent {
  long_name: string;
  short_name: string;
  types: string[];
}
```

### POST `/v2/api/shiftops-hubspot.php` Request

```typescript
interface HubSpotRequest {
  email: string; // Required: Valid email address

  // User Information
  firstname?: string;
  lastname?: string;

  // ShiftOps Business Data
  shiftops_business_name?: string;
  shiftops_business_address?: string;
  shiftops_business_type?: string;
  shiftops_index?: number; // Total score (0-100)
  shiftops_grade?:
    | "A+"
    | "A"
    | "A-"
    | "B+"
    | "B"
    | "B-"
    | "C+"
    | "C"
    | "C-"
    | "D+"
    | "D"
    | "D-"
    | "F";
  shiftops_scheduling_score?: number; // 0-20
  shiftops_absence_score?: number; // 0-20
  shiftops_timetracking_score?: number; // 0-20
  shiftops_compliance_score?: number; // 0-20
  shiftops_payroll_score?: number; // 0-20
  shiftops_place_id?: string;

  // Business Details
  website?: string;
  shiftops_google_rating?: number;

  // UTM Parameters
  utm_source?: string;
  utm_medium?: string;
  utm_campaign?: string;
  utm_term?: string;
  utm_content?: string;
  gclid?: string;

  // Tracking
  page_url?: string;
  referrer?: string;
}
```

## API Response Structures

### POST `/v2/api/shiftops.php` Response

```typescript
interface ShiftOpsResponse {
  success: boolean;
  analysis: ShiftOpsAnalysis;
  mode: "essential" | "enhanced";
  timestamp: string; // ISO 8601 format
  business_name: string;
  request_id: string;
  fallback?: boolean; // true if fallback data used
  error_message?: string;
}

interface ShiftOpsAnalysis {
  business_info: BusinessInfo;
  location_analysis: LocationAnalysis;
  online_presence: OnlinePresence;
  shiftops_score: ShiftOpsScore;
  cost_savings: CostSavings;
  context_data: ContextData;
  competitive_positioning?: CompetitivePositioning; // Enhanced mode only
  competitor_analysis?: CompetitorAnalysis; // Enhanced mode only
  recommendations: Recommendations;
  is_ordio_customer: boolean;
  customer_match_confidence: number; // 0-100
  customer_match?: CustomerMatch;
}

interface BusinessInfo {
  place_id: string;
  name: string;
  formatted_address: string;
  website?: string;
  types: string[];
  rating?: number;
  photos: Photo[];
  geometry?: {
    location: {
      lat: number;
      lng: number;
    };
  };
  user_ratings_total: number;
  opening_hours?: OpeningHours;
  business_status: string;
  price_level?: number;
  wheelchair_accessible_entrance?: boolean;
  service_options: ServiceOptions;
  reviews: Review[];
  utc_offset?: number;
  address_components: AddressComponent[];
  mode: "essential" | "enhanced";
}

interface ShiftOpsScore {
  total_score: number; // 0-100 (integer)
  pillar_scores: {
    scheduling_efficiency: number; // 0-20
    absence_stability: number; // 0-20
    time_tracking_hygiene: number; // 0-20
    compliance_docs: number; // 0-20
    payroll_readiness: number; // 0-20
  };
  grade:
    | "A+"
    | "A"
    | "A-"
    | "B+"
    | "B"
    | "B-"
    | "C+"
    | "C"
    | "C-"
    | "D+"
    | "D"
    | "D-"
    | "F";
  benchmark_comparison: BenchmarkComparison;
  data_completeness: DataCompleteness;
  score_metadata: ScoreMetadata;
}

interface BenchmarkComparison {
  industry: string; // 'restaurant', 'hospital', 'store', 'cafe', 'general'
  industry_average: number;
  difference: number; // score - industry_average
  percentile: number; // 0-100
}

interface DataCompleteness {
  percentage: number; // 0-100
  multiplier: number; // 0.50-1.0
  confidence: "high" | "medium" | "low";
  impact: "none" | "low" | "moderate" | "high";
  missing_fields: string[];
}

interface ScoreMetadata {
  data_completeness: number;
  estimation_confidence: "high" | "medium" | "low";
  data_sources_used: string[]; // e.g., ['google_places', 'weather_api', 'holiday_api']
  missing_data_impact: "none" | "low" | "moderate" | "high";
  calculation_timestamp: string; // ISO 8601
  is_ordio_customer: boolean;
  customer_boost_applied: boolean;
}

interface LocationAnalysis {
  address: string;
  country: string; // ISO 3166-1 alpha-2 (e.g., 'DE')
  state?: string;
  city?: string;
  is_urban: boolean;
  is_suburban: boolean;
  competitor_density: CompetitorDensity;
  location_score: number; // 0-20
}

interface CompetitorDensity {
  density: "niedrig" | "mittel" | "hoch";
  competition_level: "low" | "medium" | "high";
  market_saturation: "opportunity" | "competitive" | "saturated";
}

interface OnlinePresence {
  rating?: number;
  photo_count: number;
  review_count: number;
  price_level?: number;
  team_size_estimate: number;
  service_complexity: ServiceComplexity;
  review_sentiment: ReviewSentiment;
  reviews: Review[];
  review_quality: "excellent" | "good" | "fair" | "poor" | "unknown";
  photo_quality: "excellent" | "good" | "fair" | "poor" | "none";
  online_presence_score: number; // 0-20
}

interface ServiceComplexity {
  complexity_score: number;
  services: string[];
  level: "low" | "medium" | "high";
}

interface ReviewSentiment {
  positive_mentions: number;
  negative_mentions: number;
  staff_mentions: number;
  service_mentions: number;
  sentiment_score: number; // -1.0 to 1.0
}

interface CostSavings {
  team_size_estimate: number;
  team_size_confidence: "low" | "medium" | "high";
  team_size_factors: {
    volume: number;
    hours: number;
    complexity: number;
    quality: number;
  };
  monthly_labor_cost: number; // EUR
  annual_labor_cost: number; // EUR
  total_monthly_savings: number; // EUR
  total_annual_savings: number; // EUR
  savings_percentage: number; // 0-100
  potential_savings: {
    overtime_reduction: SavingsDetail;
    absence_cost_reduction: SavingsDetail;
    compliance_avoidance: SavingsDetail;
    turnover_reduction: SavingsDetail;
    scheduling_optimization: SavingsDetail;
  };
  roi_analysis: ROIAnalysis;
  break_even_analysis: BreakEvenAnalysis;
}

interface SavingsDetail {
  current_overtime_cost?: number;
  current_absence_cost?: number;
  current_compliance_cost?: number;
  current_turnover_rate?: number;
  current_turnover_cost?: number;
  current_scheduling_waste?: number;
  potential_savings: number; // EUR
  savings_percentage: number; // 0-100
  explanation: string;
}

interface ROIAnalysis {
  ordio_monthly_cost: number; // 89 EUR
  potential_monthly_savings: number; // EUR
  net_monthly_benefit: number; // EUR
  roi_percentage: number; // Can exceed 100%
  payback_period_months: number;
  savings_per_employee: number; // EUR
}

interface BreakEvenAnalysis {
  break_even_team_size: number;
  break_even_monthly_savings: number; // EUR
  break_even_months: number;
  is_already_profitable: boolean;
  explanation?: string;
}

interface ContextData {
  holidays: Holiday[];
  seasonal_trends: SeasonalTrends;
  weather: WeatherData;
  business_hours_analysis: BusinessHoursAnalysis;
}

interface Holiday {
  date: string; // YYYY-MM-DD
  name: string;
  global: boolean; // true if national holiday
  counties?: string[]; // ISO 3166-2 codes (e.g., 'DE-BW', 'DE-BY')
}

interface SeasonalTrends {
  demand_impact: number; // 1-5
  staffing_adjustment: number; // 1-5
  revenue_trend: number; // 1-5
}

interface WeatherData {
  current: WeatherCurrent;
  forecast: WeatherForecast[];
  data_source: "api" | "fallback";
  api_provider: "open-meteo" | "none";
}

interface WeatherCurrent {
  temp: number; // Celsius
  condition: WeatherCondition;
}

interface WeatherForecast {
  date: string; // YYYY-MM-DD
  day: string; // 'heute', 'morgen', 'übermorgen', or ''
  temp: number; // Celsius
  max_temp: number;
  min_temp: number;
  condition: WeatherCondition;
}

type WeatherCondition =
  | "clear"
  | "mostly_clear"
  | "partly_cloudy"
  | "overcast"
  | "foggy"
  | "rainy"
  | "snowy"
  | "stormy";

interface BusinessHoursAnalysis {
  hours_completeness: number; // 0-100
  weekend_coverage: number; // 0-100
  peak_hours_optimization: number; // 0-100
  weekly_hours: number;
  schedule_complexity: "none" | "low" | "medium" | "high";
  split_shifts: boolean;
  irregular_hours: boolean;
  data_source: "google_places" | "none";
}

interface CompetitivePositioning {
  market_position: MarketPosition;
  growth_potential: GrowthPotential;
  competitive_advantages: CompetitiveAdvantage[];
  market_saturation: "very_low" | "low" | "medium" | "high" | "very_high";
  recommendations: CompetitiveRecommendation[];
}

interface MarketPosition {
  overall_position:
    | "market_leader"
    | "above_average"
    | "average"
    | "below_average";
  rating_percentile: number; // 0-100
  review_percentile: number; // 0-100
  price_position: "premium" | "average" | "budget";
  competitive_strength: string;
  market_share_estimate: number; // 0-100
}

interface GrowthPotential {
  growth_score: number; // 0.0-1.0
  growth_potential: "very_high" | "high" | "medium" | "low" | "very_low";
  growth_factors: {
    market_saturation: number;
    rating_potential: number;
    review_growth_potential: number;
    operational_improvement: number;
    digital_presence: number;
  };
  key_opportunities: string[];
  growth_timeline: string; // e.g., '6-12 months'
}

interface CompetitiveAdvantage {
  type: string;
  description: string;
  impact: "high" | "medium" | "low";
}

interface CompetitiveRecommendation {
  priority: "high" | "medium" | "low";
  category: string;
  title: string;
  description: string;
  action: string;
}

interface CompetitorAnalysis {
  competitor_count: number;
  average_rating: number;
  average_review_count: number;
  average_price_level?: number;
  market_position:
    | "market_leader"
    | "above_average"
    | "average"
    | "below_average";
  competition_density: "very_low" | "low" | "medium" | "high" | "very_high";
  data_source: "google_places" | "estimated" | "fallback";
  top_competitors?: Competitor[];
}

interface Competitor {
  name: string;
  rating: number;
  user_ratings_total: number;
  place_id?: string;
}

interface Recommendations {
  all_recommendations: Recommendation[];
  quick_wins: Recommendation[];
  high_value: Recommendation[];
  strategic: Recommendation[];
  implementation_roadmap?: ImplementationRoadmap;
}

interface Recommendation {
  pillar: string; // e.g., 'scheduling_efficiency', 'absence_stability'
  category: "quick_wins" | "high_value" | "strategic";
  priority: number; // 1-10 (10 = highest)
  title: string;
  description: string;
  action: string;
  impact: "high" | "medium" | "low";
  effort: "high" | "medium" | "low";
  timeline: string; // e.g., '2-4 Wochen'
  cost_savings?: number; // EUR
  ordio_feature: string; // e.g., 'Schichtplanung', 'Zeiterfassung'
  success_metrics: string[];
  urgency?: "critical" | "high" | "medium" | "low";
}

interface ImplementationRoadmap {
  phase_1: {
    name: string;
    recommendations: Recommendation[];
  };
  phase_2: {
    name: string;
    recommendations: Recommendation[];
  };
  phase_3: {
    name: string;
    recommendations: Recommendation[];
  };
}

interface CustomerMatch {
  company_name: string;
  domain?: string;
  matched_name?: string;
  match_type: "exact" | "domain" | "fuzzy" | "prefix" | "address";
  confidence: number; // 0-100
  matched_customer?: {
    name: string;
    domain?: string;
    address?: string;
  };
}
```

## Internal Data Structures

### Cache Structure

```typescript
interface CachedAnalysis {
  analysis: ShiftOpsAnalysis;
  timestamp: number; // Unix timestamp
  mode: "essential" | "enhanced";
  cache_key: string; // MD5 hash
}
```

### Customer List Structure (ordio-customers.json)

```typescript
interface CustomerList {
  customers: Customer[];
  last_updated: string; // ISO 8601
  total_count: number;
}

interface Customer {
  company_name: string;
  domain?: string;
  normalized_name: string;
  address?: string;
  // Additional fields may exist
}
```

## localStorage Structures

### shiftops_report_data

```typescript
interface ReportData {
  business_info: BusinessInfo;
  shiftops_score: ShiftOpsScore;
  cost_savings: CostSavings;
  location_analysis: LocationAnalysis;
  online_presence: OnlinePresence;
  context_data: ContextData;
  competitive_positioning?: CompetitivePositioning | null;
  competitor_analysis?: CompetitorAnalysis | null;
  recommendations: Recommendations;
  is_ordio_customer: boolean;
  customer_match_confidence: number;
  customer_match?: CustomerMatch | null;
  timestamp: string; // ISO 8601
  requiresGate: boolean; // true if email gate required
}
```

### shiftops_enhanced_data

```typescript
interface EnhancedData {
  enhanced_analysis: ShiftOpsAnalysis; // Full analysis from enhanced mode
  timestamp: string; // ISO 8601
}
```

### shiftops_report_unlocked

**Type:** `string`  
**Value:** `'true'` when report is unlocked  
**Set When:** User submits email form on report page  
**Cleared When:** User starts new search (clears all localStorage)  
**Purpose:** Controls gated content visibility on report page

**Example:**

```javascript
localStorage.setItem("shiftops_report_unlocked", "true");
const isUnlocked = localStorage.getItem("shiftops_report_unlocked") === "true";
```

### shiftops_nps_submitted

**Type:** `string`  
**Value:** `'true'` when NPS survey is submitted  
**Set When:** User submits NPS survey  
**Cleared When:** User starts new search (clears all localStorage)  
**Purpose:** Prevents showing NPS survey again to same user

**Example:**

```javascript
localStorage.setItem("shiftops_nps_submitted", "true");
const npsSubmitted = localStorage.getItem("shiftops_nps_submitted") === "true";
```

### shiftops_nps_dismissed

**Type:** `string`  
**Value:** `'true'` when NPS survey is dismissed  
**Set When:** User clicks dismiss/close button on NPS survey  
**Cleared When:** Can be manually cleared, or cleared on new search  
**Purpose:** Prevents showing NPS survey again after user dismisses it

**Example:**

```javascript
localStorage.setItem("shiftops_nps_dismissed", "true");
const npsDismissed = localStorage.getItem("shiftops_nps_dismissed") === "true";
```

### shiftops_analysis_data

```typescript
interface AnalysisData {
  // Backup/fallback data structure
  // Same as ShiftOpsAnalysis but may be incomplete
}
```

## Component Data Structures

### ShiftOpsCustomerMatcher

```typescript
interface MatchResult {
  company_name: string;
  domain?: string;
  matched_name?: string;
  match_type: "exact" | "domain" | "fuzzy" | "prefix" | "address";
  confidence: number; // 0-100
  matched_customer?: Customer;
}
```

### ShiftOpsCostCalculator

```typescript
interface TeamSizeEstimate {
  team_size: number;
  confidence_level: "low" | "medium" | "high";
  factors_used: {
    volume: number;
    hours: number;
    complexity: number;
    quality: number;
  };
}
```

### ShiftOpsCompetitiveAnalyzer

```typescript
interface CompetitiveAnalysisResult {
  competitor_analysis: CompetitorAnalysis;
  market_position: MarketPosition;
  growth_potential: GrowthPotential;
  competitive_advantages: CompetitiveAdvantage[];
  market_saturation: string;
  recommendations: CompetitiveRecommendation[];
}
```

### ShiftOpsRecommendationsEngine

```typescript
interface RecommendationsResult {
  quick_wins: Recommendation[];
  high_value: Recommendation[];
  strategic: Recommendation[];
  all_recommendations: Recommendation[];
  implementation_roadmap: ImplementationRoadmap;
}
```

## Data Flow Examples

### Example: Essential Mode Response

```json
{
  "success": true,
  "analysis": {
    "business_info": {
      "place_id": "ChIJN1t_tDeuEmsRUsoyG83frY4",
      "name": "Test Restaurant",
      "formatted_address": "123 Main St, Berlin, Germany"
    },
    "shiftops_score": {
      "total_score": 65,
      "pillar_scores": {
        "scheduling_efficiency": 14,
        "absence_stability": 13,
        "time_tracking_hygiene": 12,
        "compliance_docs": 13,
        "payroll_readiness": 13
      },
      "grade": "C"
    },
    "cost_savings": {
      "team_size_estimate": 10,
      "total_monthly_savings": 2688
    }
  },
  "mode": "essential",
  "timestamp": "2025-11-12 19:43:35"
}
```

### Example: Customer Match Result

```json
{
  "company_name": "La Fonda",
  "domain": "lafonda.koeln",
  "match_type": "domain",
  "confidence": 95,
  "matched_customer": {
    "name": "La Fonda",
    "domain": "lafonda.koeln"
  }
}
```

## Notes

- All monetary values are in EUR (€)
- All temperatures are in Celsius (°C)
- All dates use ISO 8601 format (YYYY-MM-DD)
- All timestamps use ISO 8601 format (YYYY-MM-DDTHH:mm:ss+00:00)
- Scores are always integers (0-100 for total, 0-20 for pillars)
- Percentages are 0-100 (not 0-1)
- Confidence scores are 0-100 (not 0-1)
