Skip to content

COMcheckClient

The primary interface for interacting with the COMcheck Web API.

COMcheckClient

COMcheckClient(api_key=None)

High-level client for the COMcheck Web API.

Provides user-friendly methods that accept Pydantic models as inputs and return either :class:~comcheck_api.types.core_types.ComBuilding models, primitives, or raw dicts depending on the operation.

Can be used as a context manager to ensure the underlying HTTP connection is closed::

with COMcheckClient(api_key="your-key") as client:
    project = client.get_project("123")
Example
client = COMcheckClient(api_key="your-key")
projects = client.list_projects()
project = client.get_project(projects[0]["projectId"])
client.close()

Initialize COMcheck client.

Parameters:

Name Type Description Default
api_key Optional[str]

Optional API key for authentication. Can be set later with set_api_key()

None
Source code in comcheck_api/client/comcheck_client.py
def __init__(self, api_key: Optional[str] = None) -> None:
    """Initialize COMcheck client.

    Args:
        api_key: Optional API key for authentication. Can be set later with set_api_key()
    """
    self._api_service: Optional[COMCheckApiService] = None
    if api_key:
        self.set_api_key(api_key)

set_api_key

set_api_key(api_key)

Set the API key and initialize the API service.

Parameters:

Name Type Description Default
api_key str

API key for authentication

required

Raises:

Type Description
ValueError

If API key is not provided

Source code in comcheck_api/client/comcheck_client.py
def set_api_key(self, api_key: str) -> None:
    """Set the API key and initialize the API service.

    Args:
        api_key: API key for authentication

    Raises:
        ValueError: If API key is not provided
    """
    if not api_key:
        raise ValueError("API key is required.")
    self._api_service = COMCheckApiService(api_key)

get_project

get_project(
    project_id: str, mode: Literal["python"] = "python"
) -> Optional[ComBuilding]
get_project(
    project_id: str, mode: Literal["json"]
) -> Optional[Dict[str, Any]]
get_project(project_id, mode='python')

Fetch a single project by ID.

Parameters:

Name Type Description Default
project_id str

The project ID to retrieve.

required
mode Literal['python', 'json']

"python" returns a ComBuilding model; "json" returns a raw dict.

'python'

Returns:

Type Description
Optional[Union[ComBuilding, Dict[str, Any]]]

The project as a ComBuilding or dict, or None if not found.

Source code in comcheck_api/client/comcheck_client.py
def get_project(
    self,
    project_id: str,
    mode: Literal["python", "json"] = "python",
) -> Optional[Union["ComBuilding", Dict[str, Any]]]:
    """Fetch a single project by ID.

    Args:
        project_id: The project ID to retrieve.
        mode: ``"python"`` returns a ``ComBuilding`` model; ``"json"`` returns a raw dict.

    Returns:
        The project as a ``ComBuilding`` or dict, or ``None`` if not found.
    """
    resp = self._service.get_project(project_id)
    data = resp.get("data")
    if data is not None:
        for building_area in data["lighting"]["wholeBldgUse"]:
            building_area["interiorLightingSpace"] = {
                **DEFAULT_BUILDING_AREA.interiorLightingSpace.model_dump(
                    mode="json", exclude_unset=True
                )
            }

    return self._parse_data(data, mode)

list_projects

list_projects()

Get a list of all projects.

Returns:

Type Description
List[Dict[str, Any]]

API response data as list of project dictionaries

Source code in comcheck_api/client/comcheck_client.py
def list_projects(self) -> List[Dict[str, Any]]:
    """Get a list of all projects.

    Returns:
        API response data as list of project dictionaries
    """
    return self._service.get_project_list().get("data", [])

update_project

update_project(project_id, project_data, mode='python')

Update a project by ID.

The existing project is fetched first to preserve server-assigned IDs and the userProject reference. Envelope component IDs that are None (auto-generated by Pydantic) are stripped before submission.

