Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 21 additions & 12 deletions apps/operator-backend/src/deterministic/room_receptacle_analog.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { randomUUID } from "node:crypto";
import { OPERATOR_BACKEND_CONTRACT_VERSION, type ChatRequest, type ChatResponse, type ToolResult } from "../contracts.js";
import type { AecTaskIntentV1 } from "../aec_task_intent.js";
import { appendGoalProgress, getActiveGoalForSession, setAgentGoal } from "../goals/service.js";
import { appendGoalProgress, getActiveGoalForSession, setAgentGoal, updateGoal } from "../goals/service.js";

const PLAN_PATH = "/revit/plan-room-receptacles-from-analog";
const APPLY_PATH = "/revit/apply-room-receptacles-from-analog";
Expand Down Expand Up @@ -41,6 +41,12 @@ function appendRoomDesignProgress(sessionId: string, entry: unknown): void {
appendGoalProgress(sessionId, entry);
}

function updateRoomDesignPhase(sessionId: string, currentPhase: string, currentStep: string): void {
const goal = getActiveGoalForSession(sessionId);
if (!goal) return;
updateGoal(goal.id, { current_phase: currentPhase, current_step: currentStep });
}

function asRecord(value: unknown): Record<string, unknown> | null {
return value !== null && typeof value === "object" && !Array.isArray(value) ? value as Record<string, unknown> : null;
}
Expand Down Expand Up @@ -130,7 +136,7 @@ export function maybeRunDeterministicRoomReceptacleAnalog(req: ChatRequest, inte

if (planResult && !applyResult) {
if (planResult.status !== "done") {
appendRoomDesignProgress(req.session_id, { summary: "Bounded project-precedent discovery failed; no model changes were made.", work_item: { id: "precedent.resolve", title: "Resolve the strongest applicable project precedent", status: "blocked", blocker: planResult.error || planResult.failure_hint || "Analog preview failed.", evidence_refs: [`action:${planResult.action_id}`] } });
appendRoomDesignProgress(req.session_id, { summary: "Bounded project-precedent discovery failed; no model changes were made.", work_item: { id: "precedent.resolve", title: "Resolve the strongest applicable project precedent", status: "blocked", scope: null, depends_on: ["target.inspect"], planned_actions: ["score same-level furnished analogs", "record selected precedent and assumptions"], blocker: planResult.error || planResult.failure_hint || "Analog preview failed.", evidence_refs: [`action:${planResult.action_id}`] } });
return { version: OPERATOR_BACKEND_CONTRACT_VERSION, assistant_message: failureMessage("preview", planResult), actions: [] };
}
const plan = payloadFor(planResult);
Expand All @@ -139,23 +145,25 @@ export function maybeRunDeterministicRoomReceptacleAnalog(req: ChatRequest, inte
const planHash = typeof plan?.planHash === "string" ? plan.planHash : "";
const ready = plan?.ready === true && plan?.status === "ready" && !!targetRoomNumber && !!sourceRoomNumber && !!planHash;
if (!ready) {
appendRoomDesignProgress(req.session_id, { summary: "The analog preview did not establish a unique hash-bound project precedent.", work_items: [{ id: "precedent.resolve", title: "Resolve the strongest applicable project precedent", status: "blocked", blocker: "No complete unique analog plan was returned.", evidence_refs: [`action:${planResult.action_id}`] }, { id: "layout.preview", title: "Rollback-preview the exact mapped layout", status: "blocked", blocker: "Project precedent remains unresolved.", evidence_refs: [`action:${planResult.action_id}`] }] });
appendRoomDesignProgress(req.session_id, { summary: "The analog preview did not establish a unique hash-bound project precedent.", work_items: [{ id: "precedent.resolve", title: "Resolve the strongest applicable project precedent", status: "blocked", scope: null, depends_on: ["target.inspect"], planned_actions: ["score same-level furnished analogs", "record selected precedent and assumptions"], blocker: "No complete unique analog plan was returned.", evidence_refs: [`action:${planResult.action_id}`] }, { id: "layout.preview", title: "Rollback-preview the exact mapped layout", status: "blocked", scope: null, depends_on: ["precedent.resolve"], planned_actions: ["hash-bound native preview"], blocker: "Project precedent remains unresolved.", evidence_refs: [`action:${planResult.action_id}`] }] });
return {
version: OPERATOR_BACKEND_CONTRACT_VERSION,
assistant_message: "The rollback preview did not produce a complete, hash-bound analog plan, so I stopped without changing the model.",
actions: []
};
}
const groundedScope = { target_room_number: targetRoomNumber, source_room_number: sourceRoomNumber };
appendRoomDesignProgress(req.session_id, {
summary: `Selected Room ${sourceRoomNumber} as the unique current-project precedent and verified a hash-bound rollback preview for Room ${targetRoomNumber}.`,
assumptions: [{ id: "precedent.room", statement: `Room ${sourceRoomNumber} is the selected current-project analog for target Room ${targetRoomNumber}.`, status: "accepted", basis: "native same-level geometry, anchor, adjacency, and exact device-inventory scoring", evidence_refs: [`action:${planResult.action_id}`] }],
work_items: [
{ id: "target.inspect", title: `Inspect Room ${targetRoomNumber} geometry and existing receptacles`, status: "complete", evidence_refs: [`action:${planResult.action_id}`], result_summary: "Target room and anchor geometry were resolved natively." },
{ id: "precedent.resolve", title: "Resolve the strongest applicable project precedent", status: "complete", evidence_refs: [`action:${planResult.action_id}`], result_summary: `Unique project analog Room ${sourceRoomNumber} selected.` },
{ id: "layout.preview", title: "Rollback-preview the exact mapped layout", status: "complete", evidence_refs: [`action:${planResult.action_id}`], result_summary: `Hash-bound plan ${planHash} verified with no persistent model change.` },
{ id: "layout.apply", title: "Apply the verified hash-bound layout", status: "ready", depends_on: ["layout.preview"], planned_actions: ["atomic apply", "exact created-id receipt"] }
{ id: "target.inspect", title: `Inspect Room ${targetRoomNumber} geometry and existing receptacles`, status: "complete", scope: { room_number: targetRoomNumber }, planned_actions: ["native room and anchor inventory"], evidence_refs: [`action:${planResult.action_id}`], result_summary: "Target room and anchor geometry were resolved natively." },
{ id: "precedent.resolve", title: "Resolve the strongest applicable project precedent", status: "complete", scope: groundedScope, depends_on: ["target.inspect"], planned_actions: ["score same-level furnished analogs", "record selected precedent and assumptions"], evidence_refs: [`action:${planResult.action_id}`], result_summary: `Unique project analog Room ${sourceRoomNumber} selected.` },
{ id: "layout.preview", title: "Rollback-preview the exact mapped layout", status: "complete", scope: groundedScope, depends_on: ["precedent.resolve"], planned_actions: ["hash-bound native preview"], evidence_refs: [`action:${planResult.action_id}`], result_summary: `Hash-bound plan ${planHash} verified with no persistent model change.` },
{ id: "layout.apply", title: "Apply the verified hash-bound layout", status: "ready", scope: groundedScope, depends_on: ["layout.preview"], planned_actions: ["atomic apply", "exact created-id receipt"] }
]
});
updateRoomDesignPhase(req.session_id, "layout_execution", `Apply the verified Room ${sourceRoomNumber} analog in Room ${targetRoomNumber}`);
return {
version: OPERATOR_BACKEND_CONTRACT_VERSION,
assistant_message: `The rollback preview verified the Room ${targetRoomNumber} layout against analog Room ${sourceRoomNumber}. Applying the same hash-bound plan…`,
Expand All @@ -170,7 +178,7 @@ export function maybeRunDeterministicRoomReceptacleAnalog(req: ChatRequest, inte

if (applyResult) {
if (applyResult.status !== "done") {
appendRoomDesignProgress(req.session_id, { summary: "The atomic apply failed and no broad or conversational recovery was attempted.", work_item: { id: "layout.apply", title: "Apply the verified hash-bound layout", status: "failed", blocker: applyResult.error || applyResult.failure_hint || "Apply failed.", evidence_refs: [`action:${applyResult.action_id}`] } });
appendRoomDesignProgress(req.session_id, { summary: "The atomic apply failed and no broad or conversational recovery was attempted.", work_item: { id: "layout.apply", title: "Apply the verified hash-bound layout", status: "failed", scope: null, depends_on: ["layout.preview"], planned_actions: ["atomic apply", "exact created-id receipt"], blocker: applyResult.error || applyResult.failure_hint || "Apply failed.", evidence_refs: [`action:${applyResult.action_id}`] } });
return { version: OPERATOR_BACKEND_CONTRACT_VERSION, assistant_message: failureMessage("apply", applyResult), actions: [] };
}
const applied = payloadFor(applyResult);
Expand All @@ -181,7 +189,7 @@ export function maybeRunDeterministicRoomReceptacleAnalog(req: ChatRequest, inte
const typeCounts = receipt?.typeCounts ?? [];
const warnings = Array.isArray(applied?.warnings) ? applied.warnings.filter(value => typeof value === "string") as string[] : [];
if (!receipt) {
appendRoomDesignProgress(req.session_id, { summary: "Apply returned without complete exact persistent readback, so completion is not claimed.", work_items: [{ id: "layout.apply", title: "Apply the verified hash-bound layout", status: "blocked", blocker: "Created IDs or persistent readback were incomplete.", evidence_refs: [`action:${applyResult.action_id}`] }, { id: "layout.verify", title: "Verify device type, room, host, position, and orientation", status: "blocked", blocker: "Persistent readback is incomplete.", evidence_refs: [`action:${applyResult.action_id}`] }] });
appendRoomDesignProgress(req.session_id, { summary: "Apply returned without complete exact persistent readback, so completion is not claimed.", work_items: [{ id: "layout.apply", title: "Apply the verified hash-bound layout", status: "blocked", scope: null, depends_on: ["layout.preview"], planned_actions: ["atomic apply", "exact created-id receipt"], blocker: "Created IDs or persistent readback were incomplete.", evidence_refs: [`action:${applyResult.action_id}`] }, { id: "layout.verify", title: "Verify device type, room, host, position, and orientation", status: "blocked", scope: null, depends_on: ["layout.apply"], planned_actions: ["native persistent readback"], blocker: "Persistent readback is incomplete.", evidence_refs: [`action:${applyResult.action_id}`] }] });
return {
version: OPERATOR_BACKEND_CONTRACT_VERSION,
assistant_message: "The apply call did not return a complete persistent inventory/readback receipt, so I cannot claim the layout is complete.",
Expand All @@ -201,11 +209,12 @@ export function maybeRunDeterministicRoomReceptacleAnalog(req: ChatRequest, inte
appendRoomDesignProgress(req.session_id, {
summary: `Atomic Room ${targetRoomNumber} apply and native persistent readback passed for ${createdIds.length} receptacle(s) using Room ${sourceRoomNumber}.`,
work_items: [
{ id: "layout.apply", title: "Apply the verified hash-bound layout", status: "complete", evidence_refs: [`action:${applyResult.action_id}`], result_summary: `${createdIds.length} unique receptacle IDs created atomically.` },
{ id: "layout.verify", title: "Verify device type, room, host, position, and orientation", status: "complete", evidence_refs: [`action:${applyResult.action_id}`], result_summary: "Exact ID, type, room, physical host, point, and orientation readback passed for every created device." },
{ id: "verify.visual", title: `Perform focused visual QA in Room ${targetRoomNumber}`, status: "ready", depends_on: ["layout.verify"], evidence_refs: [`action:${applyResult.action_id}`], result_summary: previewNote ? "Native readback passed; focused visual follow-up remains required by the preview warning." : "Native readback passed; focused human-style Revit inspection is queued." }
{ id: "layout.apply", title: "Apply the verified hash-bound layout", status: "complete", scope: { target_room_number: targetRoomNumber, source_room_number: sourceRoomNumber }, depends_on: ["layout.preview"], planned_actions: ["atomic apply", "exact created-id receipt"], evidence_refs: [`action:${applyResult.action_id}`], result_summary: `${createdIds.length} unique receptacle IDs created atomically.` },
{ id: "layout.verify", title: "Verify device type, room, host, position, and orientation", status: "complete", scope: { target_room_number: targetRoomNumber, source_room_number: sourceRoomNumber }, depends_on: ["layout.apply"], planned_actions: ["native persistent readback"], evidence_refs: [`action:${applyResult.action_id}`], result_summary: "Exact ID, type, room, physical host, point, and orientation readback passed for every created device." },
{ id: "verify.visual", title: `Perform focused visual QA in Room ${targetRoomNumber}`, status: "ready", scope: { room_number: targetRoomNumber }, depends_on: ["layout.verify"], planned_actions: ["focused Revit inspection", "bounded repair if needed"], evidence_refs: [`action:${applyResult.action_id}`], result_summary: previewNote ? "Native readback passed; focused visual follow-up remains required by the preview warning." : "Native readback passed; focused human-style Revit inspection is queued." }
]
});
updateRoomDesignPhase(req.session_id, "visual_verification", `Perform focused visual QA in Room ${targetRoomNumber}`);
return {
version: OPERATOR_BACKEND_CONTRACT_VERSION,
assistant_message: `Room ${targetRoomNumber} is complete. I placed and natively verified ${createdIds.length} receptacles using Room ${sourceRoomNumber} as the project-standard analog${types ? ` (${types})` : ""}. The model write was atomic and the returned host, position, orientation, type, and room readback all passed.${previewNote}`,
Expand Down
8 changes: 8 additions & 0 deletions apps/operator-backend/test/room_receptacle_analog.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,9 @@ test("room design persists target, selected precedent, exact apply evidence, and
assert.equal(previewed?.assumptions.find(item => item.id === "precedent.room")?.statement, "Room 409 is the selected current-project analog for target Room 407.");
assert.equal(previewed?.work_items.find(item => item.id === "layout.preview")?.status, "complete");
assert.equal(previewed?.work_items.find(item => item.id === "layout.apply")?.status, "ready");
assert.deepEqual(previewed?.work_items.find(item => item.id === "layout.apply")?.scope, { target_room_number: "407", source_room_number: "409" });
assert.deepEqual(previewed?.work_items.find(item => item.id === "layout.apply")?.depends_on, ["layout.preview"]);
assert.equal(previewed?.current_phase, "layout_execution");

const receipt = appliedReceipt([1700407], [{ familyType: "Duplex Receptacle|Standard", count: 1 }]);
receipt.target.number = "407";
Expand All @@ -129,6 +132,11 @@ test("room design persists target, selected precedent, exact apply evidence, and
assert.equal(completed?.work_items.find(item => item.id === "layout.apply")?.status, "complete");
assert.equal(completed?.work_items.find(item => item.id === "layout.verify")?.status, "complete");
assert.equal(completed?.work_items.find(item => item.id === "verify.visual")?.status, "ready");
assert.deepEqual(completed?.work_items.find(item => item.id === "layout.verify")?.scope, { target_room_number: "407", source_room_number: "409" });
assert.deepEqual(completed?.work_items.find(item => item.id === "verify.visual")?.scope, { room_number: "407" });
assert.deepEqual(completed?.work_items.find(item => item.id === "verify.visual")?.planned_actions, ["focused Revit inspection", "bounded repair if needed"]);
assert.equal(completed?.current_phase, "visual_verification");
assert.equal(completed?.current_step, "Perform focused visual QA in Room 407");
assert.match(completed?.progress_summary ?? "", /Room 407 apply and native persistent readback passed/);
});

Expand Down
Loading