OpenPH-Demand
Energy demand calculations for Passive House (PHPP)
Part of the openph UV workspace - implements PHPP energy demand calculations with exact numerical fidelity to Excel PHPP.
Purpose
OpenPH-Demand calculates:
- Annual Heating Demand: Transmission and ventilation heat losses, internal/solar gains
- Annual Cooling Demand: Solar gains, internal gains, cooling strategies
- Solar Radiation: Window and opaque surface solar radiation for heating/cooling periods
- Ground Heat Transfer: Temperature calculations for ground-coupled surfaces
It does not yet implement the PHPP Heating load / Cooling load
worksheets — only the peak-load climate inputs those worksheets consume. So
there is no design-load (W) result, and the energy demand summary reports
peak_w: null rather than a misleading zero.
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 — a web request, a batch parametric run, a stored record — use the compact summary rather than 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"}
}
What it is, and is not:
- It supplements
openph.results.collect_results— the complete, PHPP-addressable audit document — and does not replace or shrink it. The summary is ~200x smaller and carries no worksheet addresses. - Annual values are canonical PHPP results, not sums the builder computed.
Callers must not re-aggregate
monthly_kwh. - Monthly values carry
"jan"…"dec"period labels; array position is never the contract. nullmeans "OpenPH does not calculate this quantity", never zero. That is whypeak_wisnulltoday.coolingis sensible useful cooling demand and excludes dehumidification — it is not the combined PHIVerification!I39figure.schema_versionis independent of the package version. A reader accepts any matching major version, so additive1.xfields stay readable.- No pandas, no rich: the summary is core output, not a table view.
Wanting a subset of the audit document instead
If you need PHPP-addressable demand records rather than the compact payload —
QA against a workbook, say — 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 semantics — the four selectors, how they combine, and which of them raise
rather than silently matching nothing — live in the openph package README
and in ResultSelection's own docstring (help(ResultSelection)), and are not
repeated here. One caveat specific to this package: selecting
energy_demand still constructs ground and solar_radiation, because the
demand solvers call them as calculation dependencies. The guarantee is that
the collector does not instantiate a solver you excluded, not that an
excluded solver is never built.
For a per-request application payload, reach for
build_energy_demand_summary above rather than a selection — it is the
surface 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)
Development
Part of UV workspace - see root context/ENVIRONMENT.md:
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.8.0.tar.gz.
File metadata
- Download URL: openph_demand-0.8.0.tar.gz
- Upload date:
- Size: 104.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
dbb87ad9289dbf3cd61c770bcf5a38725c90c6aaba604ff45d51793f97c2a6b5
|
|
| MD5 |
498efb0ae3889878009d33f0b79ae74a
|
|
| BLAKE2b-256 |
ead53a62a3fcd2634faafb576ba42f75cb9a57395b16130e24ab106816c36695
|
Provenance
The following attestation bundles were made for openph_demand-0.8.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.8.0.tar.gz -
Subject digest:
dbb87ad9289dbf3cd61c770bcf5a38725c90c6aaba604ff45d51793f97c2a6b5 - Sigstore transparency entry: 2471238359
- Sigstore integration time:
-
Permalink:
Open-PH/openph-demand@079e6a0e3d17c3b2ae53af6982c8ffe5e8b196b0 -
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@079e6a0e3d17c3b2ae53af6982c8ffe5e8b196b0 -
Trigger Event:
workflow_dispatch
-
Statement type:
File details
Details for the file openph_demand-0.8.0-py3-none-any.whl.
File metadata
- Download URL: openph_demand-0.8.0-py3-none-any.whl
- Upload date:
- Size: 107.3 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 |
e0cb650bb82a7f89b1ba92c07d3312cfd9c4c5e30764d0b1d5ad529a72ccb92e
|
|
| MD5 |
b8e7a2ec8895be376172951e6723e8f0
|
|
| BLAKE2b-256 |
2e07084e722dd1e6dc6f0f77f9f01aac2b7861887b98bb2d168c9ab63cfa787b
|
Provenance
The following attestation bundles were made for openph_demand-0.8.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.8.0-py3-none-any.whl -
Subject digest:
e0cb650bb82a7f89b1ba92c07d3312cfd9c4c5e30764d0b1d5ad529a72ccb92e - Sigstore transparency entry: 2471238455
- Sigstore integration time:
-
Permalink:
Open-PH/openph-demand@079e6a0e3d17c3b2ae53af6982c8ffe5e8b196b0 -
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@079e6a0e3d17c3b2ae53af6982c8ffe5e8b196b0 -
Trigger Event:
workflow_dispatch
-
Statement type: