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)

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__)

    if project_id:
        logger.info("Updating project: %s", project_id)
        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()