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. nullmeans OpenPH does not calculate that quantity. It never means zero, and it is whypeak_wisnulltoday.coolingis sensible useful cooling demand and excludes dehumidification. It differs from the combined PHIVerification!I39figure.schema_versionis independent of the package version. A reader accepts any matching major version, so fields added in1.xstay 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_demand→OpPhEnergyDemandSolver(heating + cooling)ground→OpPhGroundSolver(ground heat transfer)solar_radiation→OpPhSolarRadiationSolver(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
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
a1c31bf7d1b6052a0972dd0dfdf1eaf2ce6a4de18a460c35fb97c6bcdb1fbd81
|
|
| MD5 |
75ef460416a336821878a652e5f58af7
|
|
| BLAKE2b-256 |
608d9e7189ea6547f093b4411fe4b9d7ce7e541d80515bc22e69ec2b49222f09
|
Provenance
The following attestation bundles were made for openph_demand-0.14.0.tar.gz:
Publisher:
publish.yml on Open-PH/openph-demand
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
openph_demand-0.14.0.tar.gz -
Subject digest:
a1c31bf7d1b6052a0972dd0dfdf1eaf2ce6a4de18a460c35fb97c6bcdb1fbd81 - Sigstore transparency entry: 2838926375
- Sigstore integration time:
-
Permalink:
Open-PH/openph-demand@fee8f99cb176169524e592827d42dc4474befdf5 -
Branch / Tag:
refs/heads/main - Owner: https://github.com/Open-PH
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@fee8f99cb176169524e592827d42dc4474befdf5 -
Trigger Event:
workflow_dispatch
-
Statement type:
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
6142af1b0540ec6d22795fbe2ac4529b9c0cfc1eeb0ace6b350d4b6d111bdedd
|
|
| MD5 |
d482fa261f0aa2a169c277da2cdafdc2
|
|
| BLAKE2b-256 |
eaec6acae37a575afc30ab87ad40cf5b5c8f55dee3fbbbe49a07dc2f2425d0ec
|
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
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
openph_demand-0.14.0-py3-none-any.whl -
Subject digest:
6142af1b0540ec6d22795fbe2ac4529b9c0cfc1eeb0ace6b350d4b6d111bdedd - Sigstore transparency entry: 2838926470
- Sigstore integration time:
-
Permalink:
Open-PH/openph-demand@fee8f99cb176169524e592827d42dc4474befdf5 -
Branch / Tag:
refs/heads/main - Owner: https://github.com/Open-PH
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@fee8f99cb176169524e592827d42dc4474befdf5 -
Trigger Event:
workflow_dispatch
-
Statement type: