import { canTransition, createRegistry, createScene, editPatch, inversePatch, validateParams, type CommandState, type OperationDefinition, type ProcedureDefinition, type SceneObjectPatch } from "@klorad/api/world";
import { WHITE_TOWER } from "./thessaloniki";
const operations: OperationDefinition[] = [
{ id: "cooling.setpoint", version: 1, label: "Set cooling setpoint", effect: "actuation", appliesTo: { correspondence: ["twin"] }, params: { celsius: { type: "number", label: "Setpoint", min: 16, max: 30, unit: "°C", required: true } }, command: "setpoint", timeoutMs: 5_000, compensation: { operation: "cooling.setpoint", version: 1 } },
{ id: "maintenance.flag", version: 1, label: "Flag for inspection", effect: "model-edit", appliesTo: {}, params: {}, edit: { properties: { inspection: { value: "requested" } } } },
{ id: "maintenance.inspected", version: 1, label: "Mark inspected", effect: "model-edit", appliesTo: {}, params: {}, edit: { properties: { inspection: { value: "done" } } } },
];
const procedures: ProcedureDefinition[] = [
{ id: "cooling.check", version: 1, label: "Cooling check", steps: [{ operation: "cooling.setpoint", version: 1, label: "Set 21 °C", params: { celsius: 21 } }, { operation: "maintenance.inspected", version: 1, label: "Record it", params: {} }] },
];
export interface DemoCommand {
readonly id: number;
readonly operation: string;
state: CommandState;
note: string;
undo?: SceneObjectPatch;
undone?: boolean;
}
/**
* A server room with a cooling unit (a Digital Twin) and a rack. Commands run through their life
* cycle against a simulated device that answers after a second; model edits are applied at once
* and can be undone. On the platform the same rules run on the server, journaled and audited.
*/
export function createOperationsDemo(onChange: () => void) {
const registry = createRegistry(operations, procedures);
const scene = createScene({ name: "Server room", coordinateSystem: { origin: WHITE_TOWER } });
scene.add({ id: "room", name: "Server room", position: { east: 0, north: 0 }, geometry: { kind: "extrusion", footprint: [[-8, -6], [8, -6], [8, 6], [-8, 6]], height: 0.2 }, properties: { kind: "room" } });
scene.add({ id: "crac", name: "Cooling unit", position: { east: -5, north: 3 }, geometry: { kind: "box", width: 2.4, depth: 1.2, height: 1.9 }, correspondence: "twin", binding: { source: "bms", entity: "crac-1" }, properties: { kind: "crac", inspection: "none" } });
scene.add({ id: "rack", name: "Rack A", position: { east: 2, north: 0 }, geometry: { kind: "box", width: 0.8, depth: 1.2, height: 2.1 }, properties: { kind: "rack", inspection: "none" } });
const commands: DemoCommand[] = [];
let next = 1;
const move = (c: DemoCommand, to: CommandState, note: string) => {
if (!canTransition(c.state, to)) return;
c.state = to;
c.note = note;
onChange();
};
function run(operation: string, objectId: string, params: Record<string, number>, after?: (c: DemoCommand) => void): DemoCommand | string {
const op = registry.get(operation);
const object = scene.get(objectId);
if (!op || !object) return "unknown operation or object";
const bad = validateParams(op, params);
if (bad) return bad;
const c: DemoCommand = { id: next++, operation, state: "accepted", note: "authorised and recorded" };
commands.unshift(c);
if (op.effect === "model-edit") {
const patch = editPatch(op, params);
c.undo = inversePatch(object, patch);
scene.update(objectId, patch);
move(c, "acknowledged", "applied to the scene");
after?.(c);
return c;
}
onChange();
setTimeout(() => move(c, "dispatched", "sent to the simulated device"), 250);
setTimeout(() => {
scene.observe({ objectId, quantity: op.command!, value: params.celsius!, observedAt: Date.now(), source: "bms" });
move(c, "acknowledged", "the simulated device confirmed it");
after?.(c);
}, 1_200);
return c;
}
function undo(c: DemoCommand, objectId: string): void {
if (!c.undo || c.undone) return;
scene.update(objectId, c.undo);
c.undone = true;
c.note = "undone";
onChange();
}
function runProcedure(id: string, objectId: string): void {
const p = registry.procedure(id)!;
const step = (i: number) => {
const s = p.steps[i];
if (!s) return;
run(s.operation, objectId, s.params as Record<string, number>, (c) => c.state === "acknowledged" && step(i + 1));
};
step(0);
}
return { scene, registry, commands, run, undo, runProcedure };
}