Skip to content

Add a Smart Building sensor-deployment example language - #47

Closed
haphantran wants to merge 4 commits into
mainfrom
feature/smart-building-example
Closed

haphantran wants to merge 4 commits into
mainfrom
feature/smart-building-example

Conversation

@haphantran

@haphantran haphantran commented Oct 12, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

Depends on #45 (spatial rules); this branch contains its commit. Review the commits after "Add declarative spatial rules and yaw-aware spatial geometry". The floor plan looks best with #46 merged too (one-line device names and no link labels).

A third example language shows the spatial layer outside robotics and exercises elevation policies and containment rules.

  • Metamodel Smart Building: Building contains Room and Corridor zones (abstract Zone) and SmokeDetector, MotionSensor, Camera, Gateway devices (abstract Device, with deviceId, coverage and radio attributes). Devices reference the zone they are installed in and their gateway.
  • Spatial rules: rooms do not overlap (plan); every device lies within a zone (plan); smoke detectors at least 300 mm inside a zone (plan); every device within 15,000 mm of a gateway (volume).
  • Model: six rooms, one corridor, 25 devices at class-specific mounting heights; validates with zero issues.
  • Viewpoint Building Operations: Floor Plan diagram (translucent zones, devices, dashed radio links to gateways), Device Placement 3D diagram (zones grounded floor slabs, devices adjustable: smoke 2,700 mm, motion 2,200, camera 2,500, gateway 2,400; range 0 to 4,000 mm), and a Device Inventory table.
  • Generator Smart Building Commissioning: writes device-commissioning.json (device ID, type, room, gateway, x/y/z in metres, yaw). Also shipped in examples/codegen-projects/.
  • Packaging: bundled with the other examples; the project dialog gains "Include Smart Building starter" and "Include UML Activity starter" switches. Every session is project-scoped now and new projects start empty, so the activity example was otherwise unreachable.
  • Backend generation engine: exposes resolvedReferences target summaries (id, name, className) like the browser generator, so templates naming referenced elements render the same in pipeline runs (this also affects the existing Visual Components templates that use resolvedReferences).

Verification

  • Frontend: typecheck, 398 tests pass. Backend: typecheck, lint, 697 tests pass (the intermittent route-test failures described in Add declarative spatial rules and yaw-aware spatial geometry #45 also occur on main).
  • Tests cover the rules and model, a detector moved onto a wall (two rule errors), devices left out of range when a gateway is removed, the elevation policies, generation in both the browser and backend engines (every device's z equals its modeled elevation in metres), the remapped starter bundle, and the starter imports.
  • Live: the starter imported into a new project; the floor plan validated with 0 issues; a headless pipeline (validate + generate) succeeded with 25 devices; after moving SD-104 onto a wall, the pipeline failed with the two expected rule errors and the detector showed an error marker.

Spatial geometry now honours rotationZ. A shared oriented-box module
(shared/spatial-geometry.ts, mirrored in frontend/src/services/spatial)
builds each placed element's rotated footprint extruded over its
vertical interval. The JS/OCL spatial helpers keep their names and
signatures but use separating-axis overlap tests and exact convex-polygon
clearance, so rotated elements no longer get axis-aligned answers.

Metamodels gain spatialRules: noOverlap, minClearance, maxDistance,
within, and adjacentTo over placed instances of a source and a target
metaclass (subclasses included), in plan or volume mode. A pure
evaluator (shared/spatial-rules.ts plus the frontend mirror) runs in
browser validation, which marks both elements of an offending pair in
2D and 3D, and in the backend validation engine used by the headless
pipeline. A parity test keeps the two copies identical and checks that
they report the same issues.

Rules are persisted in a new metamodels.spatialRules column, validated
on create and update, carried through checkpoints and metamodel
evolution (a rule on a removed class blocks the evolution), removed with
a deleted class, and exported to Ecore as the EPackage annotation
spatialdsl/spatialRules. The metamodel editor gains a Spatial Rules
section with an add/edit dialog. The Smart Warehouse example declares a
FixedEquipment supertype and four rules that its bundled model
satisfies, and its unbounded controller references now use '*'.
A third example shows the spatial layer outside robotics. The Smart
Building metamodel has a Building with Room and Corridor zones and
SmokeDetector, MotionSensor, Camera, and Gateway devices that reference
their zone and gateway. Its spatial rules keep rooms from overlapping,
keep every device inside a zone (smoke detectors 300 mm from the
boundary), and keep every device within 15 m of a gateway in 3D. The
bundled model (six rooms, a corridor, 25 devices) satisfies them.

The Building Operations viewpoint has a floor-plan diagram with radio
links to the gateways, a 3D diagram in which zones are grounded floor
slabs and devices have adjustable mounting heights per class, and a
device table. The Smart Building Commissioning generator writes
device-commissioning.json with each device's ID, type, room, gateway,
position in metres, and yaw. The example is bundled with the others and
can be imported into a new project with an "Include Smart Building
starter" switch.

The headless generation engine now exposes resolvedReferences target
summaries (id, name, class) like the browser generator, so templates
that name referenced elements render the same in pipeline runs.
Every session is now project scoped and new projects start empty, so the
bundled UML Activity example could no longer be opened. The project
dialog gains an "Include UML Activity starter" switch that imports its
metamodel, model, viewpoint, and both saved views, using the same
starter import as the other examples.
Copilot AI balanced review requested due to automatic review settings October 12, 2026 02:21

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Several persistence paths accept invalid or dangling rules, and the commissioning template can generate malformed JSON.

5 open findings
What changed in this PR

Adds declarative spatial validation and a Smart Building example language, integrating both across browser, backend, persistence, generation, interoperability, and documentation.

Changes:

  • Adds rotation-aware spatial geometry and five declarative spatial-rule kinds.
  • Adds the Smart Building starter, views, commissioning generator, and project options.
  • Extends backend persistence, pipelines, Ecore/Sirius export, checkpoints, and tests.
File Description
shared/​types/​index.ts Adds shared spatial-rule and issue types.
shared/​spatial-rules.ts Implements shared rule validation and evaluation.
shared/​spatial-geometry.ts Implements oriented spatial geometry.
README.md Advertises spatial rules.
frontend/​src/​services/​spatial/​spatial-rules.ts Mirrors rule evaluation in the frontend.
frontend/​src/​services/​spatial/​spatial-geometry.ts Mirrors geometry in the frontend.
frontend/​src/​services/​spatial/​index.ts Exports spatial modules.
frontend/​src/​services/​smart-warehouse-project-import.service.ts Generalizes starter imports.
frontend/​src/​services/​model/​model-validation.service.ts Runs spatial rules during validation.
frontend/​src/​services/​metamodel/​metamodel.service.ts Validates imported rule definitions.
frontend/​src/​services/​metamodel/​metamodel-api-sync.service.ts Synchronizes rules to the API.
frontend/​src/​services/​metamodel/​metaclass-crud.service.ts Removes rules referencing deleted classes.
frontend/​src/​services/​metamodel/​exampleData.service.ts Bundles Smart Building and Activity starters.
frontend/​src/​services/​metamodel/​ecore.service.ts Imports and exports rule annotations.
frontend/​src/​services/​diagram/​view-validation.service.ts Marks all elements involved in issues.
frontend/​src/​services/​constraint/​spatial.helpers.ts Makes constraint geometry rotation-aware.
frontend/​src/​models/​types.ts Adds frontend spatial-rule types.
frontend/​src/​examples/​data/​smart-warehouse-metamodel.json Adds warehouse spatial rules and superclass.
frontend/​src/​examples/​data/​smart-building-views.json Defines Smart Building saved views.
frontend/​src/​examples/​data/​smart-building-viewpoints.json Defines building representations and notation.
frontend/​src/​examples/​data/​smart-building-project.json Defines the commissioning generator.
frontend/​src/​examples/​data/​smart-building-metamodel.json Defines the Smart Building language.
frontend/​src/​components/​projects/​ProjectPickerPage.tsx Adds starter selection switches.
frontend/​src/​components/​metamodel/​SpatialRulesEditor.tsx Adds spatial-rule authoring UI.
frontend/​src/​components/​metamodel/​components/​sidebar/​MetamodelSidebar.tsx Integrates the rule editor.
frontend/​src/​__tests__/​services/​view-validation.test.ts Tests pair-issue visualization.
frontend/​src/​__tests__/​services/​spatial-rules.test.ts Tests rule semantics and validation.
frontend/​src/​__tests__/​services/​spatial-helpers.test.ts Tests rotation-aware helpers.
frontend/​src/​__tests__/​services/​smart-warehouse-project-import.service.test.ts Tests starter imports.
frontend/​src/​__tests__/​services/​smart-building-example.test.ts Tests the Smart Building example.
frontend/​src/​__tests__/​services/​ecore-xmi-interop.test.ts Tests Ecore rule annotations.
frontend/​src/​__tests__/​services/​codegen-example-retarget.test.ts Tests generator retargeting.
frontend/​src/​__tests__/​components/​SpatialRulesEditor.test.tsx Tests rule editor behavior.
examples/​codegen-projects/​smart-building-commissioning-project.json Ships the commissioning generator.
docs/​user-guide/​viewpoints.md Documents building viewpoints.
docs/​user-guide/​spatial-rules.md Adds the spatial-rules guide.
docs/​user-guide/​metamodels.md Documents metamodel rule support.
docs/​user-guide/​code-generation.md Documents resolved references.
docs/​reference/​data-model.md Documents persisted rules.
docs/​reference/​api.md Documents rule API fields.
docs/​README.md Links the new guide.
backend/​src/​services/​sirius-interop.service.ts Exports rules in Sirius Ecore.
backend/​src/​services/​pipeline/​model-validation.engine.ts Runs rules in pipelines.
backend/​src/​services/​pipeline/​headless-pipeline.service.ts Maps persisted rules.
backend/​src/​services/​pipeline/​code-generation.engine.ts Adds resolved reference summaries.
backend/​src/​services/​metamodel.service.ts Persists and validates rules.
backend/​src/​services/​lifecycle/​project-checkpoint.service.ts Checkpoints spatial rules.
backend/​src/​services/​lifecycle/​metamodel-evolution.service.ts Evolves and fingerprints rules.
backend/​src/​__tests__/​services/​spatial-rules-parity.test.ts Tests frontend/backend parity.
backend/​src/​__tests__/​services/​sirius-interop.service.test.ts Tests Sirius rule export.
backend/​src/​__tests__/​services/​sharing.service.test.ts Updates metamodel fixtures.
backend/​src/​__tests__/​services/​project-checkpoint.service.test.ts Tests rule checkpoint restoration.
backend/​src/​__tests__/​services/​pipeline-engines.test.ts Tests pipeline validation and generation.
backend/​src/​__tests__/​services/​metamodel.service.test.ts Tests rule persistence and validation.
backend/​src/​__tests__/​services/​metamodel-evolution.service.test.ts Tests rule-aware evolution.
backend/​prisma/​schema.prisma Adds the spatial-rules column.
backend/​prisma/​migrations/​20261011000000_add_metamodel_spatial_rules/​migration.sql Migrates the database schema.

🧠 Review effort: Balanced


💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

"id": "b5500000-0000-4000-8000-000000000101",
"name": "Device Commissioning List",
"language": "json",
"templateContent": "{\n \"building\": \"{{#each elementsByClassName.Building}}{{name}}{{/each}}\",\n \"units\": {\"position\": \"m\", \"yaw\": \"deg\"},\n \"devices\": [\n{{resetListComma \"devices\"}}{{#each elementsByClassName.SmokeDetector}}{{commaIfNeeded \"devices\"}}\n {\"deviceId\": \"{{deviceId}}\", \"type\": \"SmokeDetector\", \"room\": \"{{resolvedReferences.installedIn.name}}\", \"gateway\": \"{{resolvedReferences.gateway.name}}\", \"x\": {{meters X}}, \"y\": {{meters Y}}, \"z\": {{meters Z}}, \"yaw\": {{#unless RZ}}0{{else}}{{RZ}}{{/unless}} }{{/each}}\n{{#each elementsByClassName.MotionSensor}}{{commaIfNeeded \"devices\"}}\n {\"deviceId\": \"{{deviceId}}\", \"type\": \"MotionSensor\", \"room\": \"{{resolvedReferences.installedIn.name}}\", \"gateway\": \"{{resolvedReferences.gateway.name}}\", \"x\": {{meters X}}, \"y\": {{meters Y}}, \"z\": {{meters Z}}, \"yaw\": {{#unless RZ}}0{{else}}{{RZ}}{{/unless}} }{{/each}}\n{{#each elementsByClassName.Camera}}{{commaIfNeeded \"devices\"}}\n {\"deviceId\": \"{{deviceId}}\", \"type\": \"Camera\", \"room\": \"{{resolvedReferences.installedIn.name}}\", \"gateway\": \"{{resolvedReferences.gateway.name}}\", \"x\": {{meters X}}, \"y\": {{meters Y}}, \"z\": {{meters Z}}, \"yaw\": {{#unless RZ}}0{{else}}{{RZ}}{{/unless}} }{{/each}}\n{{#each elementsByClassName.Gateway}}{{commaIfNeeded \"devices\"}}\n {\"deviceId\": \"{{deviceId}}\", \"type\": \"Gateway\", \"room\": \"{{resolvedReferences.installedIn.name}}\", \"gateway\": \"{{resolvedReferences.gateway.name}}\", \"x\": {{meters X}}, \"y\": {{meters Y}}, \"z\": {{meters Z}}, \"yaw\": {{#unless RZ}}0{{else}}{{RZ}}{{/unless}} }{{/each}}\n ]\n}\n",
"id": "b5500000-0000-4000-8000-000000000101",
"name": "Device Commissioning List",
"language": "json",
"templateContent": "{\n \"building\": \"{{#each elementsByClassName.Building}}{{name}}{{/each}}\",\n \"units\": {\"position\": \"m\", \"yaw\": \"deg\"},\n \"devices\": [\n{{resetListComma \"devices\"}}{{#each elementsByClassName.SmokeDetector}}{{commaIfNeeded \"devices\"}}\n {\"deviceId\": \"{{deviceId}}\", \"type\": \"SmokeDetector\", \"room\": \"{{resolvedReferences.installedIn.name}}\", \"gateway\": \"{{resolvedReferences.gateway.name}}\", \"x\": {{meters X}}, \"y\": {{meters Y}}, \"z\": {{meters Z}}, \"yaw\": {{#unless RZ}}0{{else}}{{RZ}}{{/unless}} }{{/each}}\n{{#each elementsByClassName.MotionSensor}}{{commaIfNeeded \"devices\"}}\n {\"deviceId\": \"{{deviceId}}\", \"type\": \"MotionSensor\", \"room\": \"{{resolvedReferences.installedIn.name}}\", \"gateway\": \"{{resolvedReferences.gateway.name}}\", \"x\": {{meters X}}, \"y\": {{meters Y}}, \"z\": {{meters Z}}, \"yaw\": {{#unless RZ}}0{{else}}{{RZ}}{{/unless}} }{{/each}}\n{{#each elementsByClassName.Camera}}{{commaIfNeeded \"devices\"}}\n {\"deviceId\": \"{{deviceId}}\", \"type\": \"Camera\", \"room\": \"{{resolvedReferences.installedIn.name}}\", \"gateway\": \"{{resolvedReferences.gateway.name}}\", \"x\": {{meters X}}, \"y\": {{meters Y}}, \"z\": {{meters Z}}, \"yaw\": {{#unless RZ}}0{{else}}{{RZ}}{{/unless}} }{{/each}}\n{{#each elementsByClassName.Gateway}}{{commaIfNeeded \"devices\"}}\n {\"deviceId\": \"{{deviceId}}\", \"type\": \"Gateway\", \"room\": \"{{resolvedReferences.installedIn.name}}\", \"gateway\": \"{{resolvedReferences.gateway.name}}\", \"x\": {{meters X}}, \"y\": {{meters Y}}, \"z\": {{meters Z}}, \"yaw\": {{#unless RZ}}0{{else}}{{RZ}}{{/unless}} }{{/each}}\n ]\n}\n",
Comment on lines +378 to +385
asArray<SpatialRule>(next.spatialRules).forEach(rule => {
[rule?.sourceClassId, rule?.targetClassId].forEach(classId => {
if (typeof classId !== 'string' || nextClassNames.has(classId)) return;
const removedName = currentClassNames.get(classId);
blockers.push(removedName
? `Spatial rule ${rule.name} references removed class ${removedName}; update or delete the rule before applying this evolution`
: `Spatial rule ${rule?.name || rule?.id} references unknown class ${classId}`);
});
Comment on lines +216 to +221
if (data.spatialRules !== undefined) {
const classes = data.classes !== undefined
? data.classes
: (await prisma.metamodel.findUnique({ where: { id }, select: { classes: true } }))?.classes;
this.assertValidSpatialRules(data.spatialRules, classes);
}
Comment on lines +154 to +165
rules.push({
id: uuidv4(),
name,
kind: parsed.kind,
sourceClassId,
targetClassId,
...(parsed.distanceMm !== undefined && { distanceMm: parsed.distanceMm }),
...(parsed.mode !== undefined && { mode: parsed.mode }),
severity: parsed.severity || 'error',
...(parsed.message !== undefined && { message: parsed.message }),
...(parsed.enabled !== undefined && { enabled: parsed.enabled }),
});
@haphantran

Copy link
Copy Markdown
Collaborator Author

Superseded by #50. The updated plan asks for a small Smart Home case study instead of this Smart Building example, so #50 replaces the example language and keeps the starter plumbing, the UML Activity starter, and the backend resolvedReferences change from this PR.

@haphantran haphantran closed this Oct 12, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants