Skip to content

Commit 2726314

Browse files
authored
feat(points): add shared zone point resolution (#75)
Finalize strict point dictionary imports and aliases, move four reviewed shared-zone records to one canonical owner, and preserve existing fault keys and runtime artifacts.
1 parent 7d31182 commit 2726314

13 files changed

Lines changed: 2185 additions & 239 deletions

SCHEMA.md

Lines changed: 87 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -488,18 +488,19 @@ YAML frontmatter followed by Markdown prose. Frontmatter fields:
488488
| `playbooks` | list || Playbook slugs in `playbooks/` |
489489
| `operating_states` | string || Applicable states (`all` or list, prose ok) |
490490
| `preconditions` | string || Host-enforced evaluation gate, prose |
491-
| `points` | list || Canonical point names consumed (see below) |
491+
| `points` | list || Bare point names consumed; resolved in the equipment dictionary (see below) |
492492
| `outputs` | list || `{name, description}` — boundary outputs |
493493
| `params` | map || name → `{default, unit, description, cxf}` |
494494
| `energy_impact` | map || `{affected_subsystem, savings_range, climate_sensitivity, runtime_estimation}` |
495495
| `emissions` | map || `{scope, method}` |
496496
| `verified` | map || `{engine_rev, content_id, date}` — all null until verified |
497497

498498
Conventions:
499-
- **Every entry in `points` is a canonical name from
500-
`points/<equipment>.points.json`, and the CXF boundary input connector for it
501-
has exactly that local name.** This single convention makes point binding
502-
mechanical for every consumer.
499+
- **Every entry in `points` is a bare name resolvable in
500+
`points/<equipment>.points.json` as either a local concrete point or an
501+
explicit v2 alias.** The CXF boundary input connector keeps that exact bare
502+
name. Resolution yields the canonical concrete target; a compatibility alias
503+
is not itself a canonical identity.
503504
- `params.*.cxf` is the parameter's CXF path relative to the root block
504505
(`<instance>.<param>`, e.g. `persist.delayTime`) so hosts can retune deployed
505506
rules via `set_param` without re-authoring. It may be a list of paths when
@@ -614,29 +615,90 @@ Target dialect: the open-control engine's composite subset
614615
one threshold-edge case, and one transient case exercising delay/reset
615616
behavior where the rule has timing state.
616617

617-
## `points/<equip>.points.json` contract (`cxf-library/points/v1`)
618+
## `points/<equip>.points.json` contract (`cxf-library/points/v1` and `v2`)
618619

619620
```json
620621
{
621-
"schema": "cxf-library/points/v1",
622-
"equipment": "ahu",
622+
"schema": "cxf-library/points/v2",
623+
"equipment": "vav",
624+
"namespaces": {
625+
"brick": {
626+
"iri": "https://brickschema.org/schema/Brick#",
627+
"verified_version": "1.4.4"
628+
},
629+
"s223": {
630+
"iri": "http://data.ashrae.org/standard223#",
631+
"verified_version": "1.0.0-ppr.2.1"
632+
},
633+
"quantitykind": {
634+
"iri": "http://qudt.org/vocab/quantitykind/",
635+
"verified_version": "QUDT 3.1.4"
636+
},
637+
"unit": {
638+
"iri": "http://qudt.org/vocab/unit/",
639+
"verified_version": "QUDT 3.1.4"
640+
}
641+
},
642+
"imports": ["points/zone.points.json"],
643+
"aliases": [
644+
{
645+
"name": "zone_temp",
646+
"target": "points/zone.points.json#zone_temp"
647+
}
648+
],
623649
"points": [
624650
{
625-
"name": "htg_vlv_cmd",
626-
"description": "Heating coil valve command (0 = closed, 100 = full open)",
651+
"name": "zone_airflow",
652+
"description": "VAV box measured supply airflow",
627653
"kind": "real",
628-
"unit": "%",
629-
"qudt_unit": "PERCENT",
630-
"brick": null,
631-
"s223": null,
632-
"provisional": true
654+
"unit": "L/s",
655+
"qudt_unit": "L-PER-SEC",
656+
"brick": "Supply_Air_Flow_Sensor",
657+
"s223": {
658+
"pattern": "Sensor observes QuantifiableObservableProperty at the VAV box discharge via hasProperty",
659+
"property_class": "QuantifiableObservableProperty",
660+
"quantitykind": "VolumeFlowRate",
661+
"unit": "L-PER-SEC",
662+
"medium": "Fluid-Air",
663+
"aspects": []
664+
},
665+
"provisional": false,
666+
"notes": "Attach isPointOf the VAV box; hosts convert CFM before binding."
633667
}
634668
]
635669
}
636670
```
637671

638-
- `name` is the library-wide canonical identifier (snake_case; suffixes: none =
639-
measured, `_sp` setpoint, `_cmd` command, `_status` status, `_fbk` feedback).
672+
- A v1 dictionary has exactly the required top-level fields `schema`,
673+
`equipment`, `namespaces`, and `points`. Only `notes` is optional. Existing
674+
v1 dictionaries remain valid and MUST NOT contain `imports` or `aliases`.
675+
- A v2 dictionary has exactly the required top-level fields `schema`,
676+
`equipment`, `namespaces`, `imports`, `aliases`, and `points`. Only `notes`
677+
is optional. Namespace and concrete point records use the same closed shapes
678+
as v1.
679+
- A concrete point's canonical identity is
680+
`points/<family>.points.json#<name>`. `name` is snake_case; suffixes are none
681+
for measured values, `_sp` for setpoints, `_cmd` for commands, `_status` for
682+
status, and `_fbk` for feedback. A bare name is meaningful only in the
683+
context of one family dictionary.
684+
- `imports` is an array of unique root-relative
685+
`points/<lowercase-family>.points.json` paths. Absolute paths, traversal,
686+
URLs, self-imports, missing files, duplicate entries, and import cycles are
687+
invalid.
688+
- `aliases` is an array of closed `{name, target}` objects. `name` MUST be
689+
unique and MUST NOT collide with a local concrete point. `target` MUST be a
690+
fully qualified point reference whose dictionary appears in `imports` and
691+
whose fragment names a concrete record. Alias-to-alias targets are invalid.
692+
- Contextual resolution of `(family dictionary, bare name)` checks one local
693+
concrete record, then one explicitly declared alias. It does not search
694+
other dictionaries. A fully qualified reference resolves its named
695+
dictionary and fragment; a legacy alias fragment normalizes once to its
696+
concrete target. Malformed, missing, colliding, or ambiguous references fail
697+
closed. There is no alias chain, filesystem-first match, or fallback.
698+
- Fault cards keep bare boundary names. Rust fault lint resolves each name in
699+
the card family's dictionary before the existing card/CXF/vector checks.
700+
Book generation uses the canonical resolved target for links and lists
701+
compatibility aliases without copying the target record or anchor.
640702
- **Role points** (documented exception, `points/sys.points.json` only): the
641703
cross-equipment sensor-health rules bind role names (`sensor_value`,
642704
`sensor_value_a/b`, `equip_active`) rather than canonical points, because
@@ -669,11 +731,14 @@ Target dialect: the open-control engine's composite subset
669731
unattested in the standard's reference models); the per-point `notes` field
670732
records the specifics. All s223 entries also await confirmation against the
671733
formal ASHRAE 223 standard text once obtained.
672-
- `tools/lint/point_semantics.py` checks strict `points/v1` JSON and shape,
673-
namespace echoes, command/setpoint direction, actuatable quantity and unit
674-
requirements, and the bounded reviewed VAV/SYS mappings. It reads the local
675-
pin record without network access. It does not verify arbitrary external
676-
term existence, evaluate topology or SHACL, or certify building instances.
734+
- `tools/lint/point_semantics.py` checks the strict mixed v1/v2 corpus,
735+
import/alias resolution, namespace echoes, command/setpoint direction,
736+
actuatable quantity and unit requirements, and the bounded reviewed
737+
VAV/SYS/zone mappings. It reads local files without network access and emits
738+
sorted diagnostics. It does not verify arbitrary external term existence,
739+
evaluate topology or SHACL, certify building instances, or change Engine or
740+
Studio runtime behavior. The Engine executes CXF and does not consume point
741+
dictionaries.
677742

678743
## `clusters/clusters.json` (`cxf-library/clusters/v1`)
679744

points/sys.points.json

Lines changed: 11 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
2-
"schema": "cxf-library/points/v1",
2+
"schema": "cxf-library/points/v2",
33
"equipment": "sys",
44
"namespaces": {
55
"brick": {
@@ -19,7 +19,16 @@
1919
"verified_version": "QUDT 3.1.4"
2020
}
2121
},
22-
"notes": "System-level / cross-equipment point dictionary. The sensor-health rules (SYS-0005/SYS-0009/SYS-0010) bind ROLE POINTS, not canonical equipment points — the documented exception to the canonical-name convention (SCHEMA.md points contract; decided 2026-08-17, internal sensor-health design note §3). The reference's own SYS-0005 card does the same ('varies by application'). The host's instance configuration records which real point each role is bound to; that same record drives the adjudicates NO_EVAL fan-out. Role entries carry brick/s223 null — the semantics belong to whatever the role is bound to. The reference ch.16 waste/schedule rules (SYS-0001..SYS-0004, SYS-0007, SYS-0008) bind CANONICAL cross-equipment points (grounded below); SYS-0006 binds the physical_sensor/virtual_value role pair. Points that also exist in equipment dicts (oat, sf_status, rht_vlv_cmd) are duplicated here with matched groundings because lint resolves a card's points against its own family dictionary.",
22+
"imports": [
23+
"points/zone.points.json"
24+
],
25+
"aliases": [
26+
{
27+
"name": "occ_sensor",
28+
"target": "points/zone.points.json#occ_sensor"
29+
}
30+
],
31+
"notes": "System-level / cross-equipment point dictionary. The sensor-health rules (SYS-0005/SYS-0009/SYS-0010) bind ROLE POINTS, not canonical equipment points — the documented exception to the canonical-name convention (SCHEMA.md points contract; decided 2026-08-17, internal sensor-health design note §3). The reference's own SYS-0005 card does the same ('varies by application'). The host's instance configuration records which real point each role is bound to; that same record drives the adjudicates NO_EVAL fan-out. Role entries carry brick/s223 null — the semantics belong to whatever the role is bound to. The reference ch.16 waste/schedule rules (SYS-0001..SYS-0004, SYS-0007, SYS-0008) bind CANONICAL cross-equipment points (grounded below); SYS-0006 binds the physical_sensor/virtual_value role pair. Points that also exist in equipment dictionaries remain duplicated here when their ownership is contextual (oat, sf_status, rht_vlv_cmd). occ_sensor is a compatibility alias to the canonical zone point.",
2332
"points": [
2433
{
2534
"name": "sensor_value",
@@ -185,23 +194,6 @@
185194
},
186195
"notes": "Brick 1.4.4 has no lighting-specific run status class; On_Off_Status on the lighting equipment/zone is the closest grounding."
187196
},
188-
{
189-
"name": "occ_sensor",
190-
"description": "Occupancy sensor state (TRUE = occupied detected)",
191-
"kind": "bool",
192-
"unit": "bool",
193-
"qudt_unit": null,
194-
"brick": "Occupancy_Sensor",
195-
"s223": {
196-
"pattern": "EnumeratedObservableProperty on the occupancy Sensor via hasProperty",
197-
"property_class": "EnumeratedObservableProperty",
198-
"quantitykind": null,
199-
"unit": null,
200-
"medium": null,
201-
"aspects": [],
202-
"enumerationkind": "Binary-OccupiedUnoccupied"
203-
}
204-
},
205197
{
206198
"name": "ef_status",
207199
"description": "Exhaust fan run status",

points/vav.points.json

Lines changed: 12 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
2-
"schema": "cxf-library/points/v1",
2+
"schema": "cxf-library/points/v2",
33
"equipment": "vav",
44
"namespaces": {
55
"brick": {
@@ -23,66 +23,25 @@
2323
"verified_version": "QUDT 3.1.4"
2424
}
2525
},
26-
"notes": "VAV terminal unit point dictionary. Zone-level points grep-verified against Brick 1.4.4 (equipment anchor: brick:Variable_Air_Volume_Box_With_Reheat); 223P patterns follow the AHU dictionary's property-class conventions but no VAV reference-model instance has been checked, so 223P attachments are pattern guidance. satisfied_zone_fraction is host-derived (see zone_reheat_fraction precedent). Airflow unit is L/s throughout; hosts on CFM convert at binding.",
27-
"points": [
26+
"imports": [
27+
"points/zone.points.json"
28+
],
29+
"aliases": [
2830
{
2931
"name": "zone_temp",
30-
"description": "Zone air temperature",
31-
"kind": "real",
32-
"unit": "°C",
33-
"qudt_unit": "DEG_C",
34-
"brick": "Zone_Air_Temperature_Sensor",
35-
"s223": {
36-
"pattern": "Sensor observes QuantifiableObservableProperty on the Zone (DomainSpace) via hasProperty",
37-
"property_class": "QuantifiableObservableProperty",
38-
"quantitykind": "Temperature",
39-
"unit": "DEG_C",
40-
"medium": "Fluid-Air",
41-
"aspects": []
42-
},
43-
"provisional": false,
44-
"notes": "brick:Zone_Air_Temperature_Sensor grep-verified in Brick 1.4.4. 223P attachment is to the zone DomainSpace rather than the VAV box; no reference-model instance checked for the zone-side pattern, but the property class and quantitykind (Temperature, DEG_C) follow the AHU temperature points."
32+
"target": "points/zone.points.json#zone_temp"
4533
},
4634
{
4735
"name": "zone_temp_sp_htg",
48-
"description": "Zone heating temperature setpoint",
49-
"kind": "real",
50-
"unit": "°C",
51-
"qudt_unit": "DEG_C",
52-
"brick": "Zone_Air_Heating_Temperature_Setpoint",
53-
"s223": {
54-
"pattern": "QuantifiableActuatableProperty (setpoint) on the Zone via hasProperty",
55-
"property_class": "QuantifiableActuatableProperty",
56-
"quantitykind": "Temperature",
57-
"unit": "DEG_C",
58-
"medium": "Fluid-Air",
59-
"aspects": [
60-
"Aspect-Setpoint"
61-
]
62-
},
63-
"provisional": false,
64-
"notes": "brick:Zone_Air_Heating_Temperature_Setpoint grep-verified in Brick 1.4.4. Bind the ACTIVE (occupied-mode) heating setpoint the loop is controlling to, not a schedule default — same active-setpoint convention as the AHU dictionary's sat_sp."
36+
"target": "points/zone.points.json#zone_temp_sp_htg"
6537
},
6638
{
6739
"name": "zone_temp_sp_clg",
68-
"description": "Zone cooling temperature setpoint",
69-
"kind": "real",
70-
"unit": "°C",
71-
"qudt_unit": "DEG_C",
72-
"brick": "Zone_Air_Cooling_Temperature_Setpoint",
73-
"s223": {
74-
"pattern": "QuantifiableActuatableProperty (setpoint) on the Zone via hasProperty",
75-
"property_class": "QuantifiableActuatableProperty",
76-
"quantitykind": "Temperature",
77-
"unit": "DEG_C",
78-
"medium": "Fluid-Air",
79-
"aspects": [
80-
"Aspect-Setpoint"
81-
]
82-
},
83-
"provisional": false,
84-
"notes": "brick:Zone_Air_Cooling_Temperature_Setpoint grep-verified in Brick 1.4.4. Active-setpoint convention as zone_temp_sp_htg."
85-
},
40+
"target": "points/zone.points.json#zone_temp_sp_clg"
41+
}
42+
],
43+
"notes": "VAV terminal unit point dictionary. Zone-level points grep-verified against Brick 1.4.4 (equipment anchor: brick:Variable_Air_Volume_Box_With_Reheat); 223P patterns follow the AHU dictionary's property-class conventions but no VAV reference-model instance has been checked, so 223P attachments are pattern guidance. satisfied_zone_fraction is host-derived (see zone_reheat_fraction precedent). Airflow unit is L/s throughout; hosts on CFM convert at binding.",
44+
"points": [
8645
{
8746
"name": "zone_airflow",
8847
"description": "VAV box measured supply airflow",

points/zone.points.json

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
{
2+
"schema": "cxf-library/points/v2",
3+
"equipment": "zone",
4+
"namespaces": {
5+
"brick": {
6+
"iri": "https://brickschema.org/schema/Brick#",
7+
"verified_version": "1.4.4"
8+
},
9+
"s223": {
10+
"iri": "http://data.ashrae.org/standard223#",
11+
"verified_version": "1.0.0-ppr.2.1"
12+
},
13+
"quantitykind": {
14+
"iri": "http://qudt.org/vocab/quantitykind/",
15+
"verified_version": "QUDT 3.1.4"
16+
},
17+
"unit": {
18+
"iri": "http://qudt.org/vocab/unit/",
19+
"verified_version": "QUDT 3.1.4"
20+
}
21+
},
22+
"imports": [],
23+
"aliases": [],
24+
"notes": "Zone point dictionary. These records describe the occupied space independently of the equipment serving it.",
25+
"points": [
26+
{
27+
"name": "zone_temp",
28+
"description": "Zone air temperature",
29+
"kind": "real",
30+
"unit": "°C",
31+
"qudt_unit": "DEG_C",
32+
"brick": "Zone_Air_Temperature_Sensor",
33+
"s223": {
34+
"pattern": "Sensor observes QuantifiableObservableProperty on the Zone (DomainSpace) via hasProperty",
35+
"property_class": "QuantifiableObservableProperty",
36+
"quantitykind": "Temperature",
37+
"unit": "DEG_C",
38+
"medium": "Fluid-Air",
39+
"aspects": []
40+
},
41+
"provisional": false,
42+
"notes": "brick:Zone_Air_Temperature_Sensor grep-verified in Brick 1.4.4. 223P attachment is to the zone DomainSpace rather than the VAV box; no reference-model instance checked for the zone-side pattern, but the property class and quantitykind (Temperature, DEG_C) follow the AHU temperature points."
43+
},
44+
{
45+
"name": "zone_temp_sp_htg",
46+
"description": "Zone heating temperature setpoint",
47+
"kind": "real",
48+
"unit": "°C",
49+
"qudt_unit": "DEG_C",
50+
"brick": "Zone_Air_Heating_Temperature_Setpoint",
51+
"s223": {
52+
"pattern": "QuantifiableActuatableProperty (setpoint) on the Zone via hasProperty",
53+
"property_class": "QuantifiableActuatableProperty",
54+
"quantitykind": "Temperature",
55+
"unit": "DEG_C",
56+
"medium": "Fluid-Air",
57+
"aspects": [
58+
"Aspect-Setpoint"
59+
]
60+
},
61+
"provisional": false,
62+
"notes": "brick:Zone_Air_Heating_Temperature_Setpoint grep-verified in Brick 1.4.4. Bind the ACTIVE (occupied-mode) heating setpoint the loop is controlling to, not a schedule default — same active-setpoint convention as the AHU dictionary's sat_sp."
63+
},
64+
{
65+
"name": "zone_temp_sp_clg",
66+
"description": "Zone cooling temperature setpoint",
67+
"kind": "real",
68+
"unit": "°C",
69+
"qudt_unit": "DEG_C",
70+
"brick": "Zone_Air_Cooling_Temperature_Setpoint",
71+
"s223": {
72+
"pattern": "QuantifiableActuatableProperty (setpoint) on the Zone via hasProperty",
73+
"property_class": "QuantifiableActuatableProperty",
74+
"quantitykind": "Temperature",
75+
"unit": "DEG_C",
76+
"medium": "Fluid-Air",
77+
"aspects": [
78+
"Aspect-Setpoint"
79+
]
80+
},
81+
"provisional": false,
82+
"notes": "brick:Zone_Air_Cooling_Temperature_Setpoint grep-verified in Brick 1.4.4. Active-setpoint convention as zone_temp_sp_htg."
83+
},
84+
{
85+
"name": "occ_sensor",
86+
"description": "Occupancy sensor state (TRUE = occupied detected)",
87+
"kind": "bool",
88+
"unit": "bool",
89+
"qudt_unit": null,
90+
"brick": "Occupancy_Sensor",
91+
"s223": {
92+
"pattern": "EnumeratedObservableProperty on the occupancy Sensor via hasProperty",
93+
"property_class": "EnumeratedObservableProperty",
94+
"quantitykind": null,
95+
"unit": null,
96+
"medium": null,
97+
"aspects": [],
98+
"enumerationkind": "Binary-OccupiedUnoccupied"
99+
}
100+
}
101+
]
102+
}

0 commit comments

Comments
 (0)