Skip to main content

OpenPH-Demand

PHPP energy demand calculations.

openph-demand is one package in the OpenPH UV workspace. It implements the PHPP energy demand calculations and reproduces the numbers in Excel PHPP exactly.

Purpose

OpenPH-Demand calculates:

  • Annual heating demand: transmission and ventilation heat losses, internal and solar gains.
  • Annual cooling demand: solar gains, internal gains, cooling strategies.
  • Solar radiation on windows and opaque surfaces for the heating and cooling periods.
  • Ground heat transfer: temperatures for ground-coupled surfaces.

It does not yet implement the PHPP Heating load and Cooling load worksheets. It covers only the peak-load climate inputs those worksheets use. There is no design-load (W) result, so the energy demand summary reports peak_w: null, because a zero would be misleading.

Structure

openph-demand/
├── src/
│   └── openph_demand/
│       ├── heating_demand/      # Heating demand calculations
│       ├── cooling_demand/      # Cooling demand calculations
│       ├── ground/              # Ground heat transfer
│       ├── solar/               # Solar radiation calculations
│       ├── to_table/            # Table view renderers
│       ├── summary.py           # EnergyDemandSummary + build_energy_demand_summary
│       ├── get_solvers.py       # Type-safe solver accessors
│       └── solvers.py           # Solver classes (Energy, Ground, Solar)
├── tests/
└── pyproject.toml

Usage

from openph.phpp import OpPhPHPP
from openph_demand.get_solvers import (
    get_openph_energy_demand_solver,
    get_openph_ground_solver,
    get_openph_solar_solver,
)

# Create PHPP model
phpp = OpPhPHPP()

# Get solvers (registered via entry points)
energy_solver = get_openph_energy_demand_solver(phpp)
ground_solver = get_openph_ground_solver(phpp)
solar_solver = get_openph_solar_solver(phpp)

# Access demand calculations
heating_demand = energy_solver.heating_demand
cooling_demand = energy_solver.cooling_demand

# Get results. Read the canonical annual scalars; never sum a monthly row.
annual_heating_kwh = heating_demand.total_yearly_heating_demand              # Heating!AF117
annual_heating_kwh_m2a = heating_demand.total_yearly_specific_heating_demand  # Heating!Q78
annual_cooling_kwh = cooling_demand.total_annual_cooling_demand_kwh           # Cooling!AG155
ground_temps = ground_solver.periods
window_radiation = solar_solver.annual_demand

Energy demand summary

For an application such as a web request, a batch parametric run, or a stored record, use the compact summary instead of reading solver properties one at a time:

from openph_demand import build_energy_demand_summary

summary = build_energy_demand_summary(phpp)
payload = summary.to_dict()   # JSON-ready
text = summary.to_json()      # stable ordering, no NaN/Infinity
{
  "schema_version": "1.0",
  "floor_area_m2": 307.40378,
  "heating": {
    "annual_kwh": 7092.797,
    "annual_kwh_m2a": 23.073228,
    "monthly_kwh": [{"period": "jan", "value": 1552.683}, "…"],
    "peak_w": null
  },
  "cooling": {"annual_kwh": 1769.475, "annual_kwh_m2a": 5.756192, "…": "…"},
  "warnings": [],
  "engine_versions": {"openph": "0.6.0", "openph-demand": "0.6.0", "PHX": "1.56.82"}
}

Rules for the summary:

  • It adds to openph.results.collect_results, the complete PHPP-addressable audit document, and leaves that document whole. The summary is about 200x smaller and has no worksheet addresses.
  • Annual values are canonical PHPP results that the builder copies as-is. Callers must not re-aggregate monthly_kwh.
  • Monthly values carry "jan" through "dec" period labels. Match on the label, never on array position.
  • null means OpenPH does not calculate that quantity. It never means zero, and it is why peak_w is null today.
  • cooling is sensible useful cooling demand and excludes dehumidification. It differs from the combined PHI Verification!I39 figure.
  • schema_version is independent of the package version. A reader accepts any matching major version, so fields added in 1.x stay readable.
  • The summary is core output and needs neither pandas nor rich.

A subset of the audit document