Parameters:

Name Type Description Default
project_id str

The project ID to update.

required
project_data ComBuilding

A ComBuilding model with the desired state.

required
mode Literal['python', 'json']

"python" returns a ComBuilding model; "json" returns a raw dict.

'python'

Returns:

Type Description
ComBuilding | dict[str, Any] | None

The refreshed project after the update, in the requested format.

Raises:

Type Description
COMCheckProjectNotFoundError

If the project does not exist.

Source code in comcheck_api/client/comcheck_client.py
def update_project(
    self,
    project_id: str,
    project_data: ComBuilding,
    mode: Literal["python", "json"] = "python",
) -> ComBuilding | dict[str, Any] | None:
    """Update a project by ID.

    The existing project is fetched first to preserve server-assigned IDs
    and the ``userProject`` reference.  Envelope component IDs that are
    ``None`` (auto-generated by Pydantic) are stripped before submission.

    Args:
        project_id: The project ID to update.
        project_data: A ``ComBuilding`` model with the desired state.
        mode: ``"python"`` returns a ``ComBuilding`` model;
            ``"json"`` returns a raw dict.

    Returns:
        The refreshed project after the update, in the requested format.

    Raises:
        COMCheckProjectNotFoundError: If the project does not exist.
    """
    # Get existing project
    old_project = self.get_project(project_id, mode="json")

    if not old_project:
        raise COMCheckProjectNotFoundError(project_id)

    project_data_json = project_data.model_dump(mode="json", exclude_unset=True)

    # Preserve user project reference
    user_project = old_project["userProject"]
    project_data_json["userProject"] = user_project

    # Preserve IDs from existing project
    for section in [
        "envelope",
        "lighting",
        "hvac",
        "control",
        "project",
        "location",
        "renewable",
    ]:
        project_data_json[section]["id"] = old_project[section]["id"]
    project_data_json["id"] = old_project["id"]

    # Ensure each building area has interiorLightingSpace initialized
    for building_area in project_data_json["lighting"]["wholeBldgUse"]:
        building_area["interiorLightingSpace"] = {
            **DEFAULT_BUILDING_AREA.interiorLightingSpace.model_dump(
                mode="json", exclude_unset=True
            )
        }

    # TODO: need to verify if other componets also need to remove None IDs (auto-generated through pydantic )
    # Remove None IDs from envelope components
    if "envelope" in project_data_json:
        envelope = project_data_json["envelope"]
        for component_type in [
            "roof",
            "agWall",
            "bgWall",
            "floor",
            "skylight",
            "window",
            "door",
        ]:
            if component_type in envelope and isinstance(
                envelope[component_type], list
            ):
                for component in envelope[component_type]:
                    if isinstance(component, dict) and component.get("id") is None:
                        component.pop("id", None)
                    # Also remove None IDs from nested components (skylights in roofs, windows/doors in walls)
                    if component_type == "roof" and "skylight" in component:
                        for skylight in component.get("skylight", []):
                            if (
                                isinstance(skylight, dict)
                                and skylight.get("id") is None
                            ):
                                skylight.pop("id", None)
                    elif component_type in ["agWall", "bgWall"]:
                        for nested_type in ["window", "door", "thermalBridge"]:
                            if nested_type in component:
                                for nested in component.get(nested_type, []):
                                    if (
                                        isinstance(nested, dict)
                                        and nested.get("id") is None
                                    ):
                                        nested.pop("id", None)

    self._service.update_project(project_id, project_data_json).get("data")

    # It's necessary that we call return get_project instead of what's returned from
    # the service's update project due to interiorLightingSpace not being updated correctly
    # when returned from the update call
    return self.get_project(project_id=project_id, mode=mode)

update_uvalues

update_uvalues(project)

Calculate assembly u-values and update the project in place.

Sends the full envelope to the u-value endpoint and writes the returned values back onto the matching agWall, bgWall, roof, and floor components (matched by assemblyType). Only these assembly types receive calculated u-values; effectiveUFactor applies to agWall only.

Parameters:

Name Type Description Default
project ComBuilding

The project whose envelope u-values should be refreshed.

required

Returns:

Type Description
ComBuilding

The same project instance, with u-values updated.

Source code in comcheck_api/client/comcheck_client.py
def update_uvalues(self, project: ComBuilding) -> ComBuilding:
    """Calculate assembly u-values and update the project in place.

    Sends the full envelope to the u-value endpoint and writes the returned
    values back onto the matching ``agWall``, ``bgWall``, ``roof``, and
    ``floor`` components (matched by ``assemblyType``). Only these assembly
    types receive calculated u-values; ``effectiveUFactor`` applies to
    ``agWall`` only.

    Args:
        project: The project whose envelope u-values should be refreshed.

    Returns:
        The same ``project`` instance, with u-values updated.
    """
    energy_code = str(project.control.code)
    envelope_data = project.envelope.model_dump(mode="json", exclude_unset=True)
    updated_assembly_uvalues = self._service.assemblies_uvalue(
        envelope_data, energy_code
    )["data"]

    for wall in project.envelope.agWall:
        for uvalue in updated_assembly_uvalues.get("agWall", []):
            if wall.assemblyType == uvalue["assemblyType"]:
                wall.effectiveUFactor = float(uvalue["effectiveUFactor"])
                wall.propUValue = float(uvalue["propUValue"])
    for wall in project.envelope.bgWall:
        for uvalue in updated_assembly_uvalues.get("bgWall", []):
            if wall.assemblyType == uvalue["assemblyType"]:
                wall.propUValue = float(uvalue["propUValue"])
    for roof in project.envelope.roof:
        for uvalue in updated_assembly_uvalues.get("roof", []):
            if roof.assemblyType == uvalue["assemblyType"]:
                roof.propUValue = float(uvalue["propUValue"])
    for floor in project.envelope.floor:
        for uvalue in updated_assembly_uvalues.get("floor", []):
            if floor.assemblyType == uvalue["assemblyType"]:
                floor.propUValue = float(uvalue["propUValue"])

    return project

check_compliance

check_compliance(project)

Check code compliance for a project.

Parameters:

Name Type Description Default
project ComBuilding

The project data to evaluate.

required

Returns:

Type Description
Any

The compliance results payload returned by the API.

Source code in comcheck_api/client/comcheck_client.py
def check_compliance(self, project: ComBuilding) -> Any:
    """Check code compliance for a project.

    Args:
        project: The project data to evaluate.

    Returns:
        The compliance results payload returned by the API.
    """
    project_data = project.model_dump(mode="json", exclude_unset=True)
    response = self._service.check_compliance(project_data)
    return response.get("data")

check_requirements

check_requirements(project)

Check the applicable requirements for a project.

Parameters:

Name Type Description Default
project ComBuilding

The project data to evaluate.

required

Returns:

Type Description
Any

The requirements payload returned by the API.

Source code in comcheck_api/client/comcheck_client.py
def check_requirements(self, project: ComBuilding) -> Any:
    """Check the applicable requirements for a project.

    Args:
        project: The project data to evaluate.

    Returns:
        The requirements payload returned by the API.
    """
    project_data = project.model_dump(mode="json", exclude_unset=True)
    response = self._service.check_requirements(project_data)
    return response.get("data")

generate_report

generate_report(
    project,
    envelope=True,
    extlighting=True,
    intlighting=True,
    mechanical=True,
    download=False,
    download_dir=None,
)

Generate a PDF report for a project.

The report PDF is stored in S3; the API returns a presigned URL to download it. The URL is short-lived (expires within a few minutes).

This method does not open the report in a browser — as a library it returns the report metadata and lets the caller decide what to do (e.g. webbrowser.open(report["url"]), stream it onward, or save it via download).

Parameters:

Name Type Description Default
project ComBuilding

The project data to generate a report for.

required
envelope bool

Whether to include the envelope section in the report.

True
extlighting bool

Whether to include the exterior lighting section.

True
intlighting bool

Whether to include the interior lighting section.

True
mechanical bool

Whether to include the mechanical section.

True
download bool

When True, fetch the PDF from the presigned URL and save it using the server-provided fileName.

False
download_dir Optional[str]

Directory to save the PDF into when download is True. Defaults to the user's ~/Downloads folder.

None

Returns:

Type Description
Optional[Dict[str, Any]]

The report metadata dict with url (the presigned S3 URL),

Optional[Dict[str, Any]]

expires, and fileName.

Source code in comcheck_api/client/comcheck_client.py
def generate_report(
    self,
    project: ComBuilding,
    envelope: bool = True,
    extlighting: bool = True,
    intlighting: bool = True,
    mechanical: bool = True,
    download: bool = False,
    download_dir: Optional[str] = None,
) -> Optional[Dict[str, Any]]:
    """Generate a PDF report for a project.

    The report PDF is stored in S3; the API returns a presigned URL to
    download it. The URL is short-lived (expires within a few minutes).

    This method does not open the report in a browser — as a library it
    returns the report metadata and lets the caller decide what to do
    (e.g. ``webbrowser.open(report["url"])``, stream it onward, or save it
    via *download*).

    Args:
        project: The project data to generate a report for.
        envelope: Whether to include the envelope section in the report.
        extlighting: Whether to include the exterior lighting section.
        intlighting: Whether to include the interior lighting section.
        mechanical: Whether to include the mechanical section.
        download: When ``True``, fetch the PDF from the presigned URL and
            save it using the server-provided ``fileName``.
        download_dir: Directory to save the PDF into when *download* is
            ``True``. Defaults to the user's ``~/Downloads`` folder.

    Returns:
        The report metadata dict with ``url`` (the presigned S3 URL),
        ``expires``, and ``fileName``.
    """
    report_data = {
        "building": project.model_dump(mode="json", exclude_unset=True),
        "envelope": envelope,
        "extlighting": extlighting,
        "intlighting": intlighting,
        "mechanical": mechanical,
    }
    response = self._service.generate_report(report_data)
    if response and download:
        if download_dir is None:
            download_dir = os.path.join(os.path.expanduser("~"), "Downloads")
        os.makedirs(download_dir, exist_ok=True)
        pdf_response = httpx.get(response["url"], timeout=60.0)
        pdf_response.raise_for_status()
        output_path = os.path.join(download_dir, response["fileName"])
        with open(output_path, "wb") as f:
            f.write(pdf_response.content)
    return response

start_run_simulation

start_run_simulation(project, project_id=None)

Start a compliance simulation run for a project.

If project_id is supplied the project is persisted via :meth:update_project before the simulation is launched.

Parameters:

Name Type Description Default
project ComBuilding

The project data to simulate.

required
project_id Optional[int]

Optional server-side project ID. When given, the project is saved before the simulation starts.

None

Returns:

Type Description
str

The simulation session ID that can be passed to

str

meth:get_simulation_status and :meth:get_simulation_result.

Raises:

Type Description
COMCheckSimulationError

If the API returns no session data.

