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,250 @@
1
+ """Envelope constants for COMcheck projects."""
2
+
3
+ from uuid import uuid4
4
+
5
+ from comcheck_api.types.core_types import (
6
+ AgWall,
7
+ BgWall,
8
+ Door,
9
+ Envelope,
10
+ Floor,
11
+ Roof,
12
+ Skylight,
13
+ ThermalBridge,
14
+ Window,
15
+ )
16
+ from comcheck_api.utilities.common import get_random_number
17
+
18
+ DEFAULT_WINDOW: Window = Window.model_validate(
19
+ {
20
+ "adjacentSpaceBuildingType": "WHOLE_BUILDING_OFFICE",
21
+ "adjacentSpaceType": "ADJACENT_SPACE_EXTERIOR",
22
+ "allowanceType": "ENV_ALLOWANCE_NONE",
23
+ "altExemptType": None,
24
+ "assemblyType": "Window:Default Window",
25
+ "bldgUseKey": str(uuid4()),
26
+ "constructionType": "NON_RESIDENTIAL",
27
+ "description": "",
28
+ "exemptionType": "ENV_EXEMPTION_NONE",
29
+ "frameType": "METAL",
30
+ "glazingMaterialType": "GLASS_GLAZING_MAT",
31
+ "glazingType": "TRIPLE_PANE",
32
+ "grossArea": 300,
33
+ "isSiteShading": False,
34
+ "orientation": "NORTH",
35
+ "perfDataType": "PERF_TYPE_NFRC",
36
+ "preAltPropShgc": 0.25,
37
+ "preAltPropUval": 0.35,
38
+ "productId": "WIN-001",
39
+ "productType": "FACTORY_ASSEMBLED_WINDOW",
40
+ "propProjectionFactor": 0.5,
41
+ "propShgc": 0.25,
42
+ "propUValue": 0.35,
43
+ "propVt": 0.65,
44
+ "solarType": "CLEAR",
45
+ "windowOpenType": "NON_OPERABLE_WINDOW",
46
+ "cavityRValue": 0,
47
+ "continuousRValue": 0,
48
+ }
49
+ )
50
+
51
+ DEFAULT_DOOR: Door = Door.model_validate(
52
+ {
53
+ "adjacentSpaceBuildingType": None,
54
+ "adjacentSpaceType": None,
55
+ "allowanceType": None,
56
+ "altExemptType": None,
57
+ "assemblyType": "Door:Default Door",
58
+ "bldgUseKey": str(uuid4()),
59
+ "cavityRValue": 0,
60
+ "continuousRValue": 0,
61
+ "constructionType": None,
62
+ "description": None,
63
+ "doorEntranceType": "ENTRANCE_DOOR",
64
+ "doorOpenType": "SWINGING_DOOR",
65
+ "doorType": "INSUL_METAL_DOOR",
66
+ "exemptionType": None,
67
+ "frameType": None,
68
+ "glazingMaterialType": None,
69
+ "glazingType": None,
70
+ "grossArea": 23,
71
+ "isSiteShading": None,
72
+ "orientation": "UNSPECIFIED_ORIENTATION",
73
+ "perfDataType": None,
74
+ "preAltPropShgc": 0,
75
+ "preAltPropUval": None,
76
+ "productId": None,
77
+ "productType": None,
78
+ "propProjectionFactor": 0,
79
+ "propShgc": 0,
80
+ "propUValue": 0.37,
81
+ "propVt": None,
82
+ "solarType": None,
83
+ }
84
+ )
85
+
86
+ DEFAULT_SKYLIGHT: Skylight = Skylight.model_validate(
87
+ {
88
+ "adjacentSpaceType": None,
89
+ "allowanceType": None,
90
+ "assemblyType": "Skylight:Default Skylight",
91
+ "bldgUseKey": "",
92
+ "curbType": "NO_CURB_SKYLIGHT",
93
+ "description": "",
94
+ "exemptionType": None,
95
+ "frameType": "METAL",
96
+ "glazingMaterialType": "GLASS_GLAZING_MAT",
97
+ "glazingType": "DOUBLE_PANE_LOWE",
98
+ "grossArea": 100,
99
+ "isSiteShading": None,
100
+ "orientation": "UNSPECIFIED_ORIENTATION",
101
+ "perfDataType": None,
102
+ "preAltPropShgc": 0,
103
+ "productId": None,
104
+ "productType": None,
105
+ "propProjectionFactor": 0,
106
+ "propShgc": 0.6,
107
+ "propUValue": 0.55,
108
+ "propVt": None,
109
+ "solarType": "TINTED",
110
+ }
111
+ )
112
+
113
+ DEFAULT_ROOF: Roof = Roof.model_validate(
114
+ {
115
+ "adjacentSpaceType": None,
116
+ "allowanceType": None,
117
+ "assemblyType": "Roof:Default Roof",
118
+ "bldgUseKey": str(uuid4()),
119
+ "cavityRValue": 0,
120
+ "continuousRValue": 37,
121
+ "description": "",
122
+ "exemptionType": None,
123
+ "grossArea": 6000,
124
+ "highAlbedoRoofReqType": "HA_ROOF_EXEMPTION_VEGETATED",
125
+ "orientation": "UNSPECIFIED_ORIENTATION",
126
+ "propUValue": 0.26,
127
+ "purlinSpacing": 0,
128
+ "roofInsulType": None,
129
+ "roofType": "ABOVE_DECK_ROOF",
130
+ "skylight": [],
131
+ "solarReflectance": 0,
132
+ "solarReflectanceIndex": 0,
133
+ "thermalEmittance": 0,
134
+ }
135
+ )
136
+
137
+ DEFAULT_FLOOR: Floor = Floor.model_validate(
138
+ {
139
+ "adjacentSpaceType": None,
140
+ "allowanceType": None,
141
+ "altExemptType": None,
142
+ "assemblyType": "Floor:Default Floor",
143
+ "bldgUseKey": str(uuid4()),
144
+ "cavityRValue": 0,
145
+ "constructionType": None,
146
+ "continuousRValue": 15,
147
+ "depthOfInsulation": 4,
148
+ "description": "",
149
+ "exemptionType": None,
150
+ "floorExposedFrameType": None,
151
+ "floorType": "HEATED_SLAB_ON_GRADE",
152
+ "grossArea": 320,
153
+ "hasEdgeInsul": None,
154
+ "insulationPosition": "VERTICAL",
155
+ "orientation": "UNSPECIFIED_ORIENTATION",
156
+ "propUValue": 0.72,
157
+ "slabFullInsulBelowMinRValue": 0,
158
+ }
159
+ )
160
+
161
+ DEFAULT_AG_WALL: AgWall = AgWall.model_validate(
162
+ {
163
+ "adjacentSpaceType": None,
164
+ "agWallConstructionDetailsType": "NONE",
165
+ "agWallExteriorFinishDetailsType": None,
166
+ "allowanceType": None,
167
+ "assemblyType": "Ext Wall:Default Exterior Wall",
168
+ "bldgUseKey": str(uuid4()),
169
+ "cavityRValue": 20,
170
+ "cmuType": None,
171
+ "concreteDensity": 0,
172
+ "concreteThickness": 0,
173
+ "continuousRValue": 10,
174
+ "description": "",
175
+ "door": [],
176
+ "effectiveUFactor": 0.054,
177
+ "exemptionType": None,
178
+ "furringType": None,
179
+ "grossArea": 4800,
180
+ "heatCapacity": 0,
181
+ "insulationPosition": None,
182
+ "nextToUncondSpace": None,
183
+ "orientation": "NORTH",
184
+ "otherWallType": "NONE",
185
+ "propUValue": 0.048,
186
+ "thermalBridge": [],
187
+ "thermalBridgeAdjustmentFactor": None,
188
+ "thermalBridgeExceptionType": "THERMAL_BRIDGE_EXCEPTION_NONE",
189
+ "wallType": "METAL_FRAME_24_AG_WALL",
190
+ "window": [],
191
+ }
192
+ )
193
+
194
+ DEFAULT_THERMAL_BRIDGE: ThermalBridge = ThermalBridge.model_validate(
195
+ {
196
+ "chiFactor": 0,
197
+ "id": get_random_number(),
198
+ "numberOfPoints": 0,
199
+ "psiFactor": 0.177,
200
+ "thermalBridgeCategory": "THERMAL_BRIDGE_LINEAR",
201
+ "thermalBridgeComplianceType": "THERMAL_BRIDGE_PRESCRIPTIVE",
202
+ "thermalBridgeLength": 100,
203
+ "thermalBridgeType": "THERMAL_BRIDGE_FLOOR_TO_WALL_INTERSECTION",
204
+ }
205
+ )
206
+
207
+ DEFAULT_BG_WALL: BgWall = BgWall.model_validate(
208
+ {
209
+ "adjacentSpaceBuildingType": None,
210
+ "adjacentSpaceType": None,
211
+ "allowanceType": None,
212
+ "altExemptType": None,
213
+ "assemblyType": "Basement:Default Basement",
214
+ "bldgUseKey": "",
215
+ "cavityRValue": 20,
216
+ "cmuType": None,
217
+ "concreteDensity": 95, # TODO: Generated value
218
+ "concreteThickness": 6, # TODO: Generated value, type is not correct in coreTypes
219
+ "constructionType": None,
220
+ "continuousRValue": 10,
221
+ "description": "",
222
+ "door": [],
223
+ "exemptionType": None,
224
+ "furringType": "WOOD_FURRING",
225
+ "grossArea": 3000,
226
+ "heatCapacity": 0,
227
+ "insulationPosition": None,
228
+ "orientation": "NORTH",
229
+ "propUValue": 0.037,
230
+ "wallHeight": 9,
231
+ "wallHeightBelowGrade": 6,
232
+ "wallType": "CONCRETE_BG_WALL",
233
+ "window": [],
234
+ }
235
+ )
236
+
237
+ DEFAULT_ENVELOPE: Envelope = Envelope(
238
+ agWall=[], bgWall=[], roof=[], floor=[], door=[], window=[], skylight=[]
239
+ )
240
+
241
+ # TODO: add values to other assemblies
242
+ DEFAULT_ASSEMBLIES = {
243
+ "Window": DEFAULT_WINDOW,
244
+ "Roof": DEFAULT_ROOF,
245
+ "Skylight": DEFAULT_SKYLIGHT,
246
+ "AgWall": DEFAULT_AG_WALL,
247
+ "BgWall": DEFAULT_BG_WALL,
248
+ "Door": DEFAULT_DOOR,
249
+ "Floor": DEFAULT_FLOOR,
250
+ }
@@ -0,0 +1,150 @@
1
+ """Module for project default templates/values."""
2
+
3
+ import copy
4
+
5
+ from comcheck_api.constants.building_area_constants import DEFAULT_BUILDING_AREA
6
+ from comcheck_api.constants.envelope_constants import (
7
+ DEFAULT_AG_WALL,
8
+ DEFAULT_BG_WALL,
9
+ DEFAULT_DOOR,
10
+ DEFAULT_FLOOR,
11
+ DEFAULT_ROOF,
12
+ DEFAULT_SKYLIGHT,
13
+ DEFAULT_THERMAL_BRIDGE,
14
+ DEFAULT_WINDOW,
15
+ )
16
+
17
+ from comcheck_api.constants.common_constants import PROJECT_TEMPLATE
18
+
19
+ # from comcheck_api.constants.lighting_constants import DEFAULT_FIXTURE_SCHEDULE
20
+
21
+
22
+ def get_default_project_template():
23
+ """Return a deep copy of the default :class:`~comcheck_api.types.core_types.ComBuilding` template.
24
+
25
+ The template is pre-populated with sensible defaults for location
26
+ (Boulder, CO), envelope, lighting, HVAC, and other required sections.
27
+
28
+ Returns:
29
+ A new ``ComBuilding`` instance ready to be customized.
30
+ """
31
+ return copy.deepcopy(PROJECT_TEMPLATE)
32
+
33
+
34
+ def get_default_building_area_template():
35
+ """Return a deep copy of the default :class:`~comcheck_api.types.core_types.WholeBldgUse` template.
36
+
37
+ Defaults to an *Automotive Facility* with 1 000 sq ft floor area
38
+ and interior lighting space initialized.
39
+
40
+ Returns:
41
+ A new ``WholeBldgUse`` instance.
42
+ """
43
+ return copy.deepcopy(DEFAULT_BUILDING_AREA)
44
+
45
+
46
+ def get_default_roof_template():
47
+ """Return a deep copy of the default :class:`~comcheck_api.types.core_types.Roof` template.
48
+
49
+ Defaults to an above-deck roof with 6 000 sq ft gross area and
50
+ R-37 continuous insulation.
51
+
52
+ Returns:
53
+ A new ``Roof`` instance.
54
+ """
55
+ return copy.deepcopy(DEFAULT_ROOF)
56
+
57
+
58
+ def get_default_ag_wall_template():
59
+ """Return a deep copy of the default :class:`~comcheck_api.types.core_types.AgWall` template.
60
+
61
+ Defaults to a metal-frame 24-inch above-grade wall with 4 800 sq ft
62
+ gross area, R-20 cavity / R-10 continuous insulation.
63
+
64
+ Returns:
65
+ A new ``AgWall`` instance.
66
+ """
67
+ return copy.deepcopy(DEFAULT_AG_WALL)
68
+
69
+
70
+ def get_default_floor_template():
71
+ """Return a deep copy of the default :class:`~comcheck_api.types.core_types.Floor` template.
72
+
73
+ Defaults to a heated slab-on-grade floor with 320 sq ft gross area
74
+ and R-15 continuous insulation.
75
+
76
+ Returns:
77
+ A new ``Floor`` instance.
78
+ """
79
+ return copy.deepcopy(DEFAULT_FLOOR)
80
+
81
+
82
+ def get_default_bg_wall_template():
83
+ """Return a deep copy of the default :class:`~comcheck_api.types.core_types.BgWall` template.
84
+
85
+ Defaults to a concrete below-grade wall with 3 000 sq ft gross area,
86
+ R-20 cavity / R-10 continuous insulation, and wood furring.
87
+
88
+ Returns:
89
+ A new ``BgWall`` instance.
90
+ """
91
+ return copy.deepcopy(DEFAULT_BG_WALL)
92
+
93
+
94
+ def get_default_skylight_template():
95
+ """Return a deep copy of the default :class:`~comcheck_api.types.core_types.Skylight` template.
96
+
97
+ Defaults to a no-curb, double-pane low-E skylight with 100 sq ft
98
+ gross area.
99
+
100
+ Returns:
101
+ A new ``Skylight`` instance.
102
+ """
103
+ return copy.deepcopy(DEFAULT_SKYLIGHT)
104
+
105
+
106
+ def get_default_window_template():
107
+ """Return a deep copy of the default :class:`~comcheck_api.types.core_types.Window` template.
108
+
109
+ Defaults to a triple-pane, non-operable, metal-frame window with
110
+ 300 sq ft gross area and NFRC performance data.
111
+
112
+ Returns:
113
+ A new ``Window`` instance.
114
+ """
115
+ return copy.deepcopy(DEFAULT_WINDOW)
116
+
117
+
118
+ def get_default_door_template():
119
+ """Return a deep copy of the default :class:`~comcheck_api.types.core_types.Door` template.
120
+
121
+ Defaults to a swinging, insulated metal entrance door with 23 sq ft
122
+ gross area.
123
+
124
+ Returns:
125
+ A new ``Door`` instance.
126
+ """
127
+ return copy.deepcopy(DEFAULT_DOOR)
128
+
129
+
130
+ def get_default_thermal_bridge_template():
131
+ """Return a deep copy of the default :class:`~comcheck_api.types.core_types.ThermalBridge` template.
132
+
133
+ Defaults to a prescriptive linear floor-to-wall intersection thermal
134
+ bridge with a psi factor of 0.177 and 100 ft length.
135
+
136
+ Returns:
137
+ A new ``ThermalBridge`` instance.
138
+ """
139
+ return copy.deepcopy(DEFAULT_THERMAL_BRIDGE)
140
+
141
+
142
+ def get_default_fixture_schedule_template():
143
+ """Return a default fixture schedule template with a unique key.
144
+
145
+ .. warning::
146
+ Not yet implemented — raises :exc:`NotImplementedError`.
147
+ """
148
+ # TODO: Implement when DEFAULT_FIXTURE_SCHEDULE constant is available
149
+ # return {**DEFAULT_FIXTURE_SCHEDULE, "scheduleFixtureKey": str(uuid4())}
150
+ raise NotImplementedError("DEFAULT_FIXTURE_SCHEDULE constant not yet implemented")
@@ -0,0 +1,54 @@
1
+ """Custom exceptions for COMcheck API client."""
2
+
3
+
4
+ class COMCheckAPIError(Exception):
5
+ """Base exception for COMcheck API errors."""
6
+
7
+ pass
8
+
9
+
10
+ class COMCheckHTTPError(COMCheckAPIError):
11
+ """HTTP request failed."""
12
+
13
+ def __init__(self, status_code: int, message: str, response_data: str = ""):
14
+ """Initialize with the HTTP status code, message, and optional response body.
15
+
16
+ Args:
17
+ status_code: The HTTP status code returned by the API.
18
+ message: Human-readable error description.
19
+ response_data: Raw response body text (empty string if unavailable).
20
+ """
21
+ self.status_code = status_code
22
+ self.response_data = response_data
23
+ super().__init__(f"HTTP {status_code}: {message}")
24
+
25
+
26
+ class COMCheckValidationError(COMCheckAPIError):
27
+ """Raised when request or response data fails Pydantic or schema validation."""
28
+
29
+ pass
30
+
31
+
32
+ class COMCheckConnectionError(COMCheckAPIError):
33
+ """Raised when the HTTP client cannot reach the COMcheck API server."""
34
+
35
+ pass
36
+
37
+
38
+ class COMCheckSimulationError(COMCheckAPIError):
39
+ """Raised when a simulation request fails or returns unexpected data."""
40
+
41
+ pass
42
+
43
+
44
+ class COMCheckProjectNotFoundError(COMCheckAPIError):
45
+ """Project not found."""
46
+
47
+ def __init__(self, project_id: str):
48
+ """Initialize with the ID of the project that was not found.
49
+
50
+ Args:
51
+ project_id: The project ID that could not be located.
52
+ """
53
+ self.project_id = project_id
54
+ super().__init__(f"Project with ID '{project_id}' not found")
@@ -0,0 +1,188 @@
1
+ """Introspection helpers over the installed SDK.
2
+
3
+ Discoverable from the package root:
4
+
5
+ >>> from comcheck_api import list_operations, lookup_type
6
+ >>> ops = list_operations()
7
+ >>> schema = lookup_type("ComBuilding")
8
+
9
+ ``list_operations`` enumerates the public functions in the project
10
+ operation modules. ``lookup_type`` reflects a Pydantic model or enum
11
+ from :mod:`comcheck_api.types`. Both return Pydantic models with
12
+ typed fields so IDEs and type checkers can see the shape.
13
+ """
14
+
15
+ from __future__ import annotations
16
+
17
+ import inspect
18
+ from typing import Any, Literal, get_args, get_origin
19
+
20
+ import pydantic
21
+ from pydantic import BaseModel, ConfigDict
22
+
23
+ from comcheck_api import (
24
+ project_building_area_operations,
25
+ project_envelope_operations,
26
+ )
27
+
28
+ _OP_MODULES = {
29
+ "building_area": project_building_area_operations,
30
+ "envelope": project_envelope_operations,
31
+ }
32
+
33
+
34
+ class OperationInfo(BaseModel):
35
+ """One public function in a project-operation module."""
36
+
37
+ group: str
38
+ module: str
39
+ name: str
40
+ signature: str
41
+ summary: str
42
+
43
+
44
+ class FieldSchema(BaseModel):
45
+ """One field of a Pydantic model."""
46
+
47
+ model_config = ConfigDict(arbitrary_types_allowed=True)
48
+
49
+ name: str
50
+ type: str
51
+ required: bool
52
+ default: Any | None
53
+ description: str
54
+
55
+
56
+ class EnumMember(BaseModel):
57
+ name: str
58
+ value: str
59
+
60
+
61
+ class TypeSchema(BaseModel):
62
+ """Reflected schema of a Pydantic model or StrEnum."""
63
+
64
+ name: str
65
+ kind: Literal["model", "enum"]
66
+ doc: str
67
+ fields: list[FieldSchema] = []
68
+ members: list[EnumMember] = []
69
+
70
+
71
+ def list_operations() -> list[OperationInfo]:
72
+ """Discover public functions in the project operation modules.
73
+
74
+ Discovered live via :mod:`inspect` so the list always matches the
75
+ installed SDK version.
76
+ """
77
+ out: list[OperationInfo] = []
78
+ for group, mod in _OP_MODULES.items():
79
+ for name, fn in inspect.getmembers(mod, inspect.isfunction):
80
+ if name.startswith("_"):
81
+ continue
82
+ if fn.__module__ != mod.__name__:
83
+ continue # skip re-exports
84
+ doc = inspect.getdoc(fn) or ""
85
+ summary = doc.split("\n", 1)[0]
86
+ out.append(
87
+ OperationInfo(
88
+ group=group,
89
+ module=fn.__module__,
90
+ name=name,
91
+ signature=f"{name}{inspect.signature(fn)}",
92
+ summary=summary,
93
+ )
94
+ )
95
+ return out
96
+
97
+
98
+ def lookup_type(name: str) -> TypeSchema | None:
99
+ """Reflect a Pydantic model or enum from :mod:`comcheck_api.types`.
100
+
101
+ Returns ``None`` if no matching type is found. Lookup is
102
+ case-insensitive as a fallback.
103
+ """
104
+ from comcheck_api import types as cc_types
105
+
106
+ obj = getattr(cc_types, name, None)
107
+ if obj is None:
108
+ lower = name.lower()
109
+ for cand in dir(cc_types):
110
+ if cand.lower() == lower:
111
+ obj = getattr(cc_types, cand)
112
+ name = cand
113
+ break
114
+
115
+ if obj is None:
116
+ return None
117
+
118
+ if isinstance(obj, type) and issubclass(obj, pydantic.BaseModel):
119
+ return _describe_model(name, obj)
120
+
121
+ if isinstance(obj, type):
122
+ members = [
123
+ EnumMember(name=member.name, value=str(member.value))
124
+ for member in getattr(obj, "__members__", {}).values()
125
+ ]
126
+ if members:
127
+ return TypeSchema(
128
+ name=name,
129
+ kind="enum",
130
+ doc=inspect.getdoc(obj) or "",
131
+ members=members,
132
+ )
133
+
134
+ return None
135
+
136
+
137
+ def _describe_model(name: str, model: type[pydantic.BaseModel]) -> TypeSchema:
138
+ fields = [
139
+ FieldSchema(
140
+ name=fname,
141
+ type=_render_type(finfo.annotation),
142
+ required=finfo.is_required(),
143
+ default=_render_default(finfo),
144
+ description=finfo.description or "",
145
+ )
146
+ for fname, finfo in model.model_fields.items()
147
+ ]
148
+ return TypeSchema(
149
+ name=name,
150
+ kind="model",
151
+ doc=inspect.getdoc(model) or "",
152
+ fields=fields,
153
+ )
154
+
155
+
156
+ def _render_type(annotation: Any) -> str:
157
+ if annotation is None or annotation is type(None):
158
+ return "None"
159
+ origin = get_origin(annotation)
160
+ if origin is None:
161
+ return getattr(annotation, "__name__", str(annotation))
162
+ args = ", ".join(_render_type(a) for a in get_args(annotation))
163
+ origin_name = getattr(origin, "__name__", str(origin))
164
+ return f"{origin_name}[{args}]"
165
+
166
+
167
+ def _render_default(finfo: pydantic.fields.FieldInfo) -> Any:
168
+ if finfo.is_required():
169
+ return None
170
+ default = finfo.default
171
+ if default is pydantic.fields.PydanticUndefined:
172
+ return None
173
+ try:
174
+ if isinstance(default, pydantic.BaseModel):
175
+ return default.model_dump(mode="json")
176
+ return default
177
+ except Exception: # noqa: BLE001
178
+ return repr(default)
179
+
180
+
181
+ __all__ = [
182
+ "OperationInfo",
183
+ "FieldSchema",
184
+ "EnumMember",
185
+ "TypeSchema",
186
+ "list_operations",
187
+ "lookup_type",
188
+ ]
File without changes
File without changes
@@ -0,0 +1,11 @@
1
+ """Building area list manager for COMcheck projects."""
2
+
3
+ from typing import Any
4
+
5
+ from comcheck_api.types.core_types import WholeBldgUse
6
+ from comcheck_api.managers.data_manager import DataManager
7
+
8
+
9
+ class BuildingAreaListManager(DataManager[WholeBldgUse]):
10
+ """Manager for WholeBldgUse (building area) items."""
11
+ model_type = WholeBldgUse
File without changes