342 lines
9.0 KiB
TypeScript
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';
|
||
|
|
}
|
||
|
|
}
|