Skip to main content

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.
  • null means "OpenPH does not calculate this quantity", never zero. That is why peak_w is null today.
  • cooling is sensible useful cooling demand and excludes dehumidification — it is not the combined PHI Verification!I39 figure.
  • schema_version is independent of the package version. A reader accepts any matching major version, so additive 1.x fields 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_demandOpPhEnergyDemandSolver (heating + cooling)
  • groundOpPhGroundSolver (ground heat transfer)
  • solar_radiationOpPhSolarRadiationSolver (solar gains)

Concurrency

The contract is core's — see ## Concurrency in the openph README. These solvers add no restriction to it. Independent models may be converted, solved and collected concurrently; one model may be read concurrently once it has been solved on a single thread; sharing an unsolved model is unsupported.

One thing worth knowing about this package specifically, since it is the reason the shared-model case is unsupported rather than merely slow. OpPhGroundSolver runs a ten-iteration solve and assigns each period's heat flow only when it finishes, and the heating and cooling calculations read those periods. Every such read forces the solve first, so a caller always gets solved values — but that is a read-time guarantee, not a lock, and it is why the ground solve must never be moved into object construction: the solve reads back into energy_demand.heating_demand, so forcing it during construction would cycle.

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

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

openph_demand-0.13.0.tar.gz (108.0 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.13.0-py3-none-any.whl (110.8 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: openph_demand-0.13.0.tar.gz
  • Upload date:
  • Size: 108.0 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.13.0.tar.gz
Algorithm Hash digest
SHA256 4638d4f30acbcd85dcd595b78116787442dc3437b14c30b9b11fb176054bfa54
MD5 6df1b79ecc6248d7276a89e8aa8655f6
BLAKE2b-256 368a62cb755c2ec8d0af2e3212c3aa46813ab7e1ad8e267568112ea61187a1de

See more details on using hashes here.

Provenance

The following attestation bundles were made for openph_demand-0.13.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.13.0-py3-none-any.whl.

File metadata

  • Download URL: openph_demand-0.13.0-py3-none-any.whl
  • Upload date:
  • Size: 110.8 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.13.0-py3-none-any.whl
Algorithm Hash digest
SHA256 d0d69269a22b06067bab062893fc1e9328c9a8ba91767b55caf16ca20d656f4f
MD5 1772b862d79c2ca990258db6c0ceecab
BLAKE2b-256 4aa5d76c80e39de4016d5bf2c672582e8cb4ddf7834ff0421a677cd57cd35525

See more details on using hashes here.

Provenance

The following attestation bundles were made for openph_demand-0.13.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

0.14.0

2 files

This release

0.13.0 This release

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