Source code in comcheck_api/client/comcheck_client.py
def start_run_simulation(
    self, project: ComBuilding, project_id: Optional[int] = None
) -> str:
    """Start a compliance simulation run for a project.

    If *project_id* is supplied the project is persisted via
    :meth:`update_project` before the simulation is launched.

    Args:
        project: The project data to simulate.
        project_id: Optional server-side project ID.  When given, the
            project is saved before the simulation starts.

    Returns:
        The simulation session ID that can be passed to
        :meth:`get_simulation_status` and :meth:`get_simulation_result`.

    Raises:
        COMCheckSimulationError: If the API returns no session data.
    """
    logger = logging.getLogger(__name__)

    # Always refresh the calculated assembly u-values before simulating.
    project = self.update_uvalues(project)

    if project_id:
        logger.info("Updating project: %s", project_id)
        project = self.update_project(str(project_id), project)

    project_data = project.model_dump(mode="json", exclude_unset=True)
    run_result = self._service.start_run_simulation(project_data)
    if run_result.data is None:
        raise COMCheckSimulationError(
            "Simulation start failed: no session data returned"
        )
    return run_result.data.sessionId

get_simulation_status

get_simulation_status(session_id)

Get the status of a running or completed simulation.

Parameters:

Name Type Description Default
session_id str

The session ID returned by :meth:start_run_simulation.

required

Returns:

Type Description
Dict[str, Any]

A dict containing sessionId, status, and optional

Dict[str, Any]

message. status is one of the values defined in

Dict[str, Any]

class:comcheck_api.types.SimulationStatus. Terminal

Dict[str, Any]

values are "SUCCESS" and "FAILED".

Raises:

Type Description
COMCheckSimulationError

If the status cannot be retrieved.

Source code in comcheck_api/client/comcheck_client.py
def get_simulation_status(self, session_id: str) -> Dict[str, Any]:
    """Get the status of a running or completed simulation.

    Args:
        session_id: The session ID returned by :meth:`start_run_simulation`.

    Returns:
        A dict containing ``sessionId``, ``status``, and optional
        ``message``. ``status`` is one of the values defined in
        :class:`comcheck_api.types.SimulationStatus`. Terminal
        values are ``"SUCCESS"`` and ``"FAILED"``.

    Raises:
        COMCheckSimulationError: If the status cannot be retrieved.
    """
    status_response = self._service.get_simulation_status(session_id)
    if status_response.data is None:
        raise COMCheckSimulationError(
            f"Failed to get simulation status for session {session_id}"
        )
    return status_response.data.model_dump(mode="json")

get_simulation_result

get_simulation_result(session_id)

Get the result metrics of a completed simulation.

Parameters:

Name Type Description Default
session_id str

The session ID returned by :meth:start_run_simulation.

required

Returns:

Type Description
Dict[str, Any]

A dict containing sessionId, performanceRating,

Dict[str, Any]

energyCreditPerformanceRating, proposedBpf, and

Dict[str, Any]

baselineBpf.

Raises:

Type Description
COMCheckSimulationError

If the result cannot be retrieved.

Source code in comcheck_api/client/comcheck_client.py
def get_simulation_result(self, session_id: str) -> Dict[str, Any]:
    """Get the result metrics of a completed simulation.

    Args:
        session_id: The session ID returned by :meth:`start_run_simulation`.

    Returns:
        A dict containing ``sessionId``, ``performanceRating``,
        ``energyCreditPerformanceRating``, ``proposedBpf``, and
        ``baselineBpf``.

    Raises:
        COMCheckSimulationError: If the result cannot be retrieved.
    """
    result_response = self._service.get_simulation_result(session_id)
    if result_response.data is None:
        raise COMCheckSimulationError(
            f"Failed to get simulation result for session {session_id}"
        )
    return result_response.data.model_dump(mode="python")

close

close()

Close the API service connection.

Source code in comcheck_api/client/comcheck_client.py
def close(self) -> None:
    """Close the API service connection."""
    if self._api_service is not None:
        self._api_service.close()

__enter__

__enter__()

Context manager entry.

Source code in comcheck_api/client/comcheck_client.py
def __enter__(self) -> "COMcheckClient":
    """Context manager entry."""
    return self

__exit__

__exit__(exc_type, exc_val, exc_tb)

Context manager exit.

Source code in comcheck_api/client/comcheck_client.py
def __exit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None:
    """Context manager exit."""
    self.close()