If you need PHPP-addressable demand records, for QA against a workbook for example, narrow the audit document with openph.results.ResultSelection instead of collecting everything:

from openph.results import ResultSelection, collect_results

results = collect_results(phpp, selection=ResultSelection(key_prefixes={"energy_demand"}, tiers={"final"}))

The openph README and help(ResultSelection) document the four selectors, how they combine, and which ones raise instead of matching nothing. This README doesn't repeat them.

One point applies to this package in particular. Selecting energy_demand still constructs ground and solar_radiation, because the demand solvers call them as calculation dependencies. The collector won't instantiate a solver you excluded. A selected solver can still build one.

For a per-request application payload, use build_energy_demand_summary above. It is the output designed for that job.

Registered solvers

This package registers three solver plugins with OpenPH:

  • energy_demandOpPhEnergyDemandSolver (heating + cooling)
  • groundOpPhGroundSolver (ground heat transfer)
  • solar_radiationOpPhSolarRadiationSolver (solar gains)

Concurrency

These solvers follow the core contract in ## Concurrency of the openph README and add no restriction to it. Independent models can be converted, solved, and collected concurrently. After one thread has solved a model, other threads can read it concurrently. Sharing an unsolved model is unsupported.

This package is the reason sharing an unsolved model is unsupported instead of only slow. OpPhGroundSolver runs a ten-iteration solve and assigns each period's heat flow only when the solve finishes. The heating and cooling calculations read those periods. Every such read forces the solve first, so a caller always gets solved values. That guarantee works at read time and uses no lock.

The ground solve must also never move into object construction. The solve reads back into energy_demand.heating_demand, so forcing it during construction would create a cycle.

openph-demand/tests/test_shared_model_boundary.py tests this behavior.

Development

This package is part of the UV workspace. See context/ENVIRONMENT.md at the workspace root:

uv sync                           # Install all workspace packages
uv run pytest openph-demand/tests/ # Run tests

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

openph_demand-0.14.0.tar.gz (107.7 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

openph_demand-0.14.0-py3-none-any.whl (110.6 kB view details)

Uploaded Python 3

File details

Details for the file openph_demand-0.14.0.tar.gz.

File metadata

  • Download URL: openph_demand-0.14.0.tar.gz
  • Upload date:
  • Size: 107.7 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for openph_demand-0.14.0.tar.gz
Algorithm Hash digest
SHA256 a1c31bf7d1b6052a0972dd0dfdf1eaf2ce6a4de18a460c35fb97c6bcdb1fbd81
MD5 75ef460416a336821878a652e5f58af7
BLAKE2b-256 608d9e7189ea6547f093b4411fe4b9d7ce7e541d80515bc22e69ec2b49222f09

See more details on using hashes here.

Provenance

The following attestation bundles were made for openph_demand-0.14.0.tar.gz:

Publisher: publish.yml on Open-PH/openph-demand

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file openph_demand-0.14.0-py3-none-any.whl.

File metadata

  • Download URL: openph_demand-0.14.0-py3-none-any.whl
  • Upload date:
  • Size: 110.6 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for openph_demand-0.14.0-py3-none-any.whl
Algorithm Hash digest
SHA256 6142af1b0540ec6d22795fbe2ac4529b9c0cfc1eeb0ace6b350d4b6d111bdedd
MD5 d482fa261f0aa2a169c277da2cdafdc2
BLAKE2b-256 eaec6acae37a575afc30ab87ad40cf5b5c8f55dee3fbbbe49a07dc2f2425d0ec

See more details on using hashes here.

Provenance

The following attestation bundles were made for openph_demand-0.14.0-py3-none-any.whl:

Publisher: publish.yml on Open-PH/openph-demand

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Release history Release notifications | RSS feed

0.15.0

2 files

This release

0.14.0 This release

2 files

0.13.0

2 files

0.12.0

2 files

0.11.0

2 files

0.10.0

2 files

0.9.0

2 files

0.8.0

2 files

0.7.0

2 files

0.6.0

2 files

0.5.0

2 files

0.4.0

2 files

0.3.0

2 files

0.2.0

2 files

0.1.1

2 files

0.1.0

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page