Files
bakery-ia/frontend/src/api/services/orchestrator.ts
2025-10-30 21:08:07 +01:00

342 lines
9.0 KiB
TypeScript

/**
* Orchestrator Service API Client
* Handles coordinated workflows across Forecasting, Production, and Procurement services
*
* NEW in Sprint 2: Orchestrator Service coordinates the daily workflow:
* 1. Forecasting Service → Get demand forecasts
* 2. Production Service → Generate production schedule from forecast
* 3. Procurement Service → Generate procurement plan from forecast + schedule
*/
import { apiClient } from '../client';
// ============================================================================
// ORCHESTRATOR WORKFLOW TYPES
// ============================================================================
export interface OrchestratorWorkflowRequest {
target_date?: string; // YYYY-MM-DD, defaults to tomorrow
planning_horizon_days?: number; // Default: 14
// Forecasting options
forecast_days_ahead?: number; // Default: 7
// Production options
auto_schedule_production?: boolean; // Default: true
production_planning_days?: number; // Default: 1
// Procurement options
auto_create_purchase_orders?: boolean; // Default: true
auto_approve_purchase_orders?: boolean; // Default: false
safety_stock_percentage?: number; // Default: 20.00
// Orchestrator options
skip_on_error?: boolean; // Continue to next step if one fails
notify_on_completion?: boolean; // Send notification when done
}
export interface WorkflowStepResult {
step: 'forecasting' | 'production' | 'procurement';
status: 'success' | 'failed' | 'skipped';
duration_ms: number;
data?: any;
error?: string;
warnings?: string[];
}
export interface OrchestratorWorkflowResponse {
success: boolean;
workflow_id: string;
tenant_id: string;
target_date: string;
execution_date: string;
total_duration_ms: number;
steps: WorkflowStepResult[];
// Step-specific results
forecast_result?: {
forecast_id: string;
total_forecasts: number;
forecast_data: any;
};
production_result?: {
schedule_id: string;
total_batches: number;
total_quantity: number;
};
procurement_result?: {
plan_id: string;
total_requirements: number;
total_cost: string;
purchase_orders_created: number;
purchase_orders_auto_approved: number;
};
warnings?: string[];
errors?: string[];
}
export interface WorkflowExecutionSummary {
id: string;
tenant_id: string;
target_date: string;
status: 'running' | 'completed' | 'failed' | 'cancelled';
started_at: string;
completed_at?: string;
total_duration_ms?: number;
steps_completed: number;
steps_total: number;
created_by?: string;
}
export interface WorkflowExecutionDetail extends WorkflowExecutionSummary {
steps: WorkflowStepResult[];
forecast_id?: string;
production_schedule_id?: string;
procurement_plan_id?: string;
warnings?: string[];
errors?: string[];
}
// ============================================================================
// ORCHESTRATOR WORKFLOW API FUNCTIONS
// ============================================================================
/**
* Run the daily orchestrated workflow
* This is the main entry point for coordinated planning
*
* Workflow:
* 1. Forecasting Service: Get demand forecasts for target date
* 2. Production Service: Generate production schedule from forecast
* 3. Procurement Service: Generate procurement plan from forecast + schedule
*
* NEW in Sprint 2: Replaces autonomous schedulers with centralized orchestration
*/
export async function runDailyWorkflow(
tenantId: string,
request?: OrchestratorWorkflowRequest
): Promise<OrchestratorWorkflowResponse> {
return apiClient.post<OrchestratorWorkflowResponse>(
`/tenants/${tenantId}/orchestrator/run-daily-workflow`,
request || {}
);
}
/**
* Run workflow for a specific date
*/
export async function runWorkflowForDate(
tenantId: string,
targetDate: string,
options?: Omit<OrchestratorWorkflowRequest, 'target_date'>
): Promise<OrchestratorWorkflowResponse> {
return runDailyWorkflow(tenantId, {
...options,
target_date: targetDate
});
}
/**
* Test workflow with sample data (for development/testing)
*/
export async function testWorkflow(
tenantId: string
): Promise<OrchestratorWorkflowResponse> {
return apiClient.post<OrchestratorWorkflowResponse>(
`/tenants/${tenantId}/orchestrator/test-workflow`,
{}
);
}
/**
* Get list of workflow executions
*/
export async function listWorkflowExecutions(
tenantId: string,
params?: {
status?: WorkflowExecutionSummary['status'];
date_from?: string;
date_to?: string;
limit?: number;
offset?: number;
}
): Promise<WorkflowExecutionSummary[]> {
return apiClient.get<WorkflowExecutionSummary[]>(
`/tenants/${tenantId}/orchestrator/executions`,
{ params }
);
}
/**
* Get a single workflow execution by ID with full details
*/
export async function getWorkflowExecution(
tenantId: string,
executionId: string
): Promise<WorkflowExecutionDetail> {
return apiClient.get<WorkflowExecutionDetail>(
`/tenants/${tenantId}/orchestrator/executions/${executionId}`
);
}
/**
* Get latest workflow execution
*/
export async function getLatestWorkflowExecution(
tenantId: string
): Promise<WorkflowExecutionDetail | null> {
const executions = await listWorkflowExecutions(tenantId, {
limit: 1
});
if (executions.length === 0) {
return null;
}
return getWorkflowExecution(tenantId, executions[0].id);
}
/**
* Cancel a running workflow execution
*/
export async function cancelWorkflowExecution(
tenantId: string,
executionId: string
): Promise<{ message: string }> {
return apiClient.post<{ message: string }>(
`/tenants/${tenantId}/orchestrator/executions/${executionId}/cancel`,
{}
);
}
/**
* Retry a failed workflow execution
*/
export async function retryWorkflowExecution(
tenantId: string,
executionId: string
): Promise<OrchestratorWorkflowResponse> {
return apiClient.post<OrchestratorWorkflowResponse>(
`/tenants/${tenantId}/orchestrator/executions/${executionId}/retry`,
{}
);
}
// ============================================================================
// ORCHESTRATOR STATUS & HEALTH
// ============================================================================
export interface OrchestratorStatus {
is_leader: boolean;
scheduler_running: boolean;
next_scheduled_run?: string;
last_execution?: {
id: string;
target_date: string;
status: string;
completed_at: string;
};
total_executions_today: number;
total_successful_executions: number;
total_failed_executions: number;
}
/**
* Get orchestrator service status
*/
export async function getOrchestratorStatus(
tenantId: string
): Promise<OrchestratorStatus> {
return apiClient.get<OrchestratorStatus>(
`/tenants/${tenantId}/orchestrator/status`
);
}
// ============================================================================
// ORCHESTRATOR CONFIGURATION
// ============================================================================
export interface OrchestratorConfig {
enabled: boolean;
schedule_cron: string; // Cron expression for daily run
default_planning_horizon_days: number;
auto_create_purchase_orders: boolean;
auto_approve_purchase_orders: boolean;
safety_stock_percentage: number;
notify_on_completion: boolean;
notify_on_failure: boolean;
skip_on_error: boolean;
}
/**
* Get orchestrator configuration for tenant
*/
export async function getOrchestratorConfig(
tenantId: string
): Promise<OrchestratorConfig> {
return apiClient.get<OrchestratorConfig>(
`/tenants/${tenantId}/orchestrator/config`
);
}
/**
* Update orchestrator configuration
*/
export async function updateOrchestratorConfig(
tenantId: string,
config: Partial<OrchestratorConfig>
): Promise<OrchestratorConfig> {
return apiClient.put<OrchestratorConfig>(
`/tenants/${tenantId}/orchestrator/config`,
config
);
}
// ============================================================================
// HELPER FUNCTIONS
// ============================================================================
/**
* Format workflow duration for display
*/
export function formatWorkflowDuration(durationMs: number): string {
if (durationMs < 1000) {
return `${durationMs}ms`;
} else if (durationMs < 60000) {
return `${(durationMs / 1000).toFixed(1)}s`;
} else {
const minutes = Math.floor(durationMs / 60000);
const seconds = Math.floor((durationMs % 60000) / 1000);
return `${minutes}m ${seconds}s`;
}
}
/**
* Get workflow step status icon
*/
export function getWorkflowStepStatusIcon(status: WorkflowStepResult['status']): string {
switch (status) {
case 'success': return '✅';
case 'failed': return '❌';
case 'skipped': return '⏭️';
default: return '❓';
}
}
/**
* Get workflow overall status color
*/
export function getWorkflowStatusColor(status: WorkflowExecutionSummary['status']): string {
switch (status) {
case 'completed': return 'green';
case 'running': return 'blue';
case 'failed': return 'red';
case 'cancelled': return 'gray';
default: return 'gray';
}
}