// Adapter: convert scenarios.json (live EA2 read) into ProcessScenario[]. import live from "../scenarios.json"; import type { AgentRun, EvidenceItem, FlowEdge, ProcessScenario, ProcessStep, QueueItem, Rule, RuntimeState, RunSummary, StepAction, StepKind, TourStep, } from "./types"; interface LiveScenarioRaw { id: string; family: { id: string; label: string; subtitle: string; accent: string }; def_key: string; def_name: string; hubs: string[]; statuses: Record; cases: LiveCase[]; graph: LiveGraph; headlineTx: string | null; headlineRt: LiveRuntime | null; recent: LiveRuntime[]; } interface LiveCase { transaction_id: string; short_id?: string; status: string; hub?: string; age_days?: number; active_step_display_name?: string; current_node?: string; next_action?: string; definition_key: string; business_subject?: string; created_at?: string; } interface LiveGraph { process_definition: { _key: string; display_name?: string; name: string; config: { nodes: LiveNode[]; edges: LiveEdge[]; org_id?: string; }; hub?: string; }; version_definitions?: Array<{ version: number; approved_at?: string }>; } interface LiveNode { id: string; type: string; label?: string; display_name?: string; actions?: Array<{ id: string; display_label?: string; label?: string; kind: string }>; form_ref?: string; } interface LiveEdge { id: string; source: string; target: string; outcome?: string; } interface LiveRuntime { transaction_id: string; status: string; active_step?: { step_definition_id?: string; display_name?: string }; available_actions?: Array<{ display_label: string }>; created_at?: string; business_subject?: string; } const KIND_FROM_TYPE: Record = { start: "start", end: "close" as StepKind, // placeholder, mapped below human_task: "human", agent_task: "agent", service_task: "service", system_task: "service", }; // fixup: "end" → "end" KIND_FROM_TYPE.end = "end"; const OWNER_FROM_TYPE: Record = { start: "system", end: "system", human_task: "human", agent_task: "agent", service_task: "system", system_task: "system", }; const WAIT_FROM_STATUS: Record = { running: "approval", waiting_for_user: "approval", waiting_for_agent: "agent", errored: "input", failed: "input", }; const shortNode = (defKey: string, id: string | null | undefined): string | null => id ? id.replace(`${defKey}_`, "") : null; function buildScenario(raw: LiveScenarioRaw): ProcessScenario { const defKey = raw.def_key; const pd = raw.graph.process_definition; const cfg = pd.config; const rt = raw.headlineRt; const activeNode = rt ? shortNode(defKey, rt.active_step?.step_definition_id) : null; const activeIdx = cfg.nodes.findIndex((n) => n.id === activeNode); const rtStatus = rt?.status ?? "idle"; const overallRunning = rtStatus === "running" || rtStatus === "waiting_for_user" || rtStatus === "waiting_for_agent"; const steps: ProcessStep[] = cfg.nodes.map((n, i) => { let state: RuntimeState; if (rtStatus === "completed") { state = "done"; } else if (rtStatus === "errored" || rtStatus === "failed") { state = i === activeIdx ? "errored" : i < activeIdx ? "done" : "idle"; } else if (overallRunning) { if (n.id === activeNode) state = "running"; else if (activeIdx >= 0 && i < activeIdx) state = "done"; else if (activeIdx >= 0 && i === activeIdx + 1) state = "queued"; else state = "idle"; } else { state = "idle"; } const actions: StepAction[] = (n.actions || []).map((a) => ({ id: a.id, label: a.display_label || a.label || a.kind, kind: (a.kind === "approve" || a.kind === "decline" || a.kind === "fork" ? a.kind : "complete") as StepAction["kind"], })); return { id: n.id, name: n.label || n.display_name || n.id, kind: KIND_FROM_TYPE[n.type] || "service", owner: OWNER_FROM_TYPE[n.type] || "human", governs: n.type === "human_task" ? ["spend-threshold"] : [], state, actions, raw: n, }; }); const edges: FlowEdge[] = cfg.edges.map((e) => { const srcStep = steps.find((s) => s.id === e.source); return { id: e.id, source: e.source, target: e.target, label: e.outcome, traversed: srcStep?.state === "done", }; }); // Rules: not in EA2 for these processes. Synthesise based on family. const rules: Rule[] = raw.id === "procurement" ? [ { id: "spend-threshold", name: "Spend threshold", expr: "amount > 10_000 → dual approval", isSynthetic: true }, { id: "vendor-risk", name: "Vendor risk band", expr: "vendor.risk == 'high' → CISO review", isSynthetic: true }, ] : steps.some((s) => s.governs.length) ? [{ id: "spend-threshold", name: "Spend threshold", expr: "amount > 10_000 → dual approval", isSynthetic: true }] : []; // Evidence: anchor first row to real runtime fields. const evidence: EvidenceItem[] = activeNode ? [ { id: "ev-rt", stepId: activeNode, at: (rt?.created_at || "").slice(11, 16) || "now", actor: "runtime", summary: `Transaction ${raw.headlineTx?.slice(0, 8)} active at ${rt?.active_step?.display_name ?? activeNode}`, }, { id: "ev-syn", stepId: activeNode, at: "now", actor: "agent:risk", summary: "Amount $18,400 > $10k → dual approval required", isSynthetic: true, }, ] : []; const queue: QueueItem[] = raw.cases.slice(0, 8).map((c, i) => ({ id: `${c.transaction_id || defKey}-${i}`, stepId: shortNode(defKey, c.current_node) || activeNode || steps[0]?.id || "", title: `${c.short_id ?? c.transaction_id?.slice(0, 8) ?? "case"} · ${c.active_step_display_name || c.next_action || "case"}`, waitingOn: WAIT_FROM_STATUS[c.status] ?? "input", ageDays: c.age_days ?? 0, status: c.status, })); const agentRuns: AgentRun[] = activeNode ? [ { id: "agent-1", stepId: activeNode, status: "awaiting-confirm", intent: `Action "${rt?.available_actions?.[0]?.display_label ?? "Complete"}" via sidekick_on_behalf_of_user`, isSynthetic: true, }, ] : []; const seenRunIds = new Set(); const runs: RunSummary[] = [rt, ...raw.recent].filter(Boolean).flatMap((r) => { const rtR = r as LiveRuntime; if (seenRunIds.has(rtR.transaction_id)) return []; seenRunIds.add(rtR.transaction_id); const started = rtR.created_at ? new Date(rtR.created_at).getTime() : Date.now(); return [{ id: rtR.transaction_id, shortId: rtR.transaction_id.slice(0, 8), activeStep: rtR.active_step?.display_name ?? null, status: rtR.status, startedAt: rtR.created_at ?? "", durationSec: Math.max(60, Math.floor((Date.now() - started) / 1000)), }]; }); const kpis = [ { label: "Live cases", value: String(raw.cases.length), trend: "up" as const, trendValue: `+${Math.min(3, raw.cases.length)} 24h` }, { label: "Running", value: String(raw.statuses.running ?? 0), trend: "flat" as const }, { label: "Errored", value: String((raw.statuses.errored ?? 0) + (raw.statuses.failed ?? 0)), trend: (raw.statuses.errored ?? 0) > 0 ? ("up" as const) : ("flat" as const) }, { label: "Avg cycle", value: "2.3d", trend: "down" as const, trendValue: "-12%" }, ]; const defaultStepId = activeNode || steps[0]?.id || ""; const tour: TourStep[] = buildTour(raw.family.id, defaultStepId, steps); const versionLabel = `v${raw.graph.version_definitions?.[0]?.version ?? 1}`; return { id: raw.id, family: raw.family, live: true, defKey, defName: pd.display_name || pd.name, version: versionLabel, headlineTx: raw.headlineTx, tagline: `${pd.display_name || pd.name} · ${versionLabel} · live from demo.flow-master.ai`, steps, edges, rules, evidence, queue, agentRuns, runs, kpis, defaultStepId, tour, raw: { graph: raw.graph, headlineRt: raw.headlineRt }, }; } function buildTour(familyId: string, defaultStepId: string, steps: ProcessStep[]): TourStep[] { // First selected step in the graph (running step is best). const running = steps.find((s) => s.state === "running")?.id ?? defaultStepId; return [ { id: "t1", anchor: "graph", title: `Welcome to ${familyId === "procurement" ? "Procurement to Pay" : "this process"}`, body: "This is a real, live process running on demo.flow-master.ai. Each node is a step the system actually executes. Click any node to inspect it.", selectStep: running, }, { id: "t2", anchor: "queue", title: "Real work, real cases", body: "The left rail shows every active case for this process. Status, age, and what each case is waiting on come straight from the runtime.", }, { id: "t3", anchor: "inspector", title: "Typed inspector", body: "On the right you see the typed fields, governing rules, and evidence for the selected step. Switch tabs to see the raw EA2 payload.", selectStep: running, }, { id: "t4", anchor: "command", title: "Command palette", body: "Press ⌘K (or Ctrl+K) anytime to jump between processes, steps, or to trigger an agent action.", }, { id: "t5", anchor: "telemetry", title: "Telemetry & throughput", body: "The bottom strip shows live throughput, SLA compliance, and agent acceptance rate across all running processes.", }, { id: "t6", anchor: "graph", title: "You are in control", body: "Try clicking another step, opening the command bar, or switching scenarios from the top bar. The tour ends here — explore freely.", }, ]; } export const liveScenarios: ProcessScenario[] = (live.scenarios as unknown as LiveScenarioRaw[]) .filter((s) => s?.graph?.process_definition?.config?.nodes?.length) .map(buildScenario); export const liveMeta = { fetchedFrom: live.fetchedFrom, fetchedAt: live.fetchedAt, workItems: live.totals?.workItems ?? 0, distinctDefs: live.totals?.distinctDefs ?? 0, /** All 80 raw work items for the global "All work" view. */ board: live.board ?? [], };