export type RecapSkuRow = { machineName: string; sku: string; good: number; scrap: number; target: number | null; progressPct: number | null; }; export type RecapMachine = { machineId: string; machineName: string; location: string | null; production: { goodParts: number; scrapParts: number; totalCycles: number; bySku: RecapSkuRow[]; }; oee: { avg: number | null; availability: number | null; performance: number | null; quality: number | null; }; downtime: { totalMin: number; stopsCount: number; topReasons: Array<{ reasonLabel: string; minutes: number; count: number; }>; ongoingStopMin: number | null; }; workOrders: { completed: Array<{ id: string; sku: string | null; goodParts: number; durationHrs: number; }>; active: { id: string; sku: string | null; progressPct: number | null; startedAt: string | null; } | null; moldChangeInProgress: boolean; moldChangeStartMs: number | null; }; heartbeat: { lastSeenAt: string | null; uptimePct: number | null; }; }; export type RecapTimelineSegment = | { type: "production"; startMs: number; endMs: number; durationSec: number; workOrderId: string | null; sku: string | null; label: string; } | { type: "mold-change"; startMs: number; endMs: number; fromMoldId: string | null; toMoldId: string | null; durationSec: number; label: string; } | { type: "macrostop" | "microstop" | "slow-cycle"; startMs: number; endMs: number; reason: string | null; reasonLabel?: string | null; durationSec: number; label: string; } | { type: "idle"; startMs: number; endMs: number; durationSec: number; label: string; }; export type RecapTimelineResponse = { range: { start: string; end: string; }; segments: RecapTimelineSegment[]; hasData: boolean; generatedAt: string; }; export type RecapResponse = { range: { start: string; end: string; }; availableShifts: Array<{ id: string; name: string; }>; machines: RecapMachine[]; }; export type RecapQuery = { orgId: string; machineId?: string; start?: Date; end?: Date; shift?: string; }; export type RecapMachineStatus = "running" | "mold-change" | "stopped" | "offline" | "idle"; /** * Reason context — currently empty in practice because the only STOPPED cause * we can detect (given Node-RED's constraints) is machine_fault. Kept as a * struct so future expansion doesn't require a type change downstream. */ export type RecapStateContext = Record; export type RecapSummaryMachine = { machineId: string; name: string; location: string | null; status: RecapMachineStatus; oee: number | null; goodParts: number; scrap: number; stopsCount: number; lastSeenMs: number | null; lastActivityMin: number | null; offlineForMin: number | null; ongoingStopMin: number | null; stateContext: RecapStateContext; activeWorkOrderId: string | null; moldChange: { active: boolean; startMs: number | null; elapsedMin: number | null; } | null; miniTimeline: RecapTimelineSegment[]; }; export type RecapSummaryResponse = { generatedAt: string; range: { start: string; end: string; hours: number; }; machines: RecapSummaryMachine[]; }; export type RecapRangeMode = "24h" | "shift" | "yesterday" | "custom"; export type RecapDowntimeTopRow = { reasonLabel: string; minutes: number; count: number; percent: number; }; export type RecapWorkOrders = { completed: Array<{ id: string; sku: string | null; goodParts: number; durationHrs: number; }>; active: { id: string; sku: string | null; progressPct: number | null; startedAt: string | null; } | null; }; export type RecapMachineDetail = { machineId: string; name: string; location: string | null; status: RecapMachineStatus; oee: number | null; goodParts: number; scrap: number; stopsCount: number; stopMinutes: number; activeWorkOrderId: string | null; lastSeenMs: number | null; offlineForMin: number | null; ongoingStopMin: number | null; stateContext: RecapStateContext; moldChange: { active: boolean; startMs: number | null; } | null; timeline: RecapTimelineSegment[]; productionBySku: RecapSkuRow[]; downtimeTop: RecapDowntimeTopRow[]; workOrders: RecapWorkOrders; heartbeat: { lastSeenAt: string | null; uptimePct: number | null; connectionStatus: "online" | "offline"; }; }; export type RecapDetailResponse = { generatedAt: string; range: { requestedMode?: RecapRangeMode; mode: RecapRangeMode; start: string; end: string; shiftAvailable?: boolean; fallbackReason?: "shift-unavailable" | "shift-inactive"; }; machine: RecapMachineDetail; };