Repository navigation
release: promote 2026-Q3 to master - #45
Merged
Merged
Conversation
…llier (#7) * feat: add SAM.Analytical.Systems.Mollier bridge (Mollier -> SystemEnergyCentre) Introduce a new netstandard2.0 assembly that converts a chain of psychrometric Mollier processes into a connected, simulation-ready SAM_Systems air-handling system, closing the loop from psychrometric design to detailed simulation. - Create.SystemComponent: per-process -> ISystemComponent mapping (cooling/heating coil, fan, exchanger, humidifier, air junction), deriving Setpoint, Duty and cooling BypassFactor from process end-states and a design airflow. - Create.SystemPlantRoom / Create.SystemEnergyCentre: order the process chain and wire components sequentially onto one AirSystem via SystemPlantRoom.Connect. - Query.Duty / Query.MassFlow / Query.BypassFactor / Query.SystemComponentType: reusable psychrometric -> systems helpers. - Twin-wheel (latent) recovery flagged when a HeatRecoveryProcess shifts humidity. - Cross-repo coupling without a circular reference: SAM.*.Mollier consumed as built DLLs from the sibling SAM_Mollier repo (build SAM + SAM_Mollier first). - Register project in SAM_Systems.sln; add docs/Mollier-Systems-Bridge.md. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DBPdu7fogKggmF47N76sXM * feat: add Grasshopper node and AirHandlingUnitResult overload for Mollier bridge - SAMSystemsCreateEnergyCentreByMollier (SAM.Analytical.Grasshopper.Systems): GH component taking a list of Mollier processes, a design airflow and a name, outputting a SystemEnergyCentre via the bridge. Reuses GooMollierProcessParam. - Grasshopper.Systems csproj: reference the bridge project plus SAM.Core.Mollier and SAM.Geometry.Grasshopper.Mollier from the sibling SAM_Mollier build. - Create.SystemEnergyCentre overloads that source the design airflow from AirHandlingUnitResult.SupplyAirFlow (the "pull from model" path). - Qualify internal Create.* calls and refresh docs. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DBPdu7fogKggmF47N76sXM * feat: twin-wheel supply+extract support for Mollier bridge Add Create.SystemPlantRoom/SystemEnergyCentre overloads that take a supply and an extract Mollier process chain and share a single SystemExchanger across both air paths. Connect auto-selects the first unconnected connector pair, so the supply chain takes air path 1 and the extract chain takes air path 2 of the same device, modelling latent + sensible recovery (twin-wheel) as one exchanger. Heat-recovery devices are reused across chains paired in order. The Grasshopper node gains optional _extractMollierProcesses_ / _extractAirflow_ inputs and routes to the twin-wheel overload when an extract chain is supplied. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DBPdu7fogKggmF47N76sXM * docs: add twin-wheel worked example for the Mollier bridge Add an end-to-end example to docs/Mollier-Systems-Bridge.md showing how to build a twin-wheel AHU as supply/extract Mollier process chains (verified factory signatures) and auto-generate the connected SystemEnergyCentre via the bridge, including the JSON round-trip hand-off point for Tas. Supports the CESBP-2025 validation case. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DBPdu7fogKggmF47N76sXM * feat: add two Mollier GH nodes and comprehensive component/input descriptions - SAMSystems.CreatePlantRoomByMollier: Mollier processes -> SystemPlantRoom (supply + optional twin-wheel extract), for merging into an existing energy centre. - SAMSystems.CreateComponentByMollierProcess: one process -> SystemComponent plus derived duty [W] and cooling bypass factor, the per-process building block. - Rewrite all Grasshopper component, input and output descriptions to be comprehensive: units, intent, process->component mapping table and twin-wheel behaviour now appear in every tooltip. - Update docs with a component reference table. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DBPdu7fogKggmF47N76sXM * test: add twin heat-recovery worked example with self-check - TwinWheelExample (bridge assembly): builds the supply/extract Mollier process chains for a twin-wheel AHU, converts them to a SystemEnergyCentre via the factory, and provides a framework-free Verify() running deterministic checks (component creation, single plant room, JSON round-trip, cooling duty and bypass factor). - SAMSystems.MollierTwinWheelExample GH node: runs the example and outputs the energy centre, a PASS/FAIL report and an overall success flag. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DBPdu7fogKggmF47N76sXM * docs(gh): comprehensive descriptions for system query/add components (1/4) Rewrite component summaries and all input/output tooltips for AddSystemEnergyCentre, ConnectedSensor, ConnectedSystemComponents, ComponentType, ConnectedSystemGroups and ConnectedSystemObjects. Also fix two copy-pasted summaries (ConnectedSystemGroups and ConnectedSystemObjects previously read "Gets Connected System Components"). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DBPdu7fogKggmF47N76sXM * docs(gh): comprehensive descriptions for connector/display components (2/4) Rewrite summaries and all input/output tooltips for SystemConnector, CreateDisplaySystemConnectorManager, CreateDisplaySystemManager and CreateSystemGeometrySymbol. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DBPdu7fogKggmF47N76sXM * docs(gh): comprehensive descriptions for query/display components (3/4) Rewrite summaries and I/O tooltips for OrderedSystemComponents, PlantRoomRelatedObjects and SystemToAHU. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DBPdu7fogKggmF47N76sXM * docs(gh): comprehensive descriptions for remaining components (4/4) Rewrite summaries and I/O tooltips for ModifyAirSystem, UpdateVentilationSystem, MergeEnergyCentreByECs, ResultValueByHourOfYear, AnalyticalSystemType, and polish CreateSystemEnergyCentre. Fill the empty summary on CreateSystemEnergyCentreByMechanicalSystems and fix the broken MergeEnergyCentreByECs summary. Completes the repo-wide Grasshopper description pass. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DBPdu7fogKggmF47N76sXM * refactor: single-chain plant-room builder reuses shared AddChain helper The single-chain Create.SystemPlantRoom now delegates to the AddChain helper used by the supply/extract (twin-wheel) builder instead of duplicating the map-and-wire loop. Behaviour is unchanged; the chain-walking logic now lives in one place. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DBPdu7fogKggmF47N76sXM * feat: derive heat-recovery effectiveness for twin-wheel exchangers Compute each shared SystemExchanger's sensible and latent effectiveness from both air paths (supply-side definition, fractions 0-1) and write them to the exchanger, with ExchangerCalculationMethod/ExchangerType set to Simple so the values are used. - Query.HeatRecoveryEfficiencies(supplyProcess, extractProcess, out sensible, out latent). - Create.SystemPlantRoom(supply, extract, ...) pairs supply/extract heat-recovery processes in order and applies the efficiencies to the shared exchangers. - Single-chain mapping still flags latent capability but leaves efficiencies unset (one process cannot determine effectiveness). - Extend TwinWheelExample.Verify with effectiveness checks; update docs. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DBPdu7fogKggmF47N76sXM * feat: set cooling-coil MinimumOffcoil to the Apparatus Dew Point Air cannot leave a cooling coil colder than the coil surface (~the ADP), so set SystemCoolingCoil.MinimumOffcoil to the ADP dry-bulb temperature derived from the cooling process. Update the mapping table in the docs. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DBPdu7fogKggmF47N76sXM * docs: add HANDOVER.md for session continuity Capture the Abstract 2 Mollier->HVAC bridge state: scope, branches/PRs, implemented files, verified API facts, deliberate omissions and next steps, so work can resume in a new session. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DBPdu7fogKggmF47N76sXM * ci: build SAM_Mollier dependency + add SPDX headers; qualify bridge Create - build.yml: add SAM_Mollier to the sibling clone/build order so the SAM.Analytical.Systems.Mollier project can resolve the SAM.*.Mollier HintPath references (fixes CS0234/CS0246 in CI). - Add the required SPDX/copyright header to 10 changed Grasshopper component files flagged by the SPDX check. - TwinWheelExample: fully-qualify Create.SystemEnergyCentre so the name binds to the bridge Create type rather than the enclosing Create method. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HwH8BymskFGrsoKZz7k5ab * fix: use compiled HeatRecoveryProcess_Supply/_Extract in TwinWheelExample The example called MollierPoint.HeatRecoveryProcess(...), but that overload lives in Create\HeatRecoveryProcess.cs which SAM.Core.Mollier.csproj excludes via <Compile Remove>. The compiled API is HeatRecoveryProcess_Supply / HeatRecoveryProcess_Extract. Switch the supply/extract chains to those, and pass the efficiencies as percentages (75/65) since the methods divide by 100 (0.75 previously meant 0.75% recovery). Fixes CS1061 in the CI build. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HwH8BymskFGrsoKZz7k5ab * build: reference SAM.Analytical.Mollier from Grasshopper.Systems project SAMSystemsCreateEnergyCentreByMollier uses AirHandlingUnitResult, whose type lives in SAM.Analytical.Mollier. The Grasshopper project referenced SAM.Core.Mollier and SAM.Geometry.Grasshopper.Mollier but not SAM.Analytical.Mollier, causing CS0012. Add the missing assembly reference (HintPath into the sibling SAM_Mollier build output). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HwH8BymskFGrsoKZz7k5ab * fix: persist resolved connector indexes in CreateSystemConnection SystemPlantRoom.CreateSystemConnection resolved the In/Out connector indexes via TryGetIndexes (index_1_out/index_2_out) but then constructed the SystemConnection with the raw index_1/index_2 arguments. When a caller passes -1 (auto-select), the connection was stored with -1, so the connectors never read as occupied and subsequent links reused the same connector, mis-wiring the generated topology. Build the connection with the resolved indexes instead; explicit-index callers are unaffected (TryGetIndexes returns those unchanged). Addresses the Codex P1 on the Mollier bridge twin-wheel wiring. Also refresh docs/HANDOVER.md with current PR/CI status and the fixes. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017hXuHWUCWPeNTmo27SzoEk * fix: persist twin-wheel HR efficiencies and relate single-component chains Addresses the two open Codex findings on the Mollier->HVAC bridge. P1 (SupplyExtract.cs): SystemPlantRoom.Add clones the component, so the exchanger list held the pre-Add original while the plant room stored a clone; ApplyHeatRecoveryEfficiencies mutated the detached original, so the stored and serialised exchanger had no sensible/latent effectiveness and twin-wheel recovery was silently dropped. It now looks the exchanger up by Guid in the plant room, sets the efficiencies on that stored instance and re-adds it (re-adding under the same Guid replaces in place, relations intact). Wiring was already correct (CreateSystemConnection resolves connectors from the live cluster by Guid). P2 (SupplyExtract.cs): a single-process chain never reached the pairwise Connect, so its lone component was never related to its AirSystem and GetSystemComponents<T>(ISystem) saw an empty plant room. AddChain now relates the first component to its air system when there is no previous component. TwinWheelExample.Verify strengthened to guard both: it reads the sensible/latent effectiveness back from the JSON-round-tripped stored exchanger and asserts a single-component plant room relates its component to its air system. Verified locally - all 14 checks pass. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix: wire Mollier chains on Out->In connectors (Codex P1) After the connector-index root fix, SystemPlantRoom.Connect with index -1 auto-selects each component's first unconnected connector. Air components define their In connector before Out, so an auto-wired step (e.g. heat recovery -> cooling) was stored as previous.In -> current.Out, leaving the upstream Out connector free; GetOrderedSystemComponents(..., Direction.Out) and the export/conversion paths could not follow the generated chain. AddChain now resolves explicit connectors: the first UNCONNECTED Out on the upstream component and the first UNCONNECTED In on the downstream one, then passes them to Connect. Picking the first unconnected (not just the first) connector also routes the shared twin-wheel exchanger's second air path onto its second connector pair instead of colliding with the first. Falls back to auto-selection if a directional connector cannot be resolved. TwinWheelExample.Verify gains an Out-direction reachability check (the supply head's Out-walk must reach its 3 downstream components; the In<->Out bug caps it at 0). Verified locally - all 15 checks pass. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Claude <noreply@anthropic.com>
…able Bridge merged to sow/2026-Q3 (Codex findings fixed + verified, build order fixed across SAM_Deploy and the 5 downstream CIs). Update HANDOVER so all tasks read done except the post-merge sow build confirmation, and record the real Abstract 2 deliverable (the SAM_Tas TPD export + CESBP-2025 validation) as the next step on a new branch. Generated by Michal Dengusiak and CodeClaude Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
TwinWheelExample produces a logical SystemEnergyCentre with no display geometry, so it does not preview in the Grasshopper viewport (unlike the laid-out MVRE.json). Document the root cause and a plan to add an auto-layout converter (logical -> DisplaySystemEnergyCentre) backed by the existing SAM_DisplaySystemManager.JSON symbol library. Coverage note: 5 of the 6 bridge-emitted component types have symbols; base SystemHumidifier is the only gap (missing from both the symbol library and Create.DisplayObject<T>). Generated by Michal Dengusiak and CodeClaude Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…symbols Per design direction: SystemHumidifier becomes the abstract base for its three concrete subtypes (SystemDirectEvaporativeCooler, SystemSprayHumidifier, SystemSteamHumidifier), each of which already has a symbol in SAM_DisplaySystemManager.JSON. The bridge maps the Mollier HumidificationProcess subtype to a concrete humidifier (Adiabatic -> Spray, Isothermal/Steam -> Steam), closing the only coverage gap (now 6/6, no resource change needed). Generated by Michal Dengusiak and CodeClaude Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…temHumidifier Implements the schematic-preview plan so a logical SystemEnergyCentre from the Mollier -> SAM_Systems bridge can be drawn/baked in Grasshopper. - Create.DisplaySystemEnergyCentre/DisplaySystemPlantRoom: auto-layout converter (one row per air system, flow order) that converts each component to its display symbol and re-creates each connection; components without a symbol are reported. - DisplaySystemPlantRoom.CreateSystemConnection: routes an orthogonal polyline between the two components' connector world points (geometry-aware index fallback; centroid fallback) instead of throwing NotImplementedException. - SystemHumidifier made abstract; bridge maps adiabatic humidification -> spray and isothermal/steam -> steam humidifier (Create.SystemComponent, Query.SystemComponentType), so every emitted humidifier has a concrete, drawable symbol. - Grasshopper: new SAMSystems.CreateDisplaySystemEnergyCentre node; _display_ toggle plus displaySystemObjects/displaySystemPlantRooms outputs on the twin-wheel example. - TwinWheelExample: CreateDisplay helper + Verify display checks (drawable geometry, connection count preserved). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LsKxVe4Rozn5AL4kPH8fGu
- DisplaySystemPlantRoom / DisplaySystemEnergyCentre: drop `using SAM.Core;` (caused CS0104 'ISystem' ambiguity vs SAM.Core.Systems.ISystem); fully-qualify SAM.Core.Direction and SAM.Core.SAMObject instead. Fixes the build job. - CreateSystemConnection: when the air system is known but no unconnected Out/In connector pair can be resolved, return null instead of fabricating a connection on an invalid/occupied connector. - DisplaySystemEnergyCentre converter: track display membership per (air system, component) pair instead of a global connected set, so a component shared across air systems is related to every system it belongs to. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LsKxVe4Rozn5AL4kPH8fGu
…entre Per review feedback, the Mollier bridge now builds drawable output by default so SAMSystems.MollierTwinWheelExample yields a SystemEnergyCentre that previews in Grasshopper without any extra conversion step. - Create.SystemPlantRoom (single-chain and supply/extract) promotes its logical plant room to a DisplaySystemPlantRoom: every component becomes its DisplaySystem* equivalent (symbol + auto layout) and every connection a routed polyline. This is done at plant-room assembly, where the symbol manager and layout are available (the per-process Create.SystemComponent factory has neither). Falls back to the logical room when no symbol library is present. DisplaySystem* are subclasses of their System* types, so simulation/export is unaffected. - TwinWheelExample.Verify: checks the bridge output is display-native (drawable components + connections) directly, instead of re-converting; skips gracefully when no symbol library is available. Removed the now-redundant CreateDisplay helper. - SAMSystems.MollierTwinWheelExample node: the systemEnergyCentre output is now drawable; it is flattened to displaySystemObjects for direct preview/bake. Dropped the obsolete _display_ toggle. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LsKxVe4Rozn5AL4kPH8fGu
SystemPlantRoom.Connect dereferences the relation cluster (Contains) before it is lazily created, and the cluster is only created by Add. The display converter's first mutation was a Connect call, so a multi-component conversion threw a NullReferenceException. Seed the DisplaySystemPlantRoom with its air systems and converted components (via Add, which creates the cluster) before any Connect. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LsKxVe4Rozn5AL4kPH8fGu
fix object generation if null
Addresses the PR #8 review request to build the Mollier example on a real plant-room template (Plantroom-Only.json) instead of emitting an air-only SystemEnergyCentre, so the result keeps the template's liquid systems, energy sources and plant equipment and is simulation-ready. - Query.SystemEnergyCentre(path) / SystemEnergyCentre_FromResources(fileName): load a template energy centre from a SAM JSON file; bundle Plantroom-Only.json under files/resources/Analytical/Systems/SystemEnergyCentre/. - Create.SystemEnergyCentre(..., SystemEnergyCentre template): build the air side from the Mollier processes and copy each air system (with its components and routed display connections) into a copy of the template's first plant room via the existing SystemPlantRoom.CopyFrom transitive copy. The template object is never mutated; a null template falls back to the previous air-only behaviour. - TwinWheelExample.Create(..., template) overload. - SAMSystems.MollierTwinWheelExample and SAMSystems.CreateEnergyCentreByMollier gain an optional _plantRoomTemplate_ file-path input; the example defaults to the bundled Plantroom-Only.json when no path is given. Deferred (need a Grasshopper viewport to verify and a maintainer design call): layout offset to avoid air/plant overlap, Junction Fresh/Exhaust Air insertion, DisplayAirSystemGroup per case, second heat-recovery offset, and the CreateAirSystemByMollier rename with an AirSystem-typed output. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01D272omUvR5c6173iECqTvy
When seeding from a plant-room template, the bridge laid the Mollier air side out from the origin - the same region where the template's plant equipment sits - so the two schematics overlapped in the viewport (the maintainer's "layout is not correct" note). Before copying the air systems into the template plant room, translate the air-side display objects (components and connections) so their combined extent sits directly above the template's display extent with a clear margin, left edges aligned. Connections move with their components (ISystemConnection is an ISystemComponent) and each moved object is re-added under its existing Guid, so all relations are preserved. Geometry-only: the logical model, JSON round-trip and simulation are unaffected, and the step no-ops when either side carries no display geometry (logical fallback). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01D272omUvR5c6173iECqTvy
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01D272omUvR5c6173iECqTvy
…unctions Addresses the PR #8 request that air paths not be left dangling at outside-air conditions. The bridge now adds explicit air junctions at the two outside-air boundaries of the generated air-handling plant room: - "Junction Fresh Air" feeds the supply intake (the supply system's open In), wired junction.Out -> component.In. - "Junction Exhaust Air" terminates the extract discharge (the extract system's open Out), wired component.Out -> junction.In - added only when an extract chain is present. The room-side connectors (supply -> room, room -> extract) are deliberately left open: those are room-side, not outside-air, boundaries. Connector indexes follow the SystemJunction layout (In=0, Out=1), matching the reference HeatRecovery- Junctions plant room. Junctions are skipped when the boundary component is already a junction. SystemAirJunction has a symbol in the bundled DisplaySystemManager and Create.DisplayObject maps it to DisplaySystemAirJunction, so the junctions draw in the preview. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01D272omUvR5c6173iECqTvy
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01D272omUvR5c6173iECqTvy
Extend the framework-free self-check so the bridge's fresh/exhaust junction capping is covered: the report now verifies that a "Junction Fresh Air" and a "Junction Exhaust Air" exist on the generated plant room and that each is wired into its chain. These are logical-model checks, so they hold even in a headless context where the symbol library is unavailable. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01D272omUvR5c6173iECqTvy
Per maintainer correction on PR #8: all objects on the supply and extract streams form a single AirSystem, not two separate ones. AddChain now wires both chains onto one shared AirSystem ("Air System"). The shared twin-wheel exchanger still consumes air path 1 on the supply chain and air path 2 on the extract chain, via the existing first-unconnected-connector selection. Because the two streams now share an air system, the outside-air junctions can no longer be found by an ambiguous open-connector query (there would be two open Ins and two open Outs). AddChain therefore reports each chain's first and last components, and the fresh/exhaust junctions are placed on the specific boundary components: fresh air on the supply chain's first component (open In), exhaust air on the extract chain's last component (open Out). Room-side boundaries (supply discharge, extract intake) remain open. Note: with one air system the default converter lays all components on one row; the two-row (supply / mirrored extract) coordinate layout and the room-side DisplayAirSystemGroup are the next step. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01D272omUvR5c6173iECqTvy
…ystemGroup Implements the room/group arrangement (PR #8 item 5), grounded in the reference airsystem-withPlantoom.json. The bridge now closes the air loop through the room: supply discharge -> Group Junction -> Damper -> Room (Space) -> Group Junction -> extract intake, all on the single air system. The room Space is created from the Mollier room condition point (dry-bulb temperature and relative humidity become setpoints; area/volume/flow sizing are placeholders, as the Mollier processes carry no space geometry). Once laid out, the room-side items (Room, Damper, both Group Junctions) are wrapped in a DisplayAirSystemGroup named after the air system and related to it, matching the reference object graph. Verify covers the new structure: room and damper present, single shared air system, and (when the symbol library is available) a DisplayAirSystemGroup. Note: layout is still the converter's single-row flow order; the two-row supply/mirrored-extract coordinate layout the maintainer described is the remaining visual step and needs a Grasshopper viewport to tune. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01D272omUvR5c6173iECqTvy
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01D272omUvR5c6173iECqTvy
… on SAMAnalyticalSystemResults (#11) Fixes CS0535 build errors when downstream repos (e.g. SAM_Revit) pull in sow/2026-Q3, since IGH_SAMComponent gained these members and this was the only implementer in this repo missing them.
- SAMAnalyticalSystemUpdateAirSystem: fix inverted null check (airSystem != null -> == null) - SAMAnalyticalSystemCreateDisplaySystemConnectorManager: fix wrong loop variable (displaySystemConnectors -> systemObjects)
… tested topology and layout (#9) * feat(mollier-bridge): structured diagnostics, humidifier/fan derivation, two-row layout, liquid systems, tests * docs: record P1-P10 delivery status in implementation plan * chore: add SPDX license headers to test files and build scripts * fix: add MinCompatibleVersion and ObsoleteSeverity to SAMAnalyticalSystemResults for IGH_SAMComponent compat * chore: add SPDX license header to SAMAnalyticalSystemResults.cs * revert: remove premature ObsoleteSeverity/MinCompatibleVersion from SAMAnalyticalSystemResults (SAM PR #36 not yet merged) * fix: re-add MinCompatibleVersion and ObsoleteSeverity to SAMAnalyticalSystemResults * test: make the Mollier bridge suite prove behaviour instead of executing it - Test project now outputs to repo-local build_tests\ (was ..\..\build_tests\, escaping the repository and never matching test.ps1's search path) and copies MVRE.json + SAM_DisplaySystemManager.JSON to the output, so the MVRE fixture and the default symbol library resolve deterministically on any machine. - MVRE_ComparisonTests: a missing fixture now fails loudly instead of silently returning (all 5 tests previously passed without ever loading MVRE.json). Replaced output-only comparisons and Assert.True(true) with exact structural assertions against the verified fixture census (1 plant room, 2 energy sources by name, 2 fans, 1 exchanger, 1 damper, 1 space, 25 connections) and both-side air-topology checks; the intentionally-uncompared liquid-plant asymmetry is documented in the tests. - HeatRecoveryProcess_MapsTo_SystemExchanger now builds a real process via the public HeatRecoveryProcess_Supply factory (the old "internal constructor" comment was wrong) and asserts setpoint, calculation method and latent type. - Exchanger_RoundTrip_PreservesEfficiency now sets and round-trips real sensible/latent efficiencies instead of asserting NotNull on a bare object. - test.ps1: missing test DLL, failed Verify run and "no tests matched" all exit 1 and print the captured output; build.ps1 prints build output on failure. Previously the Verify re-run warned and exited 0 unconditionally. - .gitignore: ignore the now repo-local build_tests/. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat: canonical diagnostics contract with emissions at every failure path One code, one meaning, one severity - documented on every DiagnosticCodes constant and enforced end to end: - Renamed four never-emitted constants to the meanings the bridge actually needs: 003 InvalidProcessState (was InvalidEndPoint), 009 ConnectionFailed (was MoistureTransferDetected), 010 DisplaySymbolMissing (was HeatRecoveryMissingExtract), 013 HeatRecoveryCountMismatch (was ConversionWithWarnings). All 14 codes now have XML docs. - Fixed MOLLIER-012 misuse: null chain/group arguments now emit 005 NullProcessChain (Error); 012 ChainEmpty is reserved for chains that produced zero components. - New emission sites: 002 null process, 003 invalid start/end state, 006 ADP unavailable, 007 bypass factor NaN, 008 heat-recovery pair with no usable effectiveness, 009 every failed Connect and every directional-to- automatic connector fallback (AddChain, boundary junctions, room loop, display group), 010 display promotion report lines incl. missing symbol library, 013 supply/extract heat-recovery count mismatch. - Liquid loop wiring failures (index unresolved or Connect false) now emit 014 LiquidInjectionFailed as Error - a partially wired loop is no longer a silent success; severity raised from Warning on the existing sites too. - Template and AirHandlingUnitResult entry points gained out-diagnostics overloads; the old signatures remain as wrappers. InjectLiquidSystems diagnostics are no longer discarded internally. - Deleted dead ConversionResult (never constructed or referenced). - ConversionDiagnosticTests: 8 new/strengthened tests prove 002, 003, 005, 006+007, 012 severity, 013, template-overload propagation and code uniqueness. 83 tests pass. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix: correct fan pressure physics, humidifier %RH setpoints, steam duty and default energy sources Fan pressure (Query/FanPressure.cs): the efficiency now MULTIPLIES. The bridge recovers pressure from the fan temperature pickup, and SAM's own convention (SAM.Core.Mollier Query.PickupTemperature) is dT = SFP/(rho*cp) with SFP = dP/eta, hence dP = eta*rho*cp*dT. The previous dP = rho*cp*dT/eta overstated pressure by 1/eta^2 (~2x at eta 0.7). cp now comes from SpecificHeatCapacity_Air at the inlet state instead of a hardcoded 1010, so FanPressureRise is the exact inverse of PickupTemperature: for a process built with FanProcess(start, sfp), dP == 1000*eta*sfp identically. Humidifier setpoint (Query/HumidifierProperties.cs): now the end-state RELATIVE HUMIDITY [%] for both spray and steam. SAM humidifier setpoints pass straight through to Tas TPD (Convert.ToTPD SprayHumidifier/ SteamHumidifier), whose humidifiers control downstream %RH under default flags (TPD template code sets Setpoint.Value = 90). The previous end dry-bulb degC value was the wrong controlled variable. Steam duty: removed the |dT| < 0.01 isothermality gate. Library-built SteamHumidificationProcess is deliberately not isothermal (injected steam carries sensible heat, dT ~ +0.3 K), so the gate never fired and Duty was never set on any SystemSteamHumidifier. Fan design flow (Create/SystemComponent.cs): stored in litres per second (TPD fan flow unit; template code uses values like 150) with DesignFlowType = Value, instead of the raw m3/s figure with type None. Existing test expectation updated accordingly. Default energy sources (Create/LiquidSystem.cs + entry points): template- less energy centres now carry "Natural Gas" (CO2 0.216, cost 0.05) and "Grid Supplied Electricity" (ElectricalEnergySource, CO2 0.519, cost 0.13) SystemEnergySources mirroring the MVRE.json reference, with the boiler and chiller stamped with SystemObjectParameter.EnergySourceName (+ FanEnergySourceName for the chiller) before Add - exactly the name-based linkage SAM_Tas resolves into TPD FuelSources. Template paths are unchanged: the template keeps supplying its own sources and plant. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix: give the twin-wheel example fans a real temperature rise The example's fans produced no pressure at all. SAM.Core.Mollier's Create.FanProcess(MollierPoint, double) assigns the pickup temperature - a RISE in K, sfp/(rho*cp) - as the End point's ABSOLUTE dry-bulb temperature, so the supply fan entering at 16 C "left" at 0.65 C and the extract fan at 30 C left at 0.68 C. Both rises were therefore negative, FanPressureRise correctly returned NaN for them, and the resulting SystemFans carried Pressure = 0 with OverallEfficiency unset - the exact opposite of what the example is meant to demonstrate. (The factory's four-argument sibling adds the pickup to the inlet temperature, confirming the inconsistency.) Build the fan End state here from the sound Query.PickupTemperature (MollierPoint, sfp) rise instead, keeping the example's specific-fan-power intent explicit as a named constant. The fans now run 16 -> 16.65 C and 30 -> 30.68 C, and both yield Pressure = 560 Pa = eta * SFP * 1000 exactly, independent of inlet state, as the SFP relation requires. The upstream factory defect is left for a separate SAM_Mollier change; this uses only sound upstream APIs and stays correct once that is fixed. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * test: prove fan, humidifier, twin-wheel, layout and liquid-loop behaviour 40 new tests replacing NotNull-only coverage with numerical and topological proof. Every value is either hand-derived or independently re-derived in the test; no assertion was relaxed to make a run green. Query/FanPressureTests (6): dP is the exact inverse of PickupTemperature (recovering the SFP from the pressure returns the original rise to 1e-9); the SFP invariant dP == eta*sfp*1000 holds at two different inlet states, proving rho and cp cancel; pressure scales LINEARLY with efficiency, which fails outright if the efficiency is moved back to the denominator; NaN guards for null/NaN/zero/negative efficiency and zero temperature rise. Query/HumidifierPropertiesTests (10): spray setpoint equals End relative humidity and is banded 50-75 %, excluding the old ~22 C dry-bulb value; effectiveness and water flow re-derived from the humidity-ratio balance and banded; steam duty is set and matches |m*dh| ~ 25 kW - the test first asserts the process is NOT isothermal, proving the removed gate would have suppressed it; components carry the derived values; edge cases: negative humidity change clamps to 0, saturated start (zero denominator) yields no effectiveness, NaN airflow leaves flows NaN but still sets the setpoint, null process leaves everything NaN. Query/HeatRecoveryEfficiencyTests (4): the query exactly inverts the factory's design percentages (75/65 -> 0.75/0.65); sensible-only recovery leaves latent NaN rather than 0; clamping holds at 1.0 for a 150% request. Create/TwinWheelTopologyTests (6): the real TwinWheelExample fixture gives exactly one AirSystem and exactly one shared exchanger despite both chains carrying heat recovery; all four exchanger air connectors are connected with In on 0/2 and Out on 1/3; efficiencies are 0.75/0.65 with latent type HumidityRatio and survive a JSON round-trip; both fans carry 560 Pa, 0.7 efficiency and 2500/2300 l/s. Create/TwoRowLayoutTests (6): exactly two rows at y = 0 and y = -0.8; supply row steps left-to-right by exactly 1.0 per chain-consecutive component; the extract row runs right-to-left along its airflow; the shared exchanger is drawn once, on the supply row, reaching the extract row only through connections; all air connections carry routed geometry and no symbol is missing; a short supply-only chain falls back to a valid single row. Create/LiquidLoopTests (8): one boiler and one chiller on two named liquid systems; every heating coil is wired liquid In 3 / Out 4 and every cooling coil In 2 / Out 3; both loops are closed - the plant equipment has both connectors connected and NONE left unconnected; each loop relates only its own plant; the two default energy sources exist with the right types and CO2 factors and the stored boiler/chiller reference them by name; the template path injects no duplicate plant, loops or sources while still merging the air side. Test project also copies Plantroom-Only.json to the output for the template test. 123 tests pass. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * docs: describe the bridge that actually exists The docs described an implementation that in places never existed. Corrected against the code, with every claim now backed by a passing test: - Fan pressure: dP = eta*rho*cp*dT (efficiency multiplies), cp read from the inlet state via SpecificHeatCapacity_Air rather than a hardcoded 1010, and the inverse relationship to PickupTemperature stated. The old dP = rho*cp*dT/eta was wrong by 1/eta^2. - Humidifier setpoint: end-state relative humidity [%] for BOTH spray and steam, with the Tas TPD rationale. The old "target humidity ratio" claim matched neither the code nor the consumer. Steam duty documented as unconditional, with the reason the isothermality gate was dead. - Fan design flow documented in l/s with DesignFlowType = Value. - Diagnostics: both docs now carry the same canonical 14-code table as ConversionDiagnostic.cs. Previously the plan's two tables disagreed with the code on ~8 of 14 codes and omitted MOLLIER-014, and an edge-case table contradicted the canonical severities and mapped null process/null chain onto MOLLIER-012; every code/severity mention across both docs was re-checked mechanically against the source. ConversionResult removed from the docs along with the code. - Liquid systems: closed-loop wiring with connector indexes, plus the new default energy sources (names, CO2/cost values, EnergySourceName linkage) and the template path's skip-injection behaviour. - One combined AirSystem carries both supply and extract, in the bridge doc, the plan and SupplyExtract.cs's own XML remarks - all three previously claimed two separate air systems. - Section F pseudocode corrected (it still taught all three defects); Section P's historical prompts flagged as a pre-audit record rather than silently left to mislead. - Counts corrected throughout: 123 tests across 17 files (was "74"/"30+ across 11"), 24 files added / 15 modified (was 21/10), 12 commits. - New Limitations section: structural-only validation with no Tas simulation or export exercised here, the MVRE comparison being type-level rather than count-for-count and why, and the upstream SAM_Mollier Create.FanProcess defect with its workaround. - Unit XML docs added to SystemFan, SystemSprayHumidifier and SystemSteamHumidifier, whose setpoint units were previously undocumented - the ambiguity behind the wrong implementation. The Section Q delivery-status table is deliberately untouched: it is updated only after final verification. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * docs: disambiguate crefs made ambiguous by the new diagnostics overloads Adding the out-diagnostics overloads turned previously-unique <see cref="SystemEnergyCentre"/> / <see cref="SystemPlantRoom"/> references into ambiguous ones, adding 14 CS0419 warnings. Those crefs mean the return TYPE, so qualify them as SAM.Core.Systems.*. Warning count returns to the base branch's 21; the 19 pre-existing CS0419 locations are untouched. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * docs: record delivery status against executed verification Rewrites the Section Q status table now that the acceptance evidence exists. The previous table marked all ten phases complete on the strength of code existing: P3, P4 and P6 were in fact delivering wrong values, and P5 and P6 had no acceptance tests at all. Each phase now cites the tests that prove it, and the verification table records results from clean worktrees with SAM and SAM_Mollier at origin/sow/2026-Q3: - build.ps1 Debug and Release: all 3 solutions, exit 0 - test.ps1 Debug: tests and TwinWheelExample.Verify both gated, exit 0 - dotnet test: 123 passed, 0 failed, 0 skipped, none returning early - TwinWheelExample.Verify: 26/26 PASS lines with the display checks executed (the previously claimed 27/27 was never accurate) - MVRE: 5 passed with the fixture provably loaded from the test output - JSON round-trip: 10 passed - 21 compiler warnings, identical to the base branch Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Update mollier_processes_to_systems_IMPLEMENTATION_PLAN.md * docs: correct the modified-file count and the SAMAnalyticalSystemResults note The count was taken before this audit's own last production edits and went stale by exactly the three files those edits touched: 18 files are modified vs sow/2026-Q3, not 15. The three XML unit-doc files are now listed. Also corrects the Section Q note calling SAMAnalyticalSystemResults.cs an unrelated change. It is required: SAM PR #36 is merged on SAM sow/2026-Q3 and adds MinCompatibleVersion and ObsoleteSeverity to IGH_SAMComponent, so without them SAM_Systems.sln does not compile against the target branch. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
SAM_Mollier PR #7 (fix/fan-process-pickup-temperature, merged into its sow/2026-Q3) corrected Create.FanProcess(MollierPoint, double) to add the pickup temperature to the inlet dry-bulb rather than assigning it as an absolute temperature. The bridge-side workaround this branch's ancestor PR added to work around that defect is no longer needed. TwinWheelExample.cs: both example fans now call the standard start.FanProcess(SpecificFanPower) directly; the private FanProcessBySpecificFanPower helper and its defect-describing XML comment are removed. SpecificFanPower stays 0.8. Fan-pressure derivation in SAM_Systems (Query/FanPressureRise.cs) is untouched, as is all topology, diagnostics, liquid-system and layout code. docs/Mollier-Systems-Bridge.md: removed the Limitations bullet describing the upstream defect and workaround (no longer true), and noted in Status/TODO that the example uses the standard factory directly. docs/mollier_processes_to_systems_IMPLEMENTATION_PLAN.md: updated the Modified Files table's TwinWheelExample.cs row to describe the current state rather than the removed workaround, and added a new Section S recording the upstream fix and this follow-up (the existing Section R historical record from 2026-07-17 is left as-is - it accurately describes what was true when written). Removed a test comment in FanPressureTests.cs that claimed Create.FanProcess(start, sfp) could not be used to build test fixtures and pointed at the now-removed workaround; the test's own logic is unchanged. Both example fans still produce Pressure = 560 Pa, OverallEfficiency = 0.7 (= eta * SFP * 1000 = 0.7 * 0.8 * 1000), confirmed by TwinWheel_Fans_HavePressureAndEfficiency in TwinWheelTopologyTests.cs. Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Iteration 0 step 5b. SAM_Systems joins the workstream as the fourth repo. CapabilityIndex.JSON sits beside the ten shipped templates and says what each can do, so choosing a system reads about forty bits instead of the twelve megabytes the templates weigh between them. Entries are keyed on the existing SystemTemplate identity - no new system or equipment identity, and no MVHR, because MVRE already is SAM's heat-recovery ventilation. Only the ventilation part is stated: these are ventilation templates, and heating, cooling, plant room and controls are chosen elsewhere. Rank is the preference order among systems that all meet a requirement, lower first. It lives here because it is a policy about this library rather than something that follows from Approved Document F - which is exactly why SAM.Analytical stopped inferring it. The order proposed is simplest-provision-first and is Michal's to confirm. Two findings from reading the templates, and both are recorded rather than smoothed over: - No shipped template models a summer bypass. SummerBypass is false for all ten, so a scenario requiring bypass is REFUSED until a template provides one. Iteration 2 needs that template. Declaring it true would credit a dwelling with mitigation the design does not have. - Continuous ventilation and boost cannot be read off a template at all - nothing in the files marks either - so they are declared from the system type's meaning in SAM_SystemTypeLibrary.JSON. Each entry carries an EvidenceFromTemplate list naming the capabilities that WERE established from the file, and the test asserts the index says so honestly rather than presenting a declaration as a measurement. Heat recovery IS in the files, and the conformance test checks every claim against them: a template recovers heat exactly when it holds an exchanger with a sensible efficiency. It also re-establishes the fact the whole MVRE-is-MVHR decision rests on - one exchanger, 0.7 sensible, 0 latent, no recirculation component - and that MV is the same system without it. Resolution to a concrete SystemEnergyCentre matches on the ventilation identity and refuses on ambiguity or a missing file, never a nearest match. It is the one point at which a template is opened at runtime, and it happens once, after the choice, for the system chosen. The conformance test reads the repository's own resources rather than a copy. The first version walked up to the first folder with a matching path and found build_tests/net8.0/, where the Mollier tests copy two templates - it would have conformance-checked the index against a two-file copy and passed a library eight templates short. It now identifies the repository root. 9 tests. SAM_Systems Mollier suite still 123 passed. Generated by Michal Dengusiak and CodeClaude Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Review fixes on c6a441d. Two of these were confidently wrong answers, not robustness polish. A missing or mistyped Rank now refuses the whole index. It used to default to 0, with a comment claiming that showed up as a tie and would be refused - which was simply wrong: ONE missing rank is a unique 0, it sorts FIRST, and the entry somebody forgot to rank becomes the preferred answer. The review changed VAV's "Rank" to "rank" - one letter - and every dwelling requirement then selected a commercial variable-air-volume unit in place of a dwelling extract fan, silently. A ventilation identity that is present but is not usable text also refuses the index. An absent one still means "not a ventilation template" and is skipped, which is what Plantroom-Only is; treating a broken one the same way made a system quietly vanish from the library. Identities are read as text and rebuilt through SystemTemplate's property setters, which strip spaces. Its JSON path assigns fields raw, so an index entry reading "M V" was offered as a selectable system and then resolved to nothing, because a caller's constructor-built "M V" is "MV". A Resource is a file name and nothing else - anything with a separator or a parent reference is refused rather than combined, so an index cannot reach outside the directory it ships in. The "no template is opened" promise is now proved structurally instead of by a stopwatch: the index is copied ALONE into an empty directory and the whole selection still gives the same answers for every requirement Part F can produce. The timing version was machine-dependent, a single long pause could flip it, and it covered one requirement. The new test also asserts that resolution DOES fail without the template, so it cannot pass by reading nothing. SummerBypass was declared evidence-backed and never checked. It is now: no template may hold a bypass token other than BypassFactor, a cooling-coil parameter that would false-positive a naive search. HeatRecovery is checked for an efficiency ABOVE ZERO rather than merely for the key existing - an exchanger set to 0 would have passed while claiming recovery - and MVRE is asserted to hold exactly one exchanger. MechanicalSupply added to the index for the SAM.Analytical vocabulary change: false for NV, EOL, EOC and UV, true for the five that supply air. Application is documentation only, so a caller reading CapableSystems can tell that CAV, VAV and DISP are commercial air-handling types. The Rank rationale is corrected to what the numbers actually are - domestic-before-commercial, unconditioned last - not simplest-first, which CAV, DISP and UV all contradicted. SAM.Analytical.Systems.Tests is now in SAM_Systems.sln, so CI compiles it. CI still runs no tests in this repo; recorded as a follow-up. 20 tests. Mollier suite still 123. Solution builds clean under VS MSBuild. Generated by Michal Dengusiak and CodeClaude Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Step 5c. Application was documentation, and Rank was doing a job it should never have had. A commercial air-handling type is not a worse answer for a dwelling - it is not an answer - so it is now filtered out in SystemCapabilityDescriptors before anything reaches SAM.Analytical. A Domestic request is offered the Domestic and Any entries and nothing else. Previously CAV, VAV and DISP were reported as SUITABLE for an Approved Document F requirement and only their ranks kept them out of the answer, so one edited number could have put a variable-air-volume unit in a flat. A test now inverts every rank in the index so commercial sorts first, confirms that premise, and asserts a dwelling still never sees one. SystemApplication lives here, not in SAM.Analytical. Which of THESE templates is a dwelling system is a fact about this repository's resources, on the same footing as their capabilities; the analytical layer never learns the words "domestic" or "commercial". A missing or unrecognised Application refuses the whole index - defaulting it to eligible would offer an unclassified template to a dwelling, defaulting it to ineligible would make a system quietly vanish. Rank is now recorded as what it is: declared library policy, not derived and not a claim of engineering minimality. The domestic order is marked PROVISIONAL and unconfirmed rather than justified - it makes selection deterministic, nothing more. Commercial ranks no longer keep anything out of a dwelling answer, so they order commercial-to-commercial selection only. The commercial Boost values are recorded as unverified declarations. An earlier revision justified CAV and DISP boost = false from "constant volume" and "displacement regime", which is inferring a capability from a label rather than knowing it. The values stay false - false refuses, true would silently mis-assess - but the index now carries UnverifiedDeclarations and a test asserts the name-derived justifications are gone. CI executes the conformance suite instead of merely compiling it. It is the only thing that checks the index against the shipped templates, and a drifted index is otherwise invisible until it selects the wrong system. 27 tests. Mollier suite still 123. Solution builds clean under VS MSBuild. Generated by Michal Dengusiak and CodeClaude Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Four of these were confidently wrong, not robustness polish. The new CI test step silently restamped three shipped assemblies to 1.0.0.0. SAM.Analytical.Systems, SAM.Core.Systems and SAM.Geometry.Systems all write to the same build\ folder the artifact step publishes, and dotnet test without /p:SAMVersion recompiled them at the local-dev fallback and overwrote the CI-stamped DLLs - so the uploaded artifact would have shipped version-inconsistent assemblies and quietly defeated the AssemblyInfo-versioning programme for this repo. Verified fixed: the test build now leaves 2026.3.999.0 in place where it previously left 1.0.0.0. VAV declared Boost true while listing Boost as unverified, contradicting the index's own note and the previous commit message, both of which said unverified values stay false. "Variable Air Volume" -> can vary -> can boost is the same inference from a label that CAV and DISP were corrected for, so the capability is no longer credited. A test now asserts every capability named in an UnverifiedDeclarations list is false, which is what would have caught it. A request for SystemApplication.Any returned only the entries literally marked Any - in this library the single template that can never be selected - so a caller meaning "I will take anything" got every dwelling refused with a message about missing capabilities rather than about the filter. As a request, Any and Undefined both now mean no constraint; the enum's two roles are documented as the asymmetry they are. Enum.TryParse accepts numeric text and comma-separated name lists, and because the members number Domestic=1, Commercial=2, Any=3, both "3" and a well-meaning "Domestic,Commercial" parsed to Any - the most permissive value there is - with Enum.IsDefined unable to object because the result is defined. Applications are now matched against the three legal names, ordinally. The systemApplication parameter is no longer optional. Defaulting it to Undefined made the commercial-inclusive call the one you get by writing nothing, with a comment saying Part F must pass Domestic - a comment enforcing exactly what the parameter existed to stop being a comment. The index's guarantee is scoped honestly. It claimed a commercial template cannot appear in a dwelling answer whatever the ranks say; that is true of capability selection and NOT of the repository, because the older Query.DefaultSystemEnergyCentres / Create.SystemEnergyCentre path derives a template from a file NAME and matches it against a space's internal condition, consulting neither the index nor Application. Four Grasshopper components reach it and nothing in step 5 changed it. Recorded as a follow-up and pinned by a test rather than left to be discovered. Also: a non-commercial entry carrying UnverifiedDeclarations was forbidden by a comment and now by an assertion; the description check no longer pins one exact English phrase; the rank-inversion test guards the "!" marker it depends on; ranks need only be distinct within an application, which is the only set they order; the Sorted helper's de-duplication is documented and backed by count assertions; and a failing test no longer costs the build artifacts. 34 tests. Mollier suite still 123. Solution builds clean under VS MSBuild. Generated by Michal Dengusiak and CodeClaude Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`SystemCapabilityDescriptors` applied eligibility with a `continue` as soon as it had read an entry's `Application`, which skipped everything below it: the ventilation text check, `SystemTemplate.IsValid`, and the `Rank` refusal. So a broken entry refused the whole index only when the caller happened to be asking for that entry's application, and the same half-edited catalogue was accepted for one application and rejected for another. The entry this reopens the hole on is the one the reader's own `Rank` comment records as the original defect: a case typo on VAV's `"Rank"`. VAV is `Commercial`, so after eligibility became a filter that typo refused an `Any` or `Commercial` request and was silently dropped from a `Domestic` one - and Approved Document F selection is exactly the `Domestic` caller. The existing `MalformedIndex_ProducesNoDescriptors` breaks EOL, a domestic entry, so it went on passing. Eligibility now filters the survivors instead: every entry is parsed and validated first, and `IsEligible` is the last thing before `result.Add`. An ineligible entry is therefore proved sound before being left out. Reported by chatgpt-codex-connector on SAM_Systems#14. Tests: `BrokenEntryOutsideTheRequestedApplication_StillRefusesTheIndex` breaks VAV five ways and asserts all four request kinds refuse - all five cases fail without the reordering. `ValidatingEveryEntry_DoesNotChangeWhatIsOffered` is the other half, pinning that an intact index still answers 6 domestic / 4 commercial, so the fix was not bought by leaking commercial templates into a dwelling answer. 40/40 SAM.Analytical.Systems.Tests and 123/123 Mollier tests pass in Release. Generated by Michal Dengusiak and CodeClaude Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Generated by Michal Dengusiak and CodeClaude Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
docs: record merged SAM#123 manufacturer-guidance work in PROJECT_PROGRESS
Cooling rule ExchangerThenCoil (60-120 l/s: HX 87.96-81.36 %, DX drop 9.265-7.705 K, fan rise 0.3-1.1 K, 13 degC floor), bypass extract 19 degC, cooling airflow range 60-120 l/s with an 80 l/s stated default; guidance settings resolve the dwelling figure, then the default, then the midpoint. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…erged Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…9-24 feat(parto): Nuaire entry follows the 24 Sep 2026 reply
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
docs: record the merged Nuaire reply work
A stated GuidanceTemplate (MVRE) makes the guidance settings partial: named units are the product's MVRE + DX arrangement from the imported prototype, every other unit ordinary MV, all in one plant room sharing one set of plant collections. Every dwelling zone is now DisplacementVentilation = false (owner decision; SAM#129). A guidance-settings overload operates at SAM's cooling airflow rule. The legacy all-guidance call is unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…-2026-09-27 feat(parto): cooled and uncooled units in one materialisation (PR3B-2)
…a PR branch Owner convention (28 Sep 2026): code + tests + evidence -> final PR CI -> merge -> PROJECT_PROGRESS.md closeout (with the merge SHA) as a direct docs-only commit on the base branch. While a PR is open, its status and evidence live in the PR record document and PR description. Unrelated stream entries are never modified. Same wording in SAM, SAM_Systems, SAM_Tas, SAM_UI and SAM_Deploy. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…upply fan (tests not yet run) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…-09-29 fix(systems): place the guidance DX cooling coil between exchanger and supply fan
…48542d) Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…system scope SAM decides which ventilation systems a Part O Systems-route run assesses (Query.PartOSystemsMaterialisationScope). SAM_Systems had no way to be told: D2 resolved every system of the cluster (unit-less NV 1 / UV 1 refused), and D10's duty cross-check summed every system naming a unit model-wide. - New overload Create.MechanicalVentilation(..., spaces, guids_VentilationSystem). Null = the legacy call (the 4-parameter method delegates to it, unchanged). Stated: only those systems reach D2 and D10; a system not stated is never processed, so a malformed one cannot refuse. Empty, Guid.Empty or a guid that is not a ventilation system of the model refuses; repeats count once. - Every refusal of a stated system is unchanged, with the same text. - Identities unchanged: scoped on the whole model == unscoped on SAM's working copy. Tests: MechanicalVentilationSystemScopeTests (34, production-shaped AddMechanicalSystems scaffold NV 1 / UV 1 / MV 1 -> AHU1 beside two Part O MVHR flats). 7 mutations killed. SAM.Analytical.Systems.Tests 303/303, Mollier 123/123. Record: docs/PartO-SystemsBoundary-PR3.md. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…026-09-30 Part O PR-3: MechanicalVentilation honours a stated ventilation system scope
…893e71) Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…om upstream contribution Removes AGENTS.md, PROJECT_PROGRESS.md and internal evidence/.claude artefacts that are SAM-BIM workflow records, not upstream project documentation. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
PR1: Use selected Part O cooling control room
…ranch Internal progress record; not part of the upstream contribution. It remains in sow/2026-Q3 history and will be restored in sow/2026-Q4. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Drop the AI implementation-prompt and repository-workflow sections and signature line from the Mollier plan docs, reword a reviewer-name comment, neutralise a placeholder string in the shipped CapabilityIndex JSON comment text. No code change. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
2026-Q3 quarter-close promotion (Hoare Lea promotion pilot)
This PR promotes SAM-BIM's
sow/2026-Q3branch to Hoare Leamaster, per the standard SAM quarter-close release convention.Source (SAM-BIM
sow/2026-Q3):05ca0c187bd8647b4ea105dd8c0e230000ccfeabTarget (Hoare Lea
master):a8cb990db1e9235debcd27ad09d1269c1d4c1802Hoare Lea
masteris a clean ancestor ofsow/2026-Q3(60 commits ahead, no divergence, no unrelated-history issue).Q3 scope summary (from git history)
CI / checks
Combined check-runs on Q3 HEAD (
05ca0c18): PASS —buildsuccess.Notes
SAM,SAM_UI,SAM_Systems,SAM_Tas). No cross-repo merge-ordering dependency is required, but reviewers should be aware the other three pilot PRs land from the same Q3 cut.masterwith Hoare Leamasteris a later, separate step and is explicitly out of scope for this PR.🤖 Generated with Claude Code