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,90 @@
1
+ # Types Reference
2
+
3
+ All types are Pydantic models. Import from `comcheck_api.types`.
4
+
5
+ ## Top-level project model
6
+
7
+ `ComBuilding` is the root project model. **All top-level fields are
8
+ lowercase camelCase** (no `Project`/`Control`/`Envelope` PascalCase
9
+ aliases exist).
10
+
11
+ | Field | Type | Purpose |
12
+ |---|---|---|
13
+ | `project` | `Project` | Project metadata. Title is `project.project.projectTitle`; address fields use `projectAddress` / `projectCity` / etc. |
14
+ | `location` | `Location` | State, city, climate zone. |
15
+ | `envelope` | `Envelope` | Roofs (`roof[]`), AG walls (`agWall[]`), BG walls (`bgWall[]`), floors, windows, doors, skylights. |
16
+ | `lighting` | `Lighting` | Holds `wholeBldgUse[]` — the **building areas / zones**. `wholeBldgUse[]` (including each area's `interiorLightingSpace` singleton) is operable; `activityUse[]`, `exteriorUse[]`, and `fixtureSchedule[]` have no operations. |
17
+ | `hvac` | `HVAC` | Mechanical systems — no operations; not editable via this SDK. |
18
+ | `renewable` | `Renewable` | Renewable energy systems — no operations; not editable via this SDK. |
19
+ | `control` | `Control` | Energy code (`control.code`, e.g. `CEZ_IECC2018`, `CEZ_90_1_2022`). |
20
+
21
+ Building areas (`WholeBldgUse`) are **not top-level** — they live
22
+ under `lighting.wholeBldgUse[]`. Use
23
+ `ba_ops.get_building_area_keys_from_project(project)` to enumerate
24
+ them.
25
+
26
+ ## Envelope component models
27
+
28
+ | Model | Notes |
29
+ |---|---|
30
+ | `Envelope` | Container for all envelope components. |
31
+ | `Roof` | Skylights nest inside. |
32
+ | `AgWall` (above-grade) | Windows, doors, thermal bridges nest inside. |
33
+ | `BgWall` (below-grade) | Same nesting as AgWall. |
34
+ | `Floor` | Top-level. |
35
+ | `Skylight` | Standalone or nested under a `Roof`. |
36
+ | `Window` | Standalone or nested under a wall. |
37
+ | `Door` | Standalone or nested under a wall. |
38
+ | `ThermalBridge` | Nested under a wall. |
39
+
40
+ ## Building area / lighting
41
+
42
+ | Model | Purpose |
43
+ |---|---|
44
+ | `WholeBldgUse` | One building area / zone. Lives in `project.lighting.wholeBldgUse[]`. Has `key`, `areaDescription`, `floorArea`, `ceilingHeight`, `interiorLightingSpace`, etc. |
45
+ | `InteriorLightingSpace` | Lighting configuration for one area. The singleton attached directly to a `WholeBldgUse` is editable via `update_building_area_in_project`; the same model nested under `activityUse[]` is **not** operable (interior-lighting fixtures live there). |
46
+
47
+ Every envelope component has a `bldgUseKey` field that ties it to
48
+ one of these area keys.
49
+
50
+ ## HVAC
51
+
52
+ ⚠️ Documented for shape only — no add/update/remove operations exist
53
+ for any of these models. Leave `project.hvac` at its template default.
54
+
55
+ | Model | Purpose |
56
+ |---|---|
57
+ | `HVAC` | Container. |
58
+ | `HVACSystem` | One HVAC system definition. |
59
+ | `HVACPlant` | Central plant equipment. |
60
+ | `Fan` / `FanSystem` | Fan and fan-system models. |
61
+ | `ServiceWaterHeatingSystem` (SWH) | Service water heating. |
62
+
63
+ ## Enums (StrEnum)
64
+
65
+ Always set typed fields with members from the matching `*Options`
66
+ enum imported from `comcheck_api.types`. Setting them as raw strings
67
+ "works" but emits `PydanticSerializationUnexpectedValue` warnings
68
+ on every serialize.
69
+
70
+ Common ones:
71
+
72
+ | Enum | Examples |
73
+ |---|---|
74
+ | `EnergyCodeOptions` | `CEZ_IECC2018`, `CEZ_IECC2021`, `CEZ_90_1_2019`, `CEZ_90_1_2022` |
75
+ | `OrientationOptions` | `NORTH`, `EAST`, `SOUTH`, `WEST`, plus diagonals and `UNSPECIFIED_ORIENTATION` |
76
+ | `WallTypeOptions` | (see source for full set) |
77
+ | `SimulationStatus` | Known: `INITIALIZING`, `GENERATING_BASELINE`, `GENERATING_PROPOSED`, `RUNNING_SIMULATIONS`, `CALCULATING_RESULTS`, `EVALUATING`, `SUCCESS`, `FAILED`. Catalog is **not exhaustive** — only `SUCCESS`/`FAILED` are guaranteed terminal. |
78
+
79
+ For exhaustive enum values, use
80
+ `comcheck_api.lookup_type("WallTypeOptions")` (or any other enum
81
+ name) — that's the live source of truth and stays in sync with the
82
+ installed SDK.
83
+
84
+ ## Tips
85
+
86
+ - Pydantic models accept dicts in their constructors and produce
87
+ validated instances — useful when interop with JSON.
88
+ - Models export to JSON with `.model_dump_json()`.
89
+ - The SDK accepts dicts for inputs but always returns validated
90
+ Pydantic models — that asymmetry is intentional.
@@ -0,0 +1 @@
1
+ """Helper scripts bundled with the Skill (importable as a Python module)."""
@@ -0,0 +1,210 @@
1
+ """Validate generated Python that uses comcheck_api against a mocked client.
2
+
3
+ Usage:
4
+ python -m comcheck_api.ai.skill.scripts.validate_code <path-to-script>
5
+ cat script.py | python -m comcheck_api.ai.skill.scripts.validate_code -
6
+
7
+ Runs the user's code in a subprocess with the network layer mocked.
8
+ Prints structured errors to stdout and exits non-zero if validation
9
+ fails.
10
+
11
+ This script is invoked by the Skill so Claude can run it via shell
12
+ tools to self-check generated code.
13
+
14
+ Security: the calling layer is responsible for sandboxing — running
15
+ this script from an untrusted source still executes arbitrary Python.
16
+ """
17
+
18
+ from __future__ import annotations
19
+
20
+ import json
21
+ import sys
22
+ from pathlib import Path
23
+
24
+
25
+ def _read_input(arg: str) -> str:
26
+ if arg == "-":
27
+ return sys.stdin.read()
28
+ return Path(arg).read_text()
29
+
30
+
31
+ UNSUPPORTED_PROJECT_ATTRS = {"hvac", "renewable"}
32
+ UNSUPPORTED_LIGHTING_ATTRS = {
33
+ "activityUse",
34
+ "exteriorUse",
35
+ "fixtureSchedule",
36
+ }
37
+
38
+
39
+ def _supported_operation_names() -> set[str]:
40
+ """Live set of supported operation function names from the SDK."""
41
+ try:
42
+ from comcheck_api import list_operations
43
+ except Exception: # noqa: BLE001
44
+ return set()
45
+ return {op.name for op in list_operations()}
46
+
47
+
48
+ def _is_attr_chain(node, head: str, tail: str) -> bool:
49
+ """True if `node` is `<head>.<tail>` — e.g. `project.hvac`."""
50
+ import ast
51
+
52
+ return (
53
+ isinstance(node, ast.Attribute)
54
+ and node.attr == tail
55
+ and isinstance(node.value, ast.Name)
56
+ and node.value.id == head
57
+ )
58
+
59
+
60
+ def _is_lighting_chain(node, attr: str) -> bool:
61
+ """True if `node` is `project.lighting.<attr>`."""
62
+ import ast
63
+
64
+ return (
65
+ isinstance(node, ast.Attribute)
66
+ and node.attr == attr
67
+ and isinstance(node.value, ast.Attribute)
68
+ and node.value.attr == "lighting"
69
+ and isinstance(node.value.value, ast.Name)
70
+ and node.value.value.id == "project"
71
+ )
72
+
73
+
74
+ def validate(code: str) -> dict:
75
+ """Compile-check the provided code and report errors as a dict.
76
+
77
+ Runs three passes:
78
+ 1. Syntax check via :func:`compile`.
79
+ 2. Import check on every imported module name.
80
+ 3. Scope check that the code only uses operations actually exposed
81
+ by the SDK and does not mutate the unsupported `hvac`,
82
+ `renewable`, or non-`wholeBldgUse` lighting subtrees.
83
+ """
84
+ errors: list[dict] = []
85
+
86
+ # 1. Syntax check via compile().
87
+ try:
88
+ compile(code, "<generated>", "exec")
89
+ except SyntaxError as e:
90
+ errors.append(
91
+ {
92
+ "kind": "syntax",
93
+ "line": e.lineno,
94
+ "col": e.offset,
95
+ "message": e.msg,
96
+ }
97
+ )
98
+ return {"ok": False, "errors": errors}
99
+
100
+ # 2. Import check: parse import statements and try importing each
101
+ # module name without executing the user's code.
102
+ import ast
103
+
104
+ tree = ast.parse(code)
105
+ for node in ast.walk(tree):
106
+ modules: list[str] = []
107
+ if isinstance(node, ast.Import):
108
+ modules = [alias.name for alias in node.names]
109
+ elif isinstance(node, ast.ImportFrom) and node.module:
110
+ modules = [node.module]
111
+ for mod in modules:
112
+ try:
113
+ __import__(mod)
114
+ except ImportError as e:
115
+ errors.append({"kind": "import", "module": mod, "message": str(e)})
116
+
117
+ # 3. Scope check: cross-reference symbols against list_operations()
118
+ # so the guard auto-tracks SDK growth, and forbid direct mutation
119
+ # of the unsupported subtrees.
120
+ supported_ops = _supported_operation_names()
121
+
122
+ # Collect the local names that resolve into the operation modules.
123
+ op_module_aliases: set[str] = set()
124
+ for node in ast.walk(tree):
125
+ if isinstance(node, ast.ImportFrom) and node.module == "comcheck_api":
126
+ for alias in node.names:
127
+ if alias.name in {
128
+ "project_envelope_operations",
129
+ "project_building_area_operations",
130
+ }:
131
+ op_module_aliases.add(alias.asname or alias.name)
132
+ elif isinstance(node, ast.ImportFrom) and node.module == (
133
+ "comcheck_api.project_operations"
134
+ ):
135
+ for alias in node.names:
136
+ if alias.name in {
137
+ "project_envelope_operations",
138
+ "project_building_area_operations",
139
+ }:
140
+ op_module_aliases.add(alias.asname or alias.name)
141
+
142
+ for node in ast.walk(tree):
143
+ # Forbid `project.hvac` / `project.renewable` access in any context.
144
+ if isinstance(node, ast.Attribute) and any(
145
+ _is_attr_chain(node, "project", attr) for attr in UNSUPPORTED_PROJECT_ATTRS
146
+ ):
147
+ errors.append(
148
+ {
149
+ "kind": "unsupported-scope",
150
+ "line": node.lineno,
151
+ "message": (
152
+ f"`project.{node.attr}` has no operations in this SDK; "
153
+ "leave it at template defaults."
154
+ ),
155
+ }
156
+ )
157
+
158
+ # Forbid `project.lighting.<unsupported>`.
159
+ if isinstance(node, ast.Attribute) and any(
160
+ _is_lighting_chain(node, attr) for attr in UNSUPPORTED_LIGHTING_ATTRS
161
+ ):
162
+ errors.append(
163
+ {
164
+ "kind": "unsupported-scope",
165
+ "line": node.lineno,
166
+ "message": (
167
+ f"`project.lighting.{node.attr}` has no operations; "
168
+ "only `lighting.wholeBldgUse[]` is editable."
169
+ ),
170
+ }
171
+ )
172
+
173
+ # Calls of the form `<op_module>.<name>(...)` must hit a real op.
174
+ if (
175
+ supported_ops
176
+ and isinstance(node, ast.Call)
177
+ and isinstance(node.func, ast.Attribute)
178
+ and isinstance(node.func.value, ast.Name)
179
+ and node.func.value.id in op_module_aliases
180
+ ):
181
+ fn_name = node.func.attr
182
+ if fn_name not in supported_ops and not fn_name.startswith("_"):
183
+ errors.append(
184
+ {
185
+ "kind": "unknown-operation",
186
+ "line": node.lineno,
187
+ "operation": fn_name,
188
+ "message": (
189
+ f"`{node.func.value.id}.{fn_name}` is not a supported "
190
+ "operation. Run comcheck_api.list_operations() for the "
191
+ "current set."
192
+ ),
193
+ }
194
+ )
195
+
196
+ return {"ok": not errors, "errors": errors}
197
+
198
+
199
+ def main() -> int:
200
+ if len(sys.argv) != 2:
201
+ print("usage: validate_code.py <path|->", file=sys.stderr)
202
+ return 2
203
+ code = _read_input(sys.argv[1])
204
+ result = validate(code)
205
+ print(json.dumps(result, indent=2))
206
+ return 0 if result["ok"] else 1
207
+
208
+
209
+ if __name__ == "__main__":
210
+ sys.exit(main())
@@ -0,0 +1 @@
1
+ from comcheck_api.api.api_services import COMCheckApiService
@@ -0,0 +1,273 @@
1
+ """COMCheck API service module for making HTTP requests to the COMCheck API."""
2
+
3
+ """Note: Service layer accepts raw data types (dicts) as inputs to stay simple and
4
+ HTTP-library-friendly, but returns validated Pydantic models to provide type safety
5
+ and catch API schema mismatches at the boundary."""
6
+
7
+ import logging
8
+ import os
9
+ from typing import Any, Dict, NoReturn, Optional
10
+
11
+ import httpx
12
+
13
+ from comcheck_api.exceptions import (
14
+ COMCheckHTTPError,
15
+ COMCheckConnectionError,
16
+ COMCheckValidationError,
17
+ )
18
+ from comcheck_api.types.api_types import (
19
+ RunSimulationResponse,
20
+ SimulationStatusResponse,
21
+ SimulationResultResponse,
22
+ )
23
+
24
+
25
+ class COMCheckApiService:
26
+ """Low-level HTTP service for the COMcheck Web API.
27
+
28
+ Handles authentication, request construction, response parsing, and
29
+ error mapping. Methods accept raw dicts as inputs and return either
30
+ raw dicts or validated Pydantic response models.
31
+
32
+ Can be used as a context manager::
33
+
34
+ with COMCheckApiService(api_key="key") as svc:
35
+ data = svc.get_project("123")
36
+ """
37
+
38
+ def __init__(self, api_key: str) -> None:
39
+ """Initialize COMCheck API service.
40
+
41
+ Args:
42
+ api_key: API key for authentication
43
+
44
+ Raises:
45
+ ValueError: If API key is not provided
46
+ """
47
+ if not api_key:
48
+ raise ValueError(
49
+ "COM_API_KEY is not set. Please provide it as a constructor parameter "
50
+ "or set it in your environment variables."
51
+ )
52
+ self.api_key = api_key
53
+ self.base_url: str = os.getenv(
54
+ "COMCHECK_API_URL", "https://comcheck.energycode.pnl.gov/checkweb-api/COM"
55
+ )
56
+ self._client: Optional[httpx.Client] = None
57
+
58
+ def _get_client(self) -> httpx.Client:
59
+ """Get or create HTTP client instance.
60
+
61
+ Returns:
62
+ Configured httpx.Client instance
63
+ """
64
+ if self._client is None:
65
+ self._client = httpx.Client(
66
+ base_url=self.base_url,
67
+ headers=self._prepare_headers(),
68
+ timeout=30.0,
69
+ )
70
+ return self._client
71
+
72
+ def _prepare_headers(self) -> Dict[str, str]:
73
+ """Prepare headers for API requests.
74
+
75
+ Returns:
76
+ HTTP headers dictionary
77
+ """
78
+ return {
79
+ "x-api-key": self.api_key,
80
+ "Content-Type": "application/json",
81
+ "Accept": "application/json",
82
+ }
83
+
84
+ def _handle_api_error(self, error: Exception) -> NoReturn:
85
+ """Handle API errors with detailed logging and raise custom exceptions.
86
+
87
+ Args:
88
+ error: The error object to handle
89
+
90
+ Raises:
91
+ COMCheckHTTPError: For HTTP status errors
92
+ COMCheckConnectionError: For connection/request errors
93
+ COMCheckValidationError: For validation errors
94
+ """
95
+ logger = logging.getLogger(__name__)
96
+
97
+ if isinstance(error, httpx.HTTPStatusError):
98
+ logger.error(
99
+ "HTTP error occurred: %s (Status: %s)",
100
+ error,
101
+ error.response.status_code,
102
+ exc_info=True,
103
+ extra={
104
+ "response_data": error.response.text,
105
+ "response_headers": dict(error.response.headers),
106
+ },
107
+ )
108
+ raise COMCheckHTTPError(
109
+ status_code=error.response.status_code,
110
+ message=error.response.reason_phrase,
111
+ response_data=error.response.text,
112
+ ) from error
113
+ elif isinstance(error, httpx.RequestError):
114
+ logger.error(
115
+ "Request error occurred: %s",
116
+ error,
117
+ exc_info=True,
118
+ extra={"request": str(error.request)},
119
+ )
120
+ raise COMCheckConnectionError(
121
+ f"Failed to connect to COMcheck API: {str(error)}"
122
+ ) from error
123
+ else:
124
+ logger.error("Unexpected error: %s", error, exc_info=True)
125
+ raise
126
+
127
+ def get_project(self, project_id: str) -> Dict[str, Any]:
128
+ """Get a single project by ID.
129
+
130
+ Args:
131
+ project_id: The project ID to retrieve
132
+
133
+ Returns:
134
+ API response data as dictionary
135
+
136
+ Raises:
137
+ COMCheckHTTPError: If the API returns an error status
138
+ COMCheckConnectionError: If the request fails
139
+ """
140
+ try:
141
+ client = self._get_client()
142
+ response = client.get(f"/project/{project_id}")
143
+ response.raise_for_status()
144
+ return response.json()
145
+ except Exception as error:
146
+ self._handle_api_error(error)
147
+
148
+ def get_project_list(self) -> Dict[str, Any]:
149
+ """Get a list of all projects.
150
+
151
+ Returns:
152
+ API response data as dictionary
153
+
154
+ Raises:
155
+ COMCheckHTTPError: If the API returns an error status
156
+ COMCheckConnectionError: If the request fails
157
+ """
158
+ try:
159
+ client = self._get_client()
160
+ response = client.get("/projects")
161
+ response.raise_for_status()
162
+ return response.json()
163
+ except Exception as error:
164
+ self._handle_api_error(error)
165
+
166
+ def update_project(
167
+ self, project_id: str, project_data: Dict[str, Any]
168
+ ) -> Dict[str, Any]:
169
+ """Update a project by ID.
170
+
171
+ Args:
172
+ project_id: The project ID to update
173
+ project_data: The project data to send in the request body
174
+
175
+ Returns:
176
+ API response data as dictionary
177
+
178
+ Raises:
179
+ COMCheckHTTPError: If the API returns an error status
180
+ COMCheckConnectionError: If the request fails
181
+ """
182
+ try:
183
+ client = self._get_client()
184
+ response = client.put(f"/project/{project_id}", json=project_data)
185
+ response.raise_for_status()
186
+ return response.json()
187
+ except Exception as error:
188
+ self._handle_api_error(error)
189
+
190
+ def start_run_simulation(
191
+ self, project_data: Dict[str, Any]
192
+ ) -> RunSimulationResponse:
193
+ """Start a simulation.
194
+
195
+ Args:
196
+ project_data: The project data to send in the request body
197
+
198
+ Returns:
199
+ RunSimulationResponse with session information
200
+
201
+ Raises:
202
+ COMCheckHTTPError: If the API returns an error status
203
+ COMCheckConnectionError: If the request fails
204
+ """
205
+ try:
206
+ client = self._get_client()
207
+ response = client.post(
208
+ f"/compliance/start-run-simulation", json=project_data
209
+ )
210
+ response.raise_for_status()
211
+ return RunSimulationResponse.model_validate(response.json())
212
+ except Exception as error:
213
+ self._handle_api_error(error)
214
+
215
+ def get_simulation_status(self, session_id: str) -> SimulationStatusResponse:
216
+ """Get status of a simulation.
217
+
218
+ Args:
219
+ session_id: The session ID of the simulation
220
+
221
+ Returns:
222
+ SimulationStatusResponse with status information
223
+
224
+ Raises:
225
+ COMCheckHTTPError: If the API returns an error status
226
+ COMCheckConnectionError: If the request fails
227
+ """
228
+ try:
229
+ client = self._get_client()
230
+ response = client.get(
231
+ f"/compliance/get-status-simulation?sessionId={session_id}"
232
+ )
233
+ response.raise_for_status()
234
+ return SimulationStatusResponse.model_validate(response.json())
235
+ except Exception as error:
236
+ self._handle_api_error(error)
237
+
238
+ def get_simulation_result(self, session_id: str) -> SimulationResultResponse:
239
+ """Get result of a simulation.
240
+
241
+ Args:
242
+ session_id: The session ID of the simulation
243
+
244
+ Returns:
245
+ SimulationResultResponse with simulation results
246
+
247
+ Raises:
248
+ COMCheckHTTPError: If the API returns an error status
249
+ COMCheckConnectionError: If the request fails
250
+ """
251
+ try:
252
+ client = self._get_client()
253
+ response = client.get(
254
+ f"/compliance/get-result-simulation?sessionId={session_id}"
255
+ )
256
+ response.raise_for_status()
257
+ return SimulationResultResponse.model_validate(response.json())
258
+ except Exception as error:
259
+ self._handle_api_error(error)
260
+
261
+ def close(self) -> None:
262
+ """Close the HTTP client connection."""
263
+ if self._client is not None:
264
+ self._client.close()
265
+ self._client = None
266
+
267
+ def __enter__(self) -> "COMCheckApiService":
268
+ """Context manager entry."""
269
+ return self
270
+
271
+ def __exit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None:
272
+ """Context manager exit."""
273
+ self.close()