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
comcheck_api/cli.py ADDED
@@ -0,0 +1,136 @@
1
+ """``comcheck-api`` console-script entry point.
2
+
3
+ Subcommands:
4
+
5
+ * ``install-skill`` — install the bundled Skill so an AI coding agent
6
+ picks it up. The Skill follows the open agent-skills standard
7
+ (``SKILL.md`` + ``reference/`` + ``scripts/``), so the same folder
8
+ works for both Claude Code and OpenAI Codex; only the install
9
+ location differs:
10
+
11
+ - Claude Code → ``.claude/skills/comcheck-api/`` (or
12
+ ``~/.claude/skills/`` with ``--global``).
13
+ - OpenAI Codex → ``.agents/skills/comcheck-api/`` (or
14
+ ``~/.agents/skills/`` with ``--global``). Codex scans
15
+ ``.agents/skills`` from the working directory up to the repo root.
16
+
17
+ By default both are installed. Pass ``--claude`` to install only the
18
+ Claude Code Skill, or ``--codex`` to install only the Codex Skill.
19
+ """
20
+
21
+ from __future__ import annotations
22
+
23
+ import argparse
24
+ import shutil
25
+ import sys
26
+ from pathlib import Path
27
+
28
+ from comcheck_api import ai
29
+
30
+ # (agent label, skills dir) for each supported target.
31
+ _TARGETS = {
32
+ "claude": ("Claude", ".claude"),
33
+ "codex": ("Codex", ".agents"),
34
+ }
35
+
36
+
37
+ def _install_one(
38
+ src: Path, base_dir: Path, agent_dir: str, force: bool, dry_run: bool
39
+ ) -> tuple[bool, Path]:
40
+ """Install the Skill for one agent. Returns (installed, target)."""
41
+ target_root = base_dir / agent_dir / "skills" / "comcheck-api"
42
+
43
+ if dry_run:
44
+ print(f"[dry-run] would copy {src} → {target_root}")
45
+ return True, target_root
46
+
47
+ if target_root.exists() and not force:
48
+ print(f"{target_root} already exists. Re-run with --force to overwrite.")
49
+ return False, target_root
50
+
51
+ if target_root.exists():
52
+ shutil.rmtree(target_root)
53
+ target_root.parent.mkdir(parents=True, exist_ok=True)
54
+ shutil.copytree(src, target_root)
55
+ print(f"Installed Skill into {target_root}")
56
+ return True, target_root
57
+
58
+
59
+ def _cmd_install_skill(args: argparse.Namespace) -> int:
60
+ if args.global_install:
61
+ base_dir = Path.home()
62
+ else:
63
+ base_dir = Path(args.path).resolve()
64
+ if not base_dir.is_dir():
65
+ print(f"ERROR: {base_dir} is not a directory.")
66
+ return 1
67
+
68
+ # No agent flag → install both. Otherwise install only the
69
+ # selected agents.
70
+ if args.claude or args.codex:
71
+ agents = [k for k in _TARGETS if getattr(args, k)]
72
+ else:
73
+ agents = list(_TARGETS)
74
+
75
+ src = ai.skill_root()
76
+ any_installed = False
77
+ for key in agents:
78
+ label, agent_dir = _TARGETS[key]
79
+ installed, _ = _install_one(src, base_dir, agent_dir, args.force, args.dry_run)
80
+ if installed and not args.dry_run:
81
+ print(f"Restart {label} to pick up the new Skill.")
82
+ any_installed = any_installed or installed
83
+
84
+ return 0 if any_installed else 1
85
+
86
+
87
+ def build_parser() -> argparse.ArgumentParser:
88
+ common = argparse.ArgumentParser(add_help=False)
89
+ common.add_argument(
90
+ "--dry-run", action="store_true", help="Show actions without applying"
91
+ )
92
+ common.add_argument(
93
+ "--force", action="store_true", help="Overwrite existing files/configs"
94
+ )
95
+
96
+ parser = argparse.ArgumentParser(prog="comcheck-api")
97
+ sub = parser.add_subparsers(dest="cmd", required=True)
98
+
99
+ p_skill = sub.add_parser(
100
+ "install-skill",
101
+ parents=[common],
102
+ help="Install the Skill for Claude Code and/or Codex (both by default)",
103
+ )
104
+ p_skill.add_argument(
105
+ "path", nargs="?", default=".", help="Project directory (default: cwd)"
106
+ )
107
+ p_skill.add_argument(
108
+ "--global",
109
+ dest="global_install",
110
+ action="store_true",
111
+ help="Install into the user-global skills dir (~/.claude/skills/ "
112
+ "and/or ~/.agents/skills/) instead of project-level",
113
+ )
114
+ p_skill.add_argument(
115
+ "--claude",
116
+ action="store_true",
117
+ help="Install only the Claude Code Skill (.claude/skills/)",
118
+ )
119
+ p_skill.add_argument(
120
+ "--codex",
121
+ action="store_true",
122
+ help="Install only the Codex Skill (.agents/skills/)",
123
+ )
124
+ p_skill.set_defaults(func=_cmd_install_skill)
125
+
126
+ return parser
127
+
128
+
129
+ def main(argv: list[str] | None = None) -> int:
130
+ parser = build_parser()
131
+ args = parser.parse_args(argv)
132
+ return args.func(args)
133
+
134
+
135
+ if __name__ == "__main__": # pragma: no cover
136
+ sys.exit(main())
@@ -0,0 +1 @@
1
+ from comcheck_api.client.comcheck_client import COMcheckClient
@@ -0,0 +1,335 @@
1
+ """COMcheck Client module for simplified API interactions."""
2
+
3
+ """Note: Client layer provides user-friendly methods that accept Pydantic models as inputs
4
+ and return either Pydantic models, primitives, or raw dicts depending on the operation."""
5
+
6
+ import logging
7
+ from typing import Any, Dict, List, Literal, Optional, Union, overload
8
+
9
+ from comcheck_api.api import COMCheckApiService
10
+ from comcheck_api.constants.building_area_constants import DEFAULT_BUILDING_AREA
11
+ from comcheck_api.exceptions import (
12
+ COMCheckProjectNotFoundError,
13
+ COMCheckSimulationError,
14
+ )
15
+ from comcheck_api.types.core_types import ComBuilding, InteriorLightingSpace
16
+
17
+ Mode = Literal["python", "json"]
18
+
19
+
20
+ class COMcheckClient:
21
+ """High-level client for the COMcheck Web API.
22
+
23
+ Provides user-friendly methods that accept Pydantic models as inputs
24
+ and return either :class:`~comcheck_api.types.core_types.ComBuilding`
25
+ models, primitives, or raw dicts depending on the operation.
26
+
27
+ Can be used as a context manager to ensure the underlying HTTP
28
+ connection is closed::
29
+
30
+ with COMcheckClient(api_key="your-key") as client:
31
+ project = client.get_project("123")
32
+
33
+ Example:
34
+ ```python
35
+ client = COMcheckClient(api_key="your-key")
36
+ projects = client.list_projects()
37
+ project = client.get_project(projects[0]["projectId"])
38
+ client.close()
39
+ ```
40
+ """
41
+
42
+ def __init__(self, api_key: Optional[str] = None) -> None:
43
+ """Initialize COMcheck client.
44
+
45
+ Args:
46
+ api_key: Optional API key for authentication. Can be set later with set_api_key()
47
+ """
48
+ self._api_service: Optional[COMCheckApiService] = None
49
+ if api_key:
50
+ self.set_api_key(api_key)
51
+
52
+ def set_api_key(self, api_key: str) -> None:
53
+ """Set the API key and initialize the API service.
54
+
55
+ Args:
56
+ api_key: API key for authentication
57
+
58
+ Raises:
59
+ ValueError: If API key is not provided
60
+ """
61
+ if not api_key:
62
+ raise ValueError("API key is required.")
63
+ self._api_service = COMCheckApiService(api_key)
64
+
65
+ @property
66
+ def _service(self) -> COMCheckApiService:
67
+ """Get the API service instance.
68
+
69
+ Returns:
70
+ COMCheckApiService instance
71
+
72
+ Raises:
73
+ RuntimeError: If API key has not been set
74
+ """
75
+ if self._api_service is None:
76
+ raise RuntimeError(
77
+ "API key not set. Call set_api_key() before using the client."
78
+ )
79
+ return self._api_service
80
+
81
+ @overload
82
+ def get_project(
83
+ self,
84
+ project_id: str,
85
+ mode: Literal["python"] = "python",
86
+ ) -> Optional["ComBuilding"]: ...
87
+
88
+ @overload
89
+ def get_project(
90
+ self,
91
+ project_id: str,
92
+ mode: Literal["json"],
93
+ ) -> Optional[Dict[str, Any]]: ...
94
+
95
+ def get_project(
96
+ self,
97
+ project_id: str,
98
+ mode: Literal["python", "json"] = "python",
99
+ ) -> Optional[Union["ComBuilding", Dict[str, Any]]]:
100
+ """Fetch a single project by ID.
101
+
102
+ Args:
103
+ project_id: The project ID to retrieve.
104
+ mode: ``"python"`` returns a ``ComBuilding`` model; ``"json"`` returns a raw dict.
105
+
106
+ Returns:
107
+ The project as a ``ComBuilding`` or dict, or ``None`` if not found.
108
+ """
109
+ resp = self._service.get_project(project_id)
110
+ data = resp.get("data")
111
+ if data is not None:
112
+ for building_area in data["lighting"]["wholeBldgUse"]:
113
+ building_area["interiorLightingSpace"] = {
114
+ **DEFAULT_BUILDING_AREA.interiorLightingSpace.model_dump(
115
+ mode="json", exclude_unset=True
116
+ )
117
+ }
118
+
119
+ return self._parse_data(data, mode)
120
+
121
+ def list_projects(self) -> List[Dict[str, Any]]:
122
+ """Get a list of all projects.
123
+
124
+ Returns:
125
+ API response data as list of project dictionaries
126
+ """
127
+ return self._service.get_project_list().get("data", [])
128
+
129
+ # TODO: return of update_project should be ComBuilding
130
+ def update_project(
131
+ self,
132
+ project_id: str,
133
+ project_data: ComBuilding,
134
+ mode: Literal["python", "json"] = "python",
135
+ ) -> ComBuilding | dict[str, Any] | None:
136
+ """Update a project by ID.
137
+
138
+ The existing project is fetched first to preserve server-assigned IDs
139
+ and the ``userProject`` reference. Envelope component IDs that are
140
+ ``None`` (auto-generated by Pydantic) are stripped before submission.
141
+
142
+ Args:
143
+ project_id: The project ID to update.
144
+ project_data: A ``ComBuilding`` model with the desired state.
145
+ mode: ``"python"`` returns a ``ComBuilding`` model;
146
+ ``"json"`` returns a raw dict.
147
+
148
+ Returns:
149
+ The refreshed project after the update, in the requested format.
150
+
151
+ Raises:
152
+ COMCheckProjectNotFoundError: If the project does not exist.
153
+ """
154
+ # Get existing project
155
+ old_project = self.get_project(project_id, mode="json")
156
+
157
+ if not old_project:
158
+ raise COMCheckProjectNotFoundError(project_id)
159
+
160
+ project_data_json = project_data.model_dump(mode="json", exclude_unset=True)
161
+
162
+ # Preserve user project reference
163
+ user_project = old_project["userProject"]
164
+ project_data_json["userProject"] = user_project
165
+
166
+ # Preserve IDs from existing project
167
+ for section in [
168
+ "envelope",
169
+ "lighting",
170
+ "hvac",
171
+ "control",
172
+ "project",
173
+ "location",
174
+ "renewable",
175
+ ]:
176
+ project_data_json[section]["id"] = old_project[section]["id"]
177
+ project_data_json["id"] = old_project["id"]
178
+
179
+ # Ensure each building area has interiorLightingSpace initialized
180
+ for building_area in project_data_json["lighting"]["wholeBldgUse"]:
181
+ building_area["interiorLightingSpace"] = {
182
+ **DEFAULT_BUILDING_AREA.interiorLightingSpace.model_dump(
183
+ mode="json", exclude_unset=True
184
+ )
185
+ }
186
+
187
+ # TODO: need to verify if other componets also need to remove None IDs (auto-generated through pydantic )
188
+ # Remove None IDs from envelope components
189
+ if "envelope" in project_data_json:
190
+ envelope = project_data_json["envelope"]
191
+ for component_type in [
192
+ "roof",
193
+ "agWall",
194
+ "bgWall",
195
+ "floor",
196
+ "skylight",
197
+ "window",
198
+ "door",
199
+ ]:
200
+ if component_type in envelope and isinstance(
201
+ envelope[component_type], list
202
+ ):
203
+ for component in envelope[component_type]:
204
+ if isinstance(component, dict) and component.get("id") is None:
205
+ component.pop("id", None)
206
+ # Also remove None IDs from nested components (skylights in roofs, windows/doors in walls)
207
+ if component_type == "roof" and "skylight" in component:
208
+ for skylight in component.get("skylight", []):
209
+ if (
210
+ isinstance(skylight, dict)
211
+ and skylight.get("id") is None
212
+ ):
213
+ skylight.pop("id", None)
214
+ elif component_type in ["agWall", "bgWall"]:
215
+ for nested_type in ["window", "door", "thermalBridge"]:
216
+ if nested_type in component:
217
+ for nested in component.get(nested_type, []):
218
+ if (
219
+ isinstance(nested, dict)
220
+ and nested.get("id") is None
221
+ ):
222
+ nested.pop("id", None)
223
+
224
+ self._service.update_project(project_id, project_data_json).get("data")
225
+
226
+ # It's necessary that we call return get_project instead of what's returned from
227
+ # the service's update project due to interiorLightingSpace not being updated correctly
228
+ # when returned from the update call
229
+ return self.get_project(project_id=project_id, mode=mode)
230
+
231
+ def _parse_data(self, data, mode):
232
+ """Convert raw API data to the requested output format.
233
+
234
+ Args:
235
+ data: Raw dict from the API response, or ``None``.
236
+ mode: ``"python"`` wraps data in a ``ComBuilding``; ``"json"`` returns it as-is.
237
+
238
+ Returns:
239
+ A ``ComBuilding``, a raw dict, or ``None`` if ``data`` is ``None``.
240
+ """
241
+ if data is None:
242
+ return None
243
+ if mode == "python":
244
+ return ComBuilding(**data)
245
+ return data
246
+
247
+ def start_run_simulation(
248
+ self, project: ComBuilding, project_id: Optional[int] = None
249
+ ) -> str:
250
+ """Start a compliance simulation run for a project.
251
+
252
+ If *project_id* is supplied the project is persisted via
253
+ :meth:`update_project` before the simulation is launched.
254
+
255
+ Args:
256
+ project: The project data to simulate.
257
+ project_id: Optional server-side project ID. When given, the
258
+ project is saved before the simulation starts.
259
+
260
+ Returns:
261
+ The simulation session ID that can be passed to
262
+ :meth:`get_simulation_status` and :meth:`get_simulation_result`.
263
+
264
+ Raises:
265
+ COMCheckSimulationError: If the API returns no session data.
266
+ """
267
+ logger = logging.getLogger(__name__)
268
+
269
+ if project_id:
270
+ logger.info("Updating project: %s", project_id)
271
+ self.update_project(str(project_id), project)
272
+
273
+ project_data = project.model_dump(mode="json", exclude_unset=True)
274
+ run_result = self._service.start_run_simulation(project_data)
275
+ if run_result.data is None:
276
+ raise COMCheckSimulationError(
277
+ "Simulation start failed: no session data returned"
278
+ )
279
+ return run_result.data.sessionId
280
+
281
+ def get_simulation_status(self, session_id: str) -> Dict[str, Any]:
282
+ """Get the status of a running or completed simulation.
283
+
284
+ Args:
285
+ session_id: The session ID returned by :meth:`start_run_simulation`.
286
+
287
+ Returns:
288
+ A dict containing ``sessionId``, ``status``, and optional
289
+ ``message``. ``status`` is one of the values defined in
290
+ :class:`comcheck_api.types.SimulationStatus`. Terminal
291
+ values are ``"SUCCESS"`` and ``"FAILED"``.
292
+
293
+ Raises:
294
+ COMCheckSimulationError: If the status cannot be retrieved.
295
+ """
296
+ status_response = self._service.get_simulation_status(session_id)
297
+ if status_response.data is None:
298
+ raise COMCheckSimulationError(
299
+ f"Failed to get simulation status for session {session_id}"
300
+ )
301
+ return status_response.data.model_dump(mode="json")
302
+
303
+ def get_simulation_result(self, session_id: str) -> Dict[str, Any]:
304
+ """Get the result metrics of a completed simulation.
305
+
306
+ Args:
307
+ session_id: The session ID returned by :meth:`start_run_simulation`.
308
+
309
+ Returns:
310
+ A dict containing ``sessionId``, ``performanceRating``,
311
+ ``energyCreditPerformanceRating``, ``proposedBpf``, and
312
+ ``baselineBpf``.
313
+
314
+ Raises:
315
+ COMCheckSimulationError: If the result cannot be retrieved.
316
+ """
317
+ result_response = self._service.get_simulation_result(session_id)
318
+ if result_response.data is None:
319
+ raise COMCheckSimulationError(
320
+ f"Failed to get simulation result for session {session_id}"
321
+ )
322
+ return result_response.data.model_dump(mode="python")
323
+
324
+ def close(self) -> None:
325
+ """Close the API service connection."""
326
+ if self._api_service is not None:
327
+ self._api_service.close()
328
+
329
+ def __enter__(self) -> "COMcheckClient":
330
+ """Context manager entry."""
331
+ return self
332
+
333
+ def __exit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None:
334
+ """Context manager exit."""
335
+ self.close()
File without changes
@@ -0,0 +1,35 @@
1
+ """Building area constants for COMcheck projects."""
2
+
3
+ from uuid import uuid4
4
+
5
+ from comcheck_api.types.core_types import WholeBldgUse
6
+
7
+ # Default building area structure with interior lighting space
8
+ DEFAULT_BUILDING_AREA: WholeBldgUse = WholeBldgUse.model_validate(
9
+ {
10
+ "key": str(uuid4()),
11
+ "wholeBldgType": "WHOLE_BUILDING_AUTOMOTIVE", # TODO: wholeBldgType is a list of enum, need to define the enum type
12
+ "areaDescription": "Automotive Facility",
13
+ "constructionType": "NON_RESIDENTIAL",
14
+ "floorArea": 1000,
15
+ "internalLoad": 0,
16
+ "powerDensity": 0.71, # TODO: power density is automatically calculated based on wholeBldgType
17
+ "isTenantSpace": False, # TODO: space conditioning is a list of enum, need to define the enum type
18
+ "activityUse": [],
19
+ "interiorLightingSpace": {
20
+ "altExemptType": "EXEMPT_NOT_SET",
21
+ "description": "",
22
+ "numFixturesAlteredOrAdded": 0,
23
+ "postAltTotalWattage": 0,
24
+ "preAltNumberFixtures": 0,
25
+ "preAltTotalWattage": 0,
26
+ "allowanceType": None,
27
+ "exemptionType": None,
28
+ "allowanceFloorArea": 0,
29
+ "rcrFloorToWorkplaneHeight": 0,
30
+ "rcrPerimeter": 0,
31
+ "rcrWorkplaneToLuminaireHeight": 0,
32
+ "fixture": [],
33
+ }, # redundant data, but need it for backend call
34
+ }
35
+ )
@@ -0,0 +1,116 @@
1
+ """Default project template used to bootstrap new COMcheck projects.
2
+
3
+ The :data:`PROJECT_TEMPLATE` constant is a fully validated :class:`ComBuilding`
4
+ instance pre-populated with sensible defaults for all required sections
5
+ (envelope, lighting, HVAC, location, etc.).
6
+ """
7
+
8
+ from comcheck_api.types.core_types import ComBuilding
9
+
10
+ PROJECT_TEMPLATE = ComBuilding.model_validate(
11
+ {
12
+ "control": {
13
+ "code": "CEZ_IECC2018",
14
+ "complianceMode": "UA",
15
+ "version": "",
16
+ },
17
+ "envelope": {
18
+ "useVltDetails": False,
19
+ "useCoolRoofPerformanceDetails": False,
20
+ "postAltWindowWallPct": None,
21
+ "postAltSkylightRoofPct": None,
22
+ "altPctGlazingAreaReplaced": None,
23
+ "altPctSkylightAreaReplaced": None,
24
+ "applyWindowPctAllowanceForDaylighting": False,
25
+ "applySkylightPctAllowanceForDaylighting": False,
26
+ "agWall": [],
27
+ "bgWall": [],
28
+ "roof": [],
29
+ "floor": [],
30
+ "door": [],
31
+ "window": [],
32
+ "skylight": [],
33
+ "useOrientDetails": True,
34
+ "airBarrierComplianceType": "AIR_BARRIER_OPTION_UNKNOWN",
35
+ },
36
+ "project": {
37
+ "projectTitle": "Default Project",
38
+ "projectPermitNumber": "",
39
+ "projectPermitDate": "",
40
+ "projectTaxMap": None,
41
+ "projectLotNumber": None,
42
+ "projectAddress": "",
43
+ "projectAddress2": None,
44
+ "projectCity": "",
45
+ "projectState": "",
46
+ "projectZipCode": "",
47
+ "projectComplete": None,
48
+ "ownerFirstName": "",
49
+ "ownerLastName": "",
50
+ "ownerCompany": "",
51
+ "ownerAddress": "",
52
+ "ownerAddress2": None,
53
+ "ownerCity": "",
54
+ "ownerState": "",
55
+ "ownerZipCode": "",
56
+ "ownerPhone": "",
57
+ "ownerEmail": "",
58
+ "developerFirstName": "",
59
+ "developerLastName": "",
60
+ "developerCompany": "",
61
+ "developerAddress": "",
62
+ "developerAddress2": None,
63
+ "developerCity": "",
64
+ "developerState": "",
65
+ "developerZipCode": "",
66
+ "developerPhone": "",
67
+ "developerEmail": "",
68
+ "notes": "",
69
+ },
70
+ "lighting": {
71
+ "exteriorLightingZoneType": "EXT_ZONE_UNSPECIFIED",
72
+ "exteriorUse": [],
73
+ "wholeBldgUse": [],
74
+ "fixtureSchedule": [],
75
+ },
76
+ "renewable": {
77
+ "renewableException": "RENEWABLE_ONSITE_EXCEPTION_NONE",
78
+ "numberOfFloors": 1,
79
+ "largestThreeFloorArea": 0,
80
+ "requiredCapacity": 0,
81
+ "proposedCapacity": 0,
82
+ "roofAreaForRenewable": 0,
83
+ "requiredOffsiteRenewableEnergy": 0,
84
+ "proposedOffsiteRenewableEnergy": 0,
85
+ "renewableSystem": [],
86
+ "offsiteRenewableProcurement": [],
87
+ },
88
+ "location": {"state": "Colorado", "city": "Boulder", "climateZone": 5},
89
+ "semiheated": None,
90
+ "hvac": {"hvacPlant": [], "hvacSystem": [], "fanSystem": []},
91
+ "swhSystem": [],
92
+ "requirements": [],
93
+ "isNonresidentialConditioning": True,
94
+ "isResidentialConditioning": False,
95
+ "isSemiheatedConditioning": False,
96
+ "isHistoricBuilding": False,
97
+ "feetBldgHeight": None,
98
+ "constructionType": "NONE",
99
+ "projectType": "NEW_CONSTRUCTION",
100
+ "projectSubType": "CONSTRUCTION_COMPLETE",
101
+ "projectMechanicalType": "PROJECT_HVAC_WITH_CENTRAL",
102
+ "projectCoreAndShellCredit": 0,
103
+ "efficiencyPackages": [],
104
+ "performanceRating": 0,
105
+ "energyCreditPerformanceRating": 0,
106
+ "buildingUseType": "ACTIVITY",
107
+ "conditioningType": "HEATING_AND_COOLING",
108
+ "heatingFuelType": "UNKNOWN_FUEL",
109
+ "userProject": None,
110
+ "allElectric": None,
111
+ "isRenewable": None,
112
+ "hasBattery": None,
113
+ "hasCharger": None,
114
+ "hasHeatPump": None,
115
+ }
116
+ )