Skip to main content
Authenticated API

All endpoints require a valid JWT Bearer token, tenant authorization, and the ANALYTICS_CORE_POLICY gating policy. Accessible via the API gateway at /v1/coursing/analytics/*.

Coursing Analytics API

Analyze course fire timing, station coordination, and kitchen performance.

Overview

The Coursing Analytics API provides detailed metrics for multi-course meal orchestration. It surfaces how well the kitchen and front-of-house coordinate course fires, tracks delay root causes, and generates actionable recommendations.

FeatureDescription
Course TimingFire delay, on-time percentage, prep time, gap timing per course
Station PerformanceTicket counts, average and P95 times, sync delay per station
Service MetricsFire response time, pace selection, auto vs manual fire breakdown
Timing TrendsTime-series data with day, week, or month granularity
Delay BreakdownCategorized delays: kitchen, fire, server, coordination
DashboardFull summary with efficiency score and recommendations
ExportCSV or JSON export with selectable data sections

Related Issues: Epic #790 (Course Management), #1062 (Python Coursing Analytics)


Authentication

All endpoints require three layers of authorization:

LayerMechanismDescription
JWTAuthorization: Bearer {token}Valid access token from the Auth service
Tenant AuthAutomatic from tokenRequest is scoped to the caller's tenant
Gating PolicyANALYTICS_CORE_POLICYFeature must be enabled for the tenant

Common Query Parameters

Most endpoints share these query parameters for date range control:

ParameterTypeDefaultDescription
start_datedateComputed from daysAnalysis start date (YYYY-MM-DD)
end_datedateToday (UTC)Analysis end date (YYYY-MM-DD)
daysinteger7Days to analyze when dates are not provided (1-90)

If start_date and end_date are omitted, the API defaults to the last days days ending today.


Get Course Timing Metrics

Returns per-course timing performance including fire delay, on-time rate, prep time, variance, and gap timing.

Request

GET /api/v1/coursing/analytics/timing/{location_id}?
start_date=2026-02-01&
end_date=2026-02-07
Authorization: Bearer {access_token}

Path Parameters

ParameterTypeRequiredDescription
location_idstringYesLocation identifier (UUID)

Query Parameters

ParameterTypeDefaultDescription
start_datedateComputedAnalysis start date
end_datedateTodayAnalysis end date
daysinteger7Days to analyze if dates not provided (1-90)

Response

{
"success": true,
"data": {
"location_id": "550e8400-e29b-41d4-a716-446655449110",
"tenant_id": "550e8400-e29b-41d4-a716-446655449100",
"start_date": "2026-02-01",
"end_date": "2026-02-07",
"courses": [
{
"course_name": "Appetizers",
"course_number": 1,
"avg_fire_delay_minutes": 1.2,
"fire_on_time_pct": 92.5,
"auto_fire_rate": 68.0,
"avg_prep_time_minutes": 8.4,
"prep_time_variance": 2.1,
"avg_hold_time_minutes": 1.8,
"avg_gap_from_prior_minutes": 0.0,
"gap_too_short_pct": 0.0,
"gap_too_long_pct": 0.0,
"avg_course_duration_minutes": 14.2,
"course_completion_rate": 98.5,
"total_orders": 312
},
{
"course_name": "Entrees",
"course_number": 2,
"avg_fire_delay_minutes": 2.4,
"fire_on_time_pct": 85.3,
"auto_fire_rate": 45.0,
"avg_prep_time_minutes": 14.6,
"prep_time_variance": 4.8,
"avg_hold_time_minutes": 2.5,
"avg_gap_from_prior_minutes": 12.3,
"gap_too_short_pct": 8.2,
"gap_too_long_pct": 5.1,
"avg_course_duration_minutes": 22.8,
"course_completion_rate": 97.1,
"total_orders": 298
},
{
"course_name": "Desserts",
"course_number": 3,
"avg_fire_delay_minutes": 1.8,
"fire_on_time_pct": 88.7,
"auto_fire_rate": 55.0,
"avg_prep_time_minutes": 6.2,
"prep_time_variance": 1.5,
"avg_hold_time_minutes": 1.1,
"avg_gap_from_prior_minutes": 18.5,
"gap_too_short_pct": 3.4,
"gap_too_long_pct": 12.0,
"avg_course_duration_minutes": 10.5,
"course_completion_rate": 94.2,
"total_orders": 185
}
]
}
}

Response Fields

FieldTypeDescription
course_namestringName of the course template
course_numberintegerSequential course number
avg_fire_delay_minutesfloatAverage delay from scheduled fire to actual fire
fire_on_time_pctfloatPercentage of fires that happened on time
auto_fire_ratefloatPercentage of courses fired automatically
avg_prep_time_minutesfloatAverage kitchen preparation time
prep_time_variancefloatStandard deviation of prep time
avg_hold_time_minutesfloatAverage time course waited at the pass
avg_gap_from_prior_minutesfloatAverage gap from the previous course
gap_too_short_pctfloatPercentage of gaps that were too short
gap_too_long_pctfloatPercentage of gaps that were too long
avg_course_duration_minutesfloatAverage total duration of the course
course_completion_ratefloatPercentage of courses that completed normally
total_ordersintegerNumber of orders that included this course

Get Station Performance

Returns performance metrics for each kitchen station, measuring how well stations coordinate during coursed service.

Request

GET /api/v1/coursing/analytics/stations/{location_id}?
start_date=2026-02-01&
end_date=2026-02-07
Authorization: Bearer {access_token}

Path Parameters

ParameterTypeRequiredDescription
location_idstringYesLocation identifier (UUID)

Query Parameters

ParameterTypeDefaultDescription
start_datedateComputedAnalysis start date
end_datedateTodayAnalysis end date
daysinteger7Days to analyze if dates not provided (1-90)

Response

{
"success": true,
"data": {
"location_id": "550e8400-e29b-41d4-a716-446655449110",
"tenant_id": "550e8400-e29b-41d4-a716-446655449100",
"start_date": "2026-02-01",
"end_date": "2026-02-07",
"stations": [
{
"station_name": "Grill",
"tickets_completed": 485,
"items_completed": 1120,
"avg_items_per_hour": 18.5,
"avg_ticket_time_minutes": 12.3,
"ticket_time_p95_minutes": 22.1,
"avg_sync_delay_minutes": 1.8,
"sync_success_rate": 91.2,
"remake_rate": 2.1,
"void_rate": 0.8
},
{
"station_name": "Saute",
"tickets_completed": 390,
"items_completed": 875,
"avg_items_per_hour": 14.2,
"avg_ticket_time_minutes": 10.8,
"ticket_time_p95_minutes": 18.5,
"avg_sync_delay_minutes": 2.4,
"sync_success_rate": 87.5,
"remake_rate": 1.5,
"void_rate": 0.5
},
{
"station_name": "Pastry",
"tickets_completed": 210,
"items_completed": 420,
"avg_items_per_hour": 10.8,
"avg_ticket_time_minutes": 7.2,
"ticket_time_p95_minutes": 12.4,
"avg_sync_delay_minutes": 0.9,
"sync_success_rate": 95.8,
"remake_rate": 0.8,
"void_rate": 0.3
}
]
}
}

Response Fields

FieldTypeDescription
station_namestringKitchen station name
tickets_completedintegerTotal tickets completed in the period
items_completedintegerTotal items completed
avg_items_per_hourfloatAverage throughput per hour
avg_ticket_time_minutesfloatAverage time to complete a ticket
ticket_time_p95_minutesfloat95th percentile ticket completion time
avg_sync_delay_minutesfloatAverage delay when syncing with other stations
sync_success_ratefloatPercentage of successful cross-station syncs
remake_ratefloatPercentage of items that required a remake
void_ratefloatPercentage of items that were voided

Get Service Metrics

Returns server-side coursing metrics covering fire response time, pace selection patterns, and the breakdown of automatic versus manual course fires.

Request

GET /api/v1/coursing/analytics/service/{location_id}?
start_date=2026-02-01&
end_date=2026-02-07
Authorization: Bearer {access_token}

Path Parameters

ParameterTypeRequiredDescription
location_idstringYesLocation identifier (UUID)

Query Parameters

ParameterTypeDefaultDescription
start_datedateComputedAnalysis start date
end_datedateTodayAnalysis end date
daysinteger7Days to analyze if dates not provided (1-90)

Response

{
"success": true,
"data": {
"location_id": "550e8400-e29b-41d4-a716-446655449110",
"tenant_id": "550e8400-e29b-41d4-a716-446655449100",
"start_date": "2026-02-01",
"end_date": "2026-02-07",
"metrics": {
"avg_fire_response_seconds": 24.5,
"fire_response_rate": 94.2,
"avg_pace_selection": "normal",
"pace_change_rate": 18.3,
"avg_pickup_delay_seconds": 42.8,
"total_fires": 1245,
"auto_fires": 720,
"manual_fires": 525
}
}
}

Response Fields

FieldTypeDescription
avg_fire_response_secondsfloatAverage time for servers to respond to fire notifications
fire_response_ratefloatPercentage of fires acknowledged by the server
avg_pace_selectionstringMost common pace setting (leisurely, normal, quick, custom)
pace_change_ratefloatPercentage of orders where guests changed the pace mid-meal
avg_pickup_delay_secondsfloatAverage delay between course ready and server pickup
total_firesintegerTotal number of course fires in the period
auto_firesintegerNumber of courses fired automatically by the system
manual_firesintegerNumber of courses fired manually by a server

Returns time-series trend data for a selected timing metric, aggregated by day, week, or month.

Request

GET /api/v1/coursing/analytics/trends/{location_id}?
metric=fire_delay&
period=day&
lookback_days=30
Authorization: Bearer {access_token}

Path Parameters

ParameterTypeRequiredDescription
location_idstringYesLocation identifier (UUID)

Query Parameters

ParameterTypeDefaultDescription
metricstringfire_delayMetric to trend (see values below)
periodstringdayAggregation granularity: day, week, month
lookback_daysinteger30Number of days to look back (7-365)

Available Metrics

MetricDescription
fire_delayTime from scheduled fire to actual fire
prep_timeKitchen preparation time per course
gap_timeGap between consecutive courses
hold_timeTime course held at the window before pickup
sync_delayStation coordination delay

Response

{
"success": true,
"data": {
"location_id": "550e8400-e29b-41d4-a716-446655449110",
"tenant_id": "550e8400-e29b-41d4-a716-446655449100",
"metric": "fire_delay",
"period": "day",
"lookback_days": 30,
"trends": [
{
"date": "2026-01-21",
"value": 2.8,
"sample_count": 45
},
{
"date": "2026-01-22",
"value": 2.5,
"sample_count": 52
},
{
"date": "2026-01-23",
"value": 3.1,
"sample_count": 38
},
{
"date": "2026-01-24",
"value": 1.9,
"sample_count": 61
},
{
"date": "2026-01-25",
"value": 2.2,
"sample_count": 58
}
]
}
}

Response Fields

FieldTypeDescription
datestringPeriod start date (format depends on period granularity)
valuefloatAverage metric value for the period
sample_countintegerNumber of data points aggregated into this period

Get Delay Breakdown

Returns a categorized breakdown of delay causes across kitchen, fire, server, and coordination categories.

Request

GET /api/v1/coursing/analytics/delays/{location_id}?
start_date=2026-02-01&
end_date=2026-02-07
Authorization: Bearer {access_token}

Path Parameters

ParameterTypeRequiredDescription
location_idstringYesLocation identifier (UUID)

Query Parameters

ParameterTypeDefaultDescription
start_datedateComputedAnalysis start date
end_datedateTodayAnalysis end date
daysinteger7Days to analyze if dates not provided (1-90)

Response

{
"success": true,
"data": {
"location_id": "550e8400-e29b-41d4-a716-446655449110",
"tenant_id": "550e8400-e29b-41d4-a716-446655449100",
"start_date": "2026-02-01",
"end_date": "2026-02-07",
"breakdown": {
"kitchen_delays": 42,
"fire_delays": 28,
"server_delays": 15,
"coordination_delays": 10,
"total_delays": 95,
"avg_delay_minutes": 3.8,
"kitchen_pct": 44.2,
"fire_pct": 29.5,
"server_pct": 15.8,
"coordination_pct": 10.5
}
}
}

Delay Categories

CategoryDescription
kitchen_delaysKitchen preparation took longer than expected
fire_delaysServer was slow to fire the next course
server_delaysServer was slow to pick up a ready course
coordination_delaysMultiple stations failed to sync their output

Response Fields

FieldTypeDescription
kitchen_delaysintegerCount of kitchen-caused delays
fire_delaysintegerCount of fire-timing delays
server_delaysintegerCount of server pickup delays
coordination_delaysintegerCount of station sync delays
total_delaysintegerTotal delay events
avg_delay_minutesfloatAverage delay duration in minutes
kitchen_pctfloatKitchen delays as a percentage of total
fire_pctfloatFire delays as a percentage of total
server_pctfloatServer delays as a percentage of total
coordination_pctfloatCoordination delays as a percentage of total

Get Dashboard Summary

Returns a comprehensive coursing analytics dashboard including overall statistics, per-course metrics, an efficiency score, and actionable recommendations.

Request

GET /api/v1/coursing/analytics/dashboard/{location_id}?days=7
Authorization: Bearer {access_token}

Path Parameters

ParameterTypeRequiredDescription
location_idstringYesLocation identifier (UUID)

Query Parameters

ParameterTypeDefaultDescription
daysinteger7Number of days to analyze (1-90)

Response

{
"success": true,
"data": {
"location_id": "550e8400-e29b-41d4-a716-446655449110",
"period_days": 7,
"total_coursed_orders": 795,
"avg_courses_per_order": 3,
"on_time_rate": 88.83,
"avg_meal_duration_minutes": 47.5,
"most_common_pace": "normal",
"top_delay_cause": "kitchen",
"efficiency_score": 87.4,
"course_metrics": [
{
"course_name": "Appetizers",
"course_number": 1,
"avg_fire_delay_minutes": 1.2,
"fire_on_time_pct": 92.5,
"auto_fire_rate": 68.0,
"avg_prep_time_minutes": 8.4,
"prep_time_variance": 2.1,
"avg_hold_time_minutes": 1.8,
"avg_gap_from_prior_minutes": 0.0,
"gap_too_short_pct": 0.0,
"gap_too_long_pct": 0.0,
"avg_course_duration_minutes": 14.2,
"course_completion_rate": 98.5,
"total_orders": 312
},
{
"course_name": "Entrees",
"course_number": 2,
"avg_fire_delay_minutes": 2.4,
"fire_on_time_pct": 85.3,
"auto_fire_rate": 45.0,
"avg_prep_time_minutes": 14.6,
"prep_time_variance": 4.8,
"avg_hold_time_minutes": 2.5,
"avg_gap_from_prior_minutes": 12.3,
"gap_too_short_pct": 8.2,
"gap_too_long_pct": 5.1,
"avg_course_duration_minutes": 22.8,
"course_completion_rate": 97.1,
"total_orders": 298
},
{
"course_name": "Desserts",
"course_number": 3,
"avg_fire_delay_minutes": 1.8,
"fire_on_time_pct": 88.7,
"auto_fire_rate": 55.0,
"avg_prep_time_minutes": 6.2,
"prep_time_variance": 1.5,
"avg_hold_time_minutes": 1.1,
"avg_gap_from_prior_minutes": 18.5,
"gap_too_short_pct": 3.4,
"gap_too_long_pct": 12.0,
"avg_course_duration_minutes": 10.5,
"course_completion_rate": 94.2,
"total_orders": 185
}
],
"recommendations": [
"Kitchen is the primary bottleneck. Review station staffing and prep workflows.",
"Course timing is below target. Consider adjusting prep times or gap settings."
],
"generated_at": "2026-02-20T14:30:00.000000+00:00"
}
}

Response Fields

FieldTypeDescription
location_idstringThe queried location
period_daysintegerNumber of days analyzed
total_coursed_ordersintegerTotal orders that used coursing
avg_courses_per_orderfloatAverage number of courses per order
on_time_ratefloatAverage on-time fire percentage across all courses
avg_meal_duration_minutesfloatAverage total meal duration
most_common_pacestringMost frequently selected pace (leisurely, normal, quick, custom)
top_delay_causestringCategory with the most delays (kitchen, fire, server, coordination, none)
efficiency_scorefloatComposite efficiency score from 0 to 100
course_metricsarrayPer-course timing metrics (same schema as the timing endpoint)
recommendationsarrayList of actionable recommendation strings
generated_atstringISO 8601 timestamp when the dashboard was generated

Export Coursing Analytics

Exports coursing analytics data in CSV or JSON format. Callers can select which data sections to include in the export.

Request

GET /api/v1/coursing/analytics/export/{location_id}?
start_date=2026-02-01&
end_date=2026-02-07&
format=json&
include_course_metrics=true&
include_station_metrics=true&
include_service_metrics=true&
include_delay_breakdown=true
Authorization: Bearer {access_token}

Path Parameters

ParameterTypeRequiredDescription
location_idstringYesLocation identifier (UUID)

Query Parameters

ParameterTypeDefaultDescription
start_datedateComputedExport start date
end_datedateTodayExport end date
daysinteger7Days to export if dates not provided (1-90)
formatstringcsvExport format: csv or json
include_course_metricsbooleantrueInclude per-course timing data
include_station_metricsbooleantrueInclude station performance data
include_service_metricsbooleantrueInclude server-side service data
include_delay_breakdownbooleantrueInclude delay categorization data

Response (JSON format)

{
"success": true,
"data": {
"metadata": {
"tenant_id": "550e8400-e29b-41d4-a716-446655449100",
"location_id": "550e8400-e29b-41d4-a716-446655449110",
"start_date": "2026-02-01",
"end_date": "2026-02-07",
"exported_at": "2026-02-20T14:30:00.000000+00:00",
"format": "json"
},
"sections": {
"course_timing": [
{
"course_name": "Appetizers",
"course_number": 1,
"avg_fire_delay_minutes": 1.2,
"fire_on_time_pct": 92.5,
"auto_fire_rate": 68.0,
"avg_prep_time_minutes": 8.4,
"prep_time_variance": 2.1,
"avg_hold_time_minutes": 1.8,
"avg_gap_from_prior_minutes": 0.0,
"gap_too_short_pct": 0.0,
"gap_too_long_pct": 0.0,
"avg_course_duration_minutes": 14.2,
"course_completion_rate": 98.5,
"total_orders": 312
}
],
"station_performance": [
{
"station_name": "Grill",
"tickets_completed": 485,
"items_completed": 1120,
"avg_items_per_hour": 18.5,
"avg_ticket_time_minutes": 12.3,
"ticket_time_p95_minutes": 22.1,
"avg_sync_delay_minutes": 1.8,
"sync_success_rate": 91.2,
"remake_rate": 2.1,
"void_rate": 0.8
}
],
"service_metrics": {
"avg_fire_response_seconds": 24.5,
"fire_response_rate": 94.2,
"avg_pace_selection": "normal",
"pace_change_rate": 18.3,
"avg_pickup_delay_seconds": 42.8,
"total_fires": 1245,
"auto_fires": 720,
"manual_fires": 525
},
"delay_breakdown": {
"kitchen_delays": 42,
"fire_delays": 28,
"server_delays": 15,
"coordination_delays": 10,
"total_delays": 95,
"avg_delay_minutes": 3.8,
"kitchen_pct": 44.2,
"fire_pct": 29.5,
"server_pct": 15.8,
"coordination_pct": 10.5
}
}
}
}

Response (CSV format)

When format=csv, the response includes a csv_files object containing base64-encoded CSV content for each included section:

{
"success": true,
"data": {
"metadata": {
"tenant_id": "550e8400-e29b-41d4-a716-446655449100",
"location_id": "550e8400-e29b-41d4-a716-446655449110",
"start_date": "2026-02-01",
"end_date": "2026-02-07",
"exported_at": "2026-02-20T14:30:00.000000+00:00",
"format": "csv"
},
"sections": { ... },
"csv_files": {
"course_timing.csv": "course_name,course_number,avg_fire_delay_minutes,...\nAppetizers,1,1.2,...\n",
"station_performance.csv": "station_name,tickets_completed,...\nGrill,485,...\n",
"service_metrics.csv": "avg_fire_response_seconds,...\n24.5,...\n",
"delay_breakdown.csv": "kitchen_delays,fire_delays,...\n42,28,...\n"
}
}
}

Error Responses

Invalid Date Range (400)

Returned when start_date is after end_date.

{
"detail": "start_date must be before or equal to end_date"
}

Unauthorized (401)

Returned when the JWT token is missing or invalid.

{
"detail": "Not authenticated"
}

Forbidden (403)

Returned when the tenant does not have the ANALYTICS_CORE_POLICY gating policy enabled.

{
"detail": "Feature not enabled for this tenant"
}

Location Not Found (404)

Returned when the location does not exist or is not accessible to the caller's tenant.

{
"detail": "Location not found"
}

Internal Server Error (500)

Returned when the analytics backend (ClickHouse) encounters an error. The endpoints degrade gracefully by returning empty result sets rather than failing outright.

{
"detail": "Internal server error"
}