comcheck-api 1.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. comcheck_api/DISCLAIMER.md +24 -0
  2. comcheck_api/__init__.py +99 -0
  3. comcheck_api/ai/__init__.py +30 -0
  4. comcheck_api/ai/skill/SKILL.md +285 -0
  5. comcheck_api/ai/skill/__init__.py +5 -0
  6. comcheck_api/ai/skill/reference/operations.md +101 -0
  7. comcheck_api/ai/skill/reference/simulation.md +99 -0
  8. comcheck_api/ai/skill/reference/types.md +90 -0
  9. comcheck_api/ai/skill/scripts/__init__.py +1 -0
  10. comcheck_api/ai/skill/scripts/validate_code.py +210 -0
  11. comcheck_api/api/__init__.py +1 -0
  12. comcheck_api/api/api_services.py +273 -0
  13. comcheck_api/cli.py +136 -0
  14. comcheck_api/client/__init__.py +1 -0
  15. comcheck_api/client/comcheck_client.py +335 -0
  16. comcheck_api/constants/__init__.py +0 -0
  17. comcheck_api/constants/building_area_constants.py +35 -0
  18. comcheck_api/constants/common_constants.py +116 -0
  19. comcheck_api/constants/envelope_constants.py +250 -0
  20. comcheck_api/defaults.py +150 -0
  21. comcheck_api/exceptions.py +54 -0
  22. comcheck_api/introspection.py +188 -0
  23. comcheck_api/managers/__init__.py +0 -0
  24. comcheck_api/managers/components/__init__.py +0 -0
  25. comcheck_api/managers/components/building_area.py +11 -0
  26. comcheck_api/managers/components/envelope/__init__.py +0 -0
  27. comcheck_api/managers/components/envelope/ag_wall.py +97 -0
  28. comcheck_api/managers/components/envelope/bg_wall.py +39 -0
  29. comcheck_api/managers/components/envelope/door.py +11 -0
  30. comcheck_api/managers/components/envelope/floor.py +11 -0
  31. comcheck_api/managers/components/envelope/roof.py +30 -0
  32. comcheck_api/managers/components/envelope/skylight.py +11 -0
  33. comcheck_api/managers/components/envelope/window.py +11 -0
  34. comcheck_api/managers/data_manager.py +369 -0
  35. comcheck_api/project_operations/__init__.py +8 -0
  36. comcheck_api/project_operations/project_building_area_operations.py +107 -0
  37. comcheck_api/project_operations/project_envelope_operations.py +899 -0
  38. comcheck_api/schemas/comCheck.schema.json +6463 -0
  39. comcheck_api/types/__init__.py +49 -0
  40. comcheck_api/types/api_types.py +127 -0
  41. comcheck_api/types/common_types.py +32 -0
  42. comcheck_api/types/core_types.py +4198 -0
  43. comcheck_api/types/custom_base_model.py +314 -0
  44. comcheck_api/utilities/__init__.py +5 -0
  45. comcheck_api/utilities/common.py +50 -0
  46. comcheck_api/utilities/envelope_utilities.py +46 -0
  47. comcheck_api/utilities/id_registry.py +79 -0
  48. comcheck_api/utilities/project_utilities.py +60 -0
  49. comcheck_api/validation.py +64 -0
  50. comcheck_api-1.0.0.dist-info/METADATA +244 -0
  51. comcheck_api-1.0.0.dist-info/RECORD +54 -0
  52. comcheck_api-1.0.0.dist-info/WHEEL +4 -0
  53. comcheck_api-1.0.0.dist-info/entry_points.txt +2 -0
  54. comcheck_api-1.0.0.dist-info/licenses/LICENSE +24 -0
@@ -0,0 +1,24 @@
1
+ DISCLAIMER
2
+
3
+ This material was prepared as an account of work sponsored by an agency of the
4
+ United States Government. Neither the United States Government nor the United
5
+ States Department of Energy, nor Battelle, nor any of their employees, nor any
6
+ jurisdiction or organization that has cooperated in the development of these
7
+ materials, makes any warranty, express or implied, or assumes any legal
8
+ liability or responsibility for the accuracy, completeness, or usefulness or
9
+ any information, apparatus, product, software, or process disclosed, or
10
+ represents that its use would not infringe privately owned rights.
11
+
12
+ Reference herein to any specific commercial product, process, or service by
13
+ trade name, trademark, manufacturer, or otherwise does not necessarily
14
+ constitute or imply its endorsement, recommendation, or favoring by the United
15
+ States Government or any agency thereof, or Battelle Memorial Institute. The
16
+ views and opinions of authors expressed herein do not necessarily state or
17
+ reflect those of the United States Government or any agency thereof.
18
+
19
+ PACIFIC NORTHWEST NATIONAL LABORATORY
20
+ operated by
21
+ BATTELLE
22
+ for the
23
+ UNITED STATES DEPARTMENT OF ENERGY
24
+ under Contract DE-AC05-76RL01830
@@ -0,0 +1,99 @@
1
+ """COMcheckWeb API - Python client for programmatic building energy code compliance.
2
+
3
+ This package provides a type-safe, automated interface to the U.S. Department of
4
+ Energy's COMcheck Web API for building energy code compliance verification.
5
+
6
+ Quick Start:
7
+ >>> from comcheck_api import COMcheckClient
8
+ >>> client = COMcheckClient(api_key="your-api-key")
9
+ >>> project = client.get_project("123")
10
+ >>> session_id = client.start_run_simulation(project)
11
+
12
+ Basic Usage:
13
+ from comcheck_api import (
14
+ COMcheckClient,
15
+ COMCheckHTTPError,
16
+ export_to_json,
17
+ )
18
+
19
+ client = COMcheckClient(api_key="your-key")
20
+
21
+ try:
22
+ projects = client.list_projects()
23
+ export_to_json(projects, "projects.json")
24
+ except COMCheckHTTPError as e:
25
+ print(f"HTTP error: {e.status_code}")
26
+
27
+ Advanced Usage:
28
+ from comcheck_api import (
29
+ COMcheckClient,
30
+ project_envelope_operations,
31
+ )
32
+ from comcheck_api.types import AgWall, ComBuilding
33
+
34
+ # For type imports, use the types submodule
35
+ # from comcheck_api.types import ...
36
+
37
+ # For advanced utilities, use the utilities submodule
38
+ # from comcheck_api.utilities import ...
39
+ """
40
+
41
+ # Client
42
+ from .client import COMcheckClient
43
+
44
+ # Exceptions
45
+ from .exceptions import (
46
+ COMCheckAPIError,
47
+ COMCheckHTTPError,
48
+ COMCheckConnectionError,
49
+ COMCheckValidationError,
50
+ COMCheckSimulationError,
51
+ COMCheckProjectNotFoundError,
52
+ )
53
+
54
+ # Project Defaults
55
+ from . import defaults
56
+
57
+ # Project Operations
58
+ from .project_operations import (
59
+ project_building_area_operations,
60
+ project_envelope_operations,
61
+ )
62
+
63
+ # Introspection helpers
64
+ from .introspection import list_operations, lookup_type
65
+
66
+ # Validation helpers
67
+ from .validation import validate_project
68
+
69
+ # Utilities
70
+ from . import utilities
71
+
72
+ # Types
73
+ from . import types
74
+
75
+ __all__ = [
76
+ # Client
77
+ "COMcheckClient",
78
+ # Exceptions
79
+ "COMCheckAPIError",
80
+ "COMCheckHTTPError",
81
+ "COMCheckConnectionError",
82
+ "COMCheckValidationError",
83
+ "COMCheckSimulationError",
84
+ "COMCheckProjectNotFoundError",
85
+ # Project Defaults
86
+ "defaults",
87
+ # Project Operations
88
+ "project_building_area_operations",
89
+ "project_envelope_operations",
90
+ # Introspection
91
+ "list_operations",
92
+ "lookup_type",
93
+ # Validation
94
+ "validate_project",
95
+ # Utilities
96
+ "utilities",
97
+ # Types
98
+ "types",
99
+ ]
@@ -0,0 +1,30 @@
1
+ """AI-facing surface for the comcheck_api package.
2
+
3
+ Ships the canonical Skill folder under ``comcheck_api/ai/skill/``
4
+ (SKILL.md + reference docs + examples + scripts). Use
5
+ :func:`skill_root` to get the on-disk path to the bundled folder —
6
+ useful for installing it into ``<project>/.claude/skills/`` or
7
+ ``~/.claude/skills/``.
8
+
9
+ Introspection and validation helpers live on the SDK itself
10
+ (``comcheck_api.list_operations``, ``comcheck_api.lookup_type``,
11
+ ``comcheck_api.validate_project``).
12
+ """
13
+
14
+ from __future__ import annotations
15
+
16
+ from importlib.resources import as_file, files
17
+ from pathlib import Path
18
+
19
+
20
+ def skill_root() -> Path:
21
+ """Return the on-disk path to the bundled Skill folder.
22
+
23
+ Uses ``importlib.resources`` so the path resolves correctly
24
+ whether the package is installed normally or zip-imported.
25
+ """
26
+ with as_file(files("comcheck_api.ai.skill")) as p:
27
+ return Path(p)
28
+
29
+
30
+ __all__ = ["skill_root"]
@@ -0,0 +1,285 @@
1
+ ---
2
+ name: comcheck-api
3
+ description: Use this skill when the user is writing Python code that uses
4
+ the comcheck_api package to build COMcheck project JSON, run compliance
5
+ simulations against the PNNL COMcheck Web API, or work with envelope
6
+ or building-area operations. Triggers on imports of `comcheck_api`,
7
+ mentions of COMcheck/ASHRAE 90.1/IECC compliance, or requests to
8
+ validate building energy code compliance.
9
+ ---
10
+
11
+ # COMcheck API Python Client Skill
12
+
13
+ ## When to use this skill
14
+
15
+ Triggers:
16
+ - The user imports `comcheck_api` or asks for help with it.
17
+ - The user mentions COMcheck, ASHRAE 90.1, IECC, or commercial building
18
+ energy code compliance.
19
+ - The user wants to build, update, or run a simulation on a COMcheck
20
+ project from Python.
21
+
22
+ ## Core concepts
23
+
24
+ - **Single entry point**: `COMcheckClient` is the only client class
25
+ users instantiate. Construct with `api_key=...`. The client does
26
+ **not** auto-read any environment variable — read it yourself and
27
+ pass it: `COMcheckClient(api_key=os.environ["COM_API_KEY"])`.
28
+ - **Project shape**: a project is a `ComBuilding` Pydantic model with
29
+ lowercase top-level fields: `project` (metadata), `location`,
30
+ `envelope`, `lighting` (which contains `wholeBldgUse[]` — the
31
+ building areas), `hvac`, `renewable`, and `control` (energy code).
32
+ No `Project`/`Control` PascalCase aliases exist.
33
+ The fields `hvac`, `renewable`, and the **interior-lighting fixtures
34
+ inside `activityUse[]`**, plus exterior lighting (`exteriorUse[]`)
35
+ and the shared `fixtureSchedule[]`, exist on the model but have
36
+ **no operation functions** — leave them at template defaults. Only
37
+ `lighting.wholeBldgUse[]` (building areas, including each area's
38
+ own `interiorLightingSpace` singleton) is mutable, via
39
+ `project_building_area_operations`.
40
+ - **Operation modules (functional)**: building areas and envelope
41
+ components are added/updated/removed via free functions in
42
+ `project_building_area_operations` and `project_envelope_operations`.
43
+ Each function takes a `ComBuilding` and returns a new `ComBuilding`.
44
+ - **Envelope items attach to a building-area key**: every
45
+ `add_*_to_project` envelope function takes
46
+ `(project, building_area_key, new_component)`. Look up the key
47
+ with `ba_ops.get_building_area_keys_from_project(project)` first.
48
+ Default projects have **no** building areas — add one with
49
+ `ba_ops.add_building_area_to_project(...)` before any envelope
50
+ work.
51
+ - **Defaults**: `comcheck_api.defaults` has `get_default_*_template()`
52
+ functions that return Pydantic models filled with sensible defaults.
53
+ Always start from these.
54
+ - **Simulation flow is async**: `start_run_simulation` returns a
55
+ session ID. Poll `get_simulation_status` until status is
56
+ `"SUCCESS"` (terminal-ok) or `"FAILED"` (terminal-error), then call
57
+ `get_simulation_result`. See `comcheck_api.types.SimulationStatus`
58
+ for known lifecycle values (the catalog isn't exhaustive — only
59
+ the terminal pair is guaranteed stable).
60
+
61
+ ## Quick start
62
+
63
+ ```python
64
+ import os
65
+ import time
66
+
67
+ from comcheck_api import COMcheckClient
68
+ from comcheck_api.defaults import get_default_project_template
69
+ from comcheck_api.types import SimulationStatus
70
+
71
+ # Client does NOT auto-read COM_API_KEY — pass it in.
72
+ client = COMcheckClient(api_key=os.environ["COM_API_KEY"])
73
+
74
+ # Build a project from a default template
75
+ project = get_default_project_template()
76
+ project.project.projectTitle = "5,000 sqft office in Seattle"
77
+
78
+ # Run a simulation
79
+ session_id = client.start_run_simulation(project)
80
+
81
+ # Poll until terminal — SUCCESS or FAILED
82
+ while True:
83
+ status = client.get_simulation_status(session_id)
84
+ if status["status"] == SimulationStatus.SUCCESS:
85
+ break
86
+ if status["status"] == SimulationStatus.FAILED:
87
+ raise RuntimeError(f"Simulation failed: {status.get('message')}")
88
+ time.sleep(5)
89
+
90
+ result = client.get_simulation_result(session_id)
91
+ print(result["performanceRating"])
92
+ ```
93
+
94
+ ## Conventions (do)
95
+
96
+ - Use `get_default_*_template()` to start any new component, then
97
+ customize. Don't construct `ComBuilding` from scratch.
98
+ - Use the operation modules (e.g.,
99
+ `env_ops.add_ag_wall_to_project(project, area_key, wall)`) to
100
+ mutate project structure. Don't manipulate nested dicts directly.
101
+ - For envelope items, look up the building-area key first via
102
+ `ba_ops.get_building_area_keys_from_project(project)`. The key
103
+ goes between `project` and the new component in every
104
+ `add_*_to_project` envelope call.
105
+ - Set enum-typed fields (`orientation`, `wallType`, `code`, etc.)
106
+ with members from the matching `*Options` enum imported from
107
+ `comcheck_api.types` — not raw strings, which trigger
108
+ Pydantic serialization warnings.
109
+ - Pass `api_key=` explicitly to `COMcheckClient(...)`. The SDK does
110
+ **not** auto-load any env var — read it yourself:
111
+
112
+ ```python
113
+ from dotenv import load_dotenv
114
+ load_dotenv()
115
+ client = COMcheckClient(api_key=os.environ["COM_API_KEY"])
116
+ ```
117
+
118
+ `client.set_api_key(api_key)` is the equivalent post-construction
119
+ setter. Users get a Personal Access Token from the COMcheck Web
120
+ site (Settings → Developer Setting); see the
121
+ [GitHub README](https://github.com/pnnl/comcheckweb-api-python#1-obtain-an-api-key)
122
+ or the
123
+ [Getting Started page](https://pnnl.github.io/comcheckweb-api-python/getting-started/)
124
+ for the full walkthrough.
125
+ - Wrap network calls in try/except and catch `COMCheckHTTPError`,
126
+ `COMCheckConnectionError`, `COMCheckValidationError`,
127
+ `COMCheckSimulationError`, `COMCheckProjectNotFoundError`.
128
+ - The package uses `httpx`, not `requests`.
129
+
130
+ ## Conventions (don't)
131
+
132
+ - Don't construct project JSON by hand — use templates + operation
133
+ functions.
134
+ - Don't suggest `requests` — the SDK is `httpx`-based.
135
+ - Don't import private modules (anything starting with `_`).
136
+ - Don't poll `get_simulation_status` faster than every 5 seconds.
137
+ - Don't put the API key in source code; use env var or argument.
138
+ - Don't use `comcheck_api.managers.*` (e.g. `AgWallListManager`,
139
+ `RoofListManager`). Those are internal list-mutation helpers; they
140
+ bypass the validation logic in the operation modules. Always go
141
+ through `project_envelope_operations` and
142
+ `project_building_area_operations` instead.
143
+ - Don't add, update, or remove interior lighting (the
144
+ `activityUse[]` fixtures), exterior lighting (`exteriorUse[]`,
145
+ `fixtureSchedule[]`), HVAC/mechanical, or renewable-energy
146
+ components — no operations exist for them. The whole-building
147
+ `interiorLightingSpace` singleton on each `WholeBldgUse` *is*
148
+ editable through `project_building_area_operations`; the per-
149
+ activity lighting nested under `activityUse[]` is not. The
150
+ `COMcheckClient` user methods (`list_projects`, `get_project`,
151
+ `update_project`, `start_run_simulation`, `get_simulation_status`,
152
+ `get_simulation_result`, `set_api_key`) are fully supported and
153
+ fine to use. If asked for an unsupported mutation area, tell the
154
+ user it's not implemented and offer building-area / envelope /
155
+ simulation instead. Confirm operation scope with
156
+ `comcheck_api.list_operations()` (only `building_area` and
157
+ `envelope` groups exist).
158
+
159
+ ## Common patterns
160
+
161
+ ### Adding a building area, then an above-grade wall
162
+
163
+ `get_default_project_template()` starts with **zero building
164
+ areas** — add one before attaching any envelope component.
165
+
166
+ ```python
167
+ from comcheck_api import (
168
+ project_envelope_operations as env_ops,
169
+ project_building_area_operations as ba_ops,
170
+ )
171
+ from comcheck_api.defaults import (
172
+ get_default_building_area_template,
173
+ get_default_ag_wall_template,
174
+ )
175
+ from comcheck_api.types import OrientationOptions
176
+
177
+ # 1. Add the building area (no areas exist by default).
178
+ area = get_default_building_area_template()
179
+ area.areaDescription = "Open office"
180
+ project = ba_ops.add_building_area_to_project(project, area)
181
+
182
+ # 2. Look up its key.
183
+ area_key = ba_ops.get_building_area_keys_from_project(project)[0]["key"]
184
+
185
+ # 3. Attach the wall to that area.
186
+ wall = get_default_ag_wall_template()
187
+ wall.description = "South wall"
188
+ wall.orientation = OrientationOptions.SOUTH # use the enum, not "SOUTH"
189
+ wall.grossArea = 4800.0 # field is grossArea, not area
190
+ project = env_ops.add_ag_wall_to_project(project, area_key, wall)
191
+ ```
192
+
193
+ ### Listing the user's projects and updating one
194
+
195
+ ```python
196
+ projects = client.list_projects()
197
+ target = next(p for p in projects if p["name"] == "My office") # key is "name", not "title"
198
+ project_obj = client.get_project(target["_id"]) # note the underscore
199
+ project_obj.project.projectTitle = "My office (revised)"
200
+ client.update_project(project_id=target["_id"], project_data=project_obj)
201
+ ```
202
+
203
+ `list_projects()` returns **raw, untyped dicts** straight from the API
204
+ (no TypedDict enforces the shape). Each entry looks like:
205
+ `{"_id", "name", "energyCode", "status", "sharing", "ownedByMe",
206
+ "lastUpdated"}`. The display name is under `"name"` — there is **no
207
+ `"title"` key** (matching on `"title"` silently fails with
208
+ `StopIteration`/`KeyError`).
209
+
210
+ `get_project(project_id, mode="json")` returns a raw dict instead of
211
+ a `ComBuilding` model — handy when you just need the JSON shape.
212
+
213
+ ### Polling a simulation with a timeout
214
+
215
+ ```python
216
+ import time
217
+ from comcheck_api.types import SimulationStatus
218
+
219
+ session_id = client.start_run_simulation(project)
220
+ deadline = time.time() + 300 # 5 min
221
+ while time.time() < deadline:
222
+ status = client.get_simulation_status(session_id)
223
+ if status["status"] == SimulationStatus.SUCCESS:
224
+ result = client.get_simulation_result(session_id)
225
+ break
226
+ if status["status"] == SimulationStatus.FAILED:
227
+ raise RuntimeError(f"Simulation failed: {status.get('message')}")
228
+ time.sleep(5)
229
+ else:
230
+ raise TimeoutError(f"Simulation {session_id} did not complete in 5 min")
231
+ ```
232
+
233
+ ## Gotchas
234
+
235
+ - **Field names are lowercase camelCase**, not PascalCase.
236
+ `project.project.projectTitle`, `project.control.code`,
237
+ `project.lighting.wholeBldgUse[0].areaDescription`. There is no
238
+ `Project`, `Control`, `WholeBldgUse` (top-level), or `.title`.
239
+ - **AG/BG wall area field is `grossArea`**, not `area`. Same for
240
+ roofs/floors/windows/doors. Setting `.area` silently does nothing
241
+ because Pydantic models reject unknown attributes only in strict
242
+ mode.
243
+ - **Use enum members for typed fields**: `OrientationOptions.NORTH`,
244
+ `WallTypeOptions.WOOD_FRAME_16_AG_WALL`,
245
+ `EnergyCodeOptions.CEZ_90_1_2022`. Setting them to raw strings
246
+ works at runtime but emits `PydanticSerializationUnexpectedValue`
247
+ warnings every time the model serializes.
248
+ - **There is no `STEEL_FRAME` wall type — steel framing maps to
249
+ `METAL_FRAME_*`.** `WallTypeOptions` members are `WOOD_FRAME_16/24`,
250
+ `METAL_FRAME_16/24`, `METAL_WALL_WO_TB`, `METAL_BLDG`, `CONCRETE`,
251
+ `MASONRY`, `OTHER` (each suffixed `_AG_WALL`). A user asking for a
252
+ "steel-frame wall" wants `WallTypeOptions.METAL_FRAME_16_AG_WALL`
253
+ (or `_24_`).
254
+ - **`COMcheckClient(api_key=...)` does not auto-read env vars.** No
255
+ `COM_API_KEY` fallback exists in `__init__`. Pass it explicitly.
256
+ - **`SimulationStatus` is a known-values catalog, not an exhaustive
257
+ contract.** The server may emit lifecycle values not yet in the
258
+ enum (e.g. `EVALUATING` was added in a later version). The
259
+ `status` field comes back as a plain `str` so unknown values
260
+ don't crash polling. Only `SUCCESS` and `FAILED` are guaranteed
261
+ terminal — break/raise on those, keep polling for everything
262
+ else (don't enumerate non-terminals).
263
+ - **A 500 from `start_run_simulation` usually means bad project
264
+ data, not a server outage.** All HTTP errors surface as the same
265
+ `COMCheckHTTPError` — there is no validation-specific exception, so
266
+ a payload the engine rejects (e.g. a malformed shape like assigning
267
+ a list to an object field, or project state the engine won't
268
+ accept) comes back as a bare HTTP 500, indistinguishable from a
269
+ real outage. To tell them apart, simulate a fresh
270
+ `get_default_project_template()` project: if that succeeds, the 500
271
+ is your project data, not the server. Inspect
272
+ `COMCheckHTTPError.status_code` / `.response_data` for detail.
273
+
274
+ ## When you need more detail
275
+
276
+ - For the authoritative list of what's implemented → call
277
+ `comcheck_api.list_operations()`. Only operations it returns are
278
+ supported.
279
+ - For envelope assemblies (roof, walls, floor, windows, doors,
280
+ skylights, thermal bridges) → read `reference/operations.md`.
281
+ - For Pydantic model field-level details → read `reference/types.md`.
282
+ - For the simulation start/poll/fetch flow → read
283
+ `reference/simulation.md`.
284
+ - To validate generated code against a mocked client → run
285
+ `scripts/validate_code.py`.
@@ -0,0 +1,5 @@
1
+ """Canonical Skill folder for the comcheck_api package.
2
+
3
+ Hand-authored content. Use ``comcheck_api.ai.skill_root()`` to get
4
+ the on-disk path.
5
+ """
@@ -0,0 +1,101 @@
1
+ # Project Operations Reference
2
+
3
+ Operation functions are free functions in two modules:
4
+
5
+ - `comcheck_api.project_operations.project_building_area_operations`
6
+ - `comcheck_api.project_operations.project_envelope_operations`
7
+
8
+ Each function takes a `ComBuilding` and a payload, and returns a new
9
+ `ComBuilding`. Treat them as immutable transformations.
10
+
11
+ ## Building area operations
12
+
13
+ ```python
14
+ from comcheck_api import project_building_area_operations as ba_ops
15
+ ```
16
+
17
+ | Function | Purpose |
18
+ |---|---|
19
+ | `add_building_area_to_project(project, new_building_area)` | Add a `WholeBldgUse` building area to the project. |
20
+ | `update_building_area_in_project(project, building_area_key, updates)` | Update fields of an existing building area by key. |
21
+ | `remove_building_area_from_project(project, building_area_key)` | Remove a building area by key. |
22
+ | `get_building_area_keys_from_project(project)` | List `[{key, areaDescription}, …]` for the project. |
23
+
24
+ ## Envelope operations
25
+
26
+ ```python
27
+ from comcheck_api import project_envelope_operations as env_ops
28
+ ```
29
+
30
+ Every envelope `add_*_to_project` call attaches the new component to
31
+ a building-area key. Default projects have no areas — add one first:
32
+
33
+ ```python
34
+ from comcheck_api.defaults import get_default_building_area_template
35
+
36
+ area = get_default_building_area_template()
37
+ area.areaDescription = "Open office"
38
+ project = ba_ops.add_building_area_to_project(project, area)
39
+
40
+ area_key = ba_ops.get_building_area_keys_from_project(project)[0]["key"]
41
+ ```
42
+
43
+ | Component | Add signature | Update / Remove key |
44
+ |---|---|---|
45
+ | Roof | `add_roof_to_project(project, building_area_key, new_roof)` | `assemblyType` |
46
+ | Above-grade wall | `add_ag_wall_to_project(project, building_area_key, new_ag_wall)` | `assemblyType` |
47
+ | Below-grade wall | `add_bg_wall_to_project(project, building_area_key, new_bg_wall)` | `assemblyType` |
48
+ | Floor | `add_floor_to_project(project, building_area_key, new_floor)` | `assemblyType` |
49
+ | Skylight | `add_skylight_to_project(project, building_area_key, new_skylight, roof=None)` | `assemblyType` |
50
+ | Window | `add_window_to_project(project, building_area_key, new_window, wall=None)` | `assemblyType` |
51
+ | Door | `add_door_to_project(project, building_area_key, new_door, wall=None)` | `assemblyType` |
52
+ | Thermal bridge | `add_thermal_bridge_to_project(project, building_area_key, ag_wall, ...)` | (no update/remove yet) |
53
+
54
+ The `update_*_in_project` and `remove_*_from_project` functions take
55
+ the component's `assemblyType` string, e.g.
56
+ `update_ag_wall_in_project(project, ag_wall_assembly_type, updates)`.
57
+
58
+ ## Nesting rules
59
+
60
+ - Skylights live inside a `Roof`. Pass `roof=` to
61
+ `add_skylight_to_project` to attach to a specific roof; otherwise
62
+ it goes on the first one in the area.
63
+ - Windows and doors live inside an above-grade or below-grade wall.
64
+ Pass `wall=` to target a specific wall.
65
+ - Thermal bridges always require an `ag_wall=` argument.
66
+ - Floors and walls live directly under the building area.
67
+
68
+ ## Working with templates
69
+
70
+ Always start from `comcheck_api.defaults`:
71
+
72
+ ```python
73
+ from comcheck_api.defaults import (
74
+ get_default_project_template,
75
+ get_default_roof_template,
76
+ get_default_ag_wall_template,
77
+ get_default_bg_wall_template,
78
+ get_default_floor_template,
79
+ get_default_window_template,
80
+ get_default_door_template,
81
+ get_default_skylight_template,
82
+ get_default_thermal_bridge_template,
83
+ get_default_building_area_template,
84
+ )
85
+ ```
86
+
87
+ Each returns a fully-populated Pydantic model with sensible defaults
88
+ (Boulder, CO; metal-frame walls; double-pane low-E glazing; etc.).
89
+ Customize fields after construction, then attach to a building area:
90
+
91
+ ```python
92
+ from comcheck_api.types import OrientationOptions
93
+
94
+ area_key = ba_ops.get_building_area_keys_from_project(project)[0]["key"]
95
+
96
+ roof = get_default_roof_template()
97
+ roof.grossArea = 6000.0 # field is grossArea, not area
98
+ roof.cavityRValue = 38.0
99
+ roof.orientation = OrientationOptions.UNSPECIFIED_ORIENTATION
100
+ project = env_ops.add_roof_to_project(project, area_key, roof)
101
+ ```
@@ -0,0 +1,99 @@
1
+ # Simulation Flow Reference
2
+
3
+ Simulations are asynchronous on the server side. Three-step pattern:
4
+
5
+ 1. **Start** — submit the project, receive a session ID.
6
+ 2. **Poll** — repeatedly check status until it reports `SUCCESS`
7
+ (terminal-ok) or `FAILED` (terminal-error).
8
+ 3. **Fetch** — retrieve the result by session ID.
9
+
10
+ ## Status values
11
+
12
+ `get_simulation_status(...)["status"]` is a string. Known values are
13
+ enumerated in `comcheck_api.types.SimulationStatus` (a `StrEnum`),
14
+ but **the catalog is not exhaustive** — the server may emit values
15
+ the SDK doesn't yet enumerate. The status field is typed as `str`
16
+ specifically so unknown values pass through without crashing the
17
+ polling loop. Only the terminal pair (`SUCCESS` / `FAILED`) is
18
+ guaranteed stable; treat anything else as "still in progress."
19
+
20
+ | Value | Terminal? | Meaning |
21
+ |---|---|---|
22
+ | `INITIALIZING` | no | Server is preparing the run |
23
+ | `GENERATING_BASELINE` | no | Building the baseline (code-minimum) model |
24
+ | `GENERATING_PROPOSED` | no | Building the proposed (user) model |
25
+ | `RUNNING_SIMULATIONS` | no | Energy simulations are executing |
26
+ | `CALCULATING_RESULTS` | no | Post-processing performance metrics |
27
+ | `EVALUATING` | no | Evaluating compliance results |
28
+ | `SUCCESS` | **yes** | Result is ready; call `get_simulation_result` |
29
+ | `FAILED` | **yes** | Simulation failed; check the `message` field |
30
+ | (any other value) | no | Future / unknown server state — keep polling |
31
+
32
+ Because `SimulationStatus` is a `StrEnum`, `==` works against both
33
+ the enum member and the raw string:
34
+ ``status == SimulationStatus.SUCCESS`` and
35
+ ``status == "SUCCESS"`` are equivalent.
36
+
37
+ ## Methods
38
+
39
+ ```python
40
+ from comcheck_api import COMcheckClient
41
+ client = COMcheckClient(api_key="...")
42
+ ```
43
+
44
+ | Method | Returns | Purpose |
45
+ |---|---|---|
46
+ | `client.start_run_simulation(project, project_id=None)` | `str` (session ID) | Kick off the simulation. If `project_id` is provided, the project is also saved/updated. |
47
+ | `client.get_simulation_status(session_id)` | `dict` | Current status. Fields: `sessionId`, `status` (see the Status values table above), optional `message`. |
48
+ | `client.get_simulation_result(session_id)` | `dict` | Final result. Fields: `sessionId`, `performanceRating`, `energyCreditPerformanceRating`, `proposedBpf`, `baselineBpf`. |
49
+
50
+ ### Loading an existing project to simulate
51
+
52
+ | Method | Returns | Notes |
53
+ |---|---|---|
54
+ | `client.list_projects()` | `list[dict]` | Each project dict uses **`_id`** (with the underscore), not `id`. |
55
+ | `client.get_project(project_id, mode="python")` | `ComBuilding` | Default. Returns a Pydantic model you can pass straight into `start_run_simulation`. |
56
+ | `client.get_project(project_id, mode="json")` | `dict` | Returns the raw project JSON instead of a parsed model. |
57
+
58
+ ## Polling pattern
59
+
60
+ Always include a timeout. 5 minutes is a safe default for a typical
61
+ small/medium project.
62
+
63
+ ```python
64
+ import time
65
+ from comcheck_api.types import SimulationStatus
66
+
67
+ session_id = client.start_run_simulation(project)
68
+ deadline = time.time() + 300
69
+
70
+ while time.time() < deadline:
71
+ status = client.get_simulation_status(session_id)
72
+ if status["status"] == SimulationStatus.SUCCESS:
73
+ break
74
+ if status["status"] == SimulationStatus.FAILED:
75
+ raise RuntimeError(f"Simulation failed: {status.get('message')}")
76
+ time.sleep(5)
77
+ else:
78
+ raise TimeoutError(f"Simulation {session_id} did not complete in 5 minutes")
79
+
80
+ result = client.get_simulation_result(session_id)
81
+ ```
82
+
83
+ ## Result interpretation
84
+
85
+ - `performanceRating` is the primary pass/fail metric — typically
86
+ expressed as a percentage margin against the baseline. Positive =
87
+ better than baseline = pass.
88
+ - `proposedBpf` / `baselineBpf` are Building Performance Factors for
89
+ the proposed and baseline (code-minimum) buildings.
90
+ - `energyCreditPerformanceRating` is the credit rating used when
91
+ optional measures (renewables, advanced controls, etc.) are
92
+ included.
93
+
94
+ ## Don't
95
+
96
+ - Don't poll faster than every 5 seconds — the backend rate-limits.
97
+ - Don't run multiple concurrent simulations from the same API key
98
+ unless you've confirmed your quota allows it.
99
+ - Don't drop the `session_id` — there's no recovery without it.