Thermal comfort prediction API — PMV, adaptive ASHRAE 55, EN 16798 — with uncertainty bounds and a deployable REST endpoint
Project description
comfortkit
A Python library and REST API for thermal comfort prediction with uncertainty bounds and personalised comfort learning. Wraps pythermalcomfort as the computational backend and adds what it doesn't have: a deployable REST endpoint, bootstrap and conformal confidence intervals on every prediction, an ML correction layer trained on real occupant votes, and a per-user Bayesian model that adapts from feedback. For interactive, point-and-click comfort analysis the CBE Thermal Comfort Tool remains the reference — comfortkit is its programmatic complement, built for automated pipelines, sensor integrations, and applications that need to call predictions at scale.
Quick start
pip install comfortkit
from comfortkit import predict
result = predict(
model="pmv",
ta=22.0, # air temperature, °C
tr=22.0, # mean radiant temperature, °C
vel=0.1, # air velocity, m/s
rh=60.0, # relative humidity, %
met=1.2, # metabolic rate, met
clo=0.5, # clothing insulation, clo
)
print(result.pmv) # -0.75
print(result.ppd) # 16.9
print(result.sensation) # "slightly cool"
print(result.ci_95) # e.g. (-1.4, -0.2) — varies; bootstrap CI is stochastic
print(result.compliant) # False
print(result.warnings) # []
The ci_95 field is a bootstrap confidence interval on PMV — accounting for
typical sensor measurement uncertainty (±0.5°C air temp, ±2°C radiant temp,
±5% RH, ±0.05 m/s velocity). Pass your own sigma values via predict(..., sigma={...})
to match your actual sensor spec.
REST API
docker compose up
curl -X POST http://localhost:8000/v1/predict \
-H "Content-Type: application/json" \
-d '{
"model": "pmv",
"ta": 22.0,
"tr": 22.0,
"vel": 0.1,
"rh": 60.0,
"met": 1.2,
"clo": 0.5
}'
{
"pmv": -0.75,
"ppd": 16.9,
"sensation": "slightly cool",
"compliant": false,
"model": "standard_pmv",
"ci_95": [-1.47, -0.22],
"warnings": []
}
Interactive API docs at http://localhost:8000/docs (OpenAPI, auto-generated).
Why comfortkit?
pythermalcomfort is the best open implementation of thermal comfort physics. comfortkit does not replace it — it builds on top of it:
| Feature | pythermalcomfort | comfortkit |
|---|---|---|
| PMV/PPD, adaptive, UTCI, SET | Yes | Via adapter |
| Uncertainty / confidence intervals | No | Yes — bootstrap CI (sensor noise) + conformal CI (data-grounded, 95.3% empirical coverage) |
| REST API + Docker | No | Yes — FastAPI, OpenAPI docs |
| Pydantic v2 typed interface | No | Yes |
| Applicability warnings (non-fatal) | Raises / returns nan | Structured warnings list |
| Batch predictions | Yes | Yes |
| ML correction layer | No | Yes (v0.2) — LightGBM residual model, ONNX |
| Personalised comfort | No | Yes (v0.3) — per-user Bayesian update from occupant votes |
Supported standards
| Standard | Model key | Notes |
|---|---|---|
| ISO 7730 | pmv / standard="iso" |
PMV/PPD via pmv_ppd_iso |
| ASHRAE 55 | pmv / standard="ashrae" |
PMV/PPD via pmv_ppd_ashrae |
| ASHRAE 55 SET | set |
Standard Effective Temperature; result in set_tmp |
| ASHRAE 55 adaptive | adaptive_ashrae (alias adaptive) |
Requires t_running_mean; includes CE fix (see below) |
| EN 16798-1 | Coming v1.0 | Adaptive model |
A note on numerical differences with the CBE Thermal Comfort Tool.
The CBE Thermal Comfort Tool pins
pythermalcomfort==2.10, while comfortkit targets pythermalcomfort>=3.9.
Small PMV differences — typically less than 0.1 — are therefore expected for
the same inputs. Neither result is wrong; they reflect different versions of
the same underlying library. If you need comfortkit's output to match the CBE
tool exactly for validation purposes, install pythermalcomfort==2.10 in an
isolated environment and compare directly against that version.
A note on ISO vs ASHRAE divergence. The two standards produce different
PMV values for the same inputs whenever air velocity is non-negligible, because
they apply different relative air velocity adjustments before computing the
convective heat loss term. For example, ISO 7730 Annex D case C
(ta=27, tr=27, vel=0.3) yields PMV = +0.43 under ISO and PMV = +0.23 under
ASHRAE — a difference of 0.20 PMV units. This is expected behaviour, not a
bug. comfortkit faithfully reproduces pythermalcomfort's implementation of
both standards; if you see this divergence, pass standard="iso" or
standard="ashrae" explicitly to make the intended calculation clear.
Uncertainty quantification
Every PMV prediction carries two complementary confidence intervals:
Bootstrap CI (ci_95) — parametric bootstrap over sensor measurement noise.
Reflects how much PMV would shift if your sensors were reading slightly off.
Width is typically ±0.5–0.8 PMV units under standard sensor tolerances.
Conformal CI (conformal_ci_95) — split conformal prediction interval
calibrated on real occupant TSV votes from the ASHRAE Global Thermal Comfort
Database II. Answers the question "how far might the actual occupant vote
deviate from the PMV prediction?" — a harder, more honest uncertainty bound.
Half-width q=2.77, empirical test-set coverage 95.3%.
result = predict(model="pmv", ta=22.0, tr=22.0, vel=0.1, rh=60.0, met=1.2, clo=0.5)
print(result.ci_95) # e.g. (-1.4, -0.2) — sensor-noise bootstrap
print(result.conformal_ci_95) # e.g. (-3.5, 1.8) — data-grounded, TSV vs PMV
For custom sensor uncertainty, use monte_carlo_propagate directly:
from comfortkit.uncertainty import monte_carlo_propagate
from comfortkit.models.base import ComfortInputs
inputs = ComfortInputs(ta=24, tr=25, vel=0.1, rh=50, met=1.2, clo=0.7)
ci = monte_carlo_propagate(
inputs,
sigma={"ta": 0.3, "tr": 1.0, "vel": 0.02, "rh": 3.0},
n_samples=5000,
)
print(ci) # (lower_pmv, upper_pmv)
ML correction layer
model="ml" applies a LightGBM residual correction on top of standard PMV,
trained on the ASHRAE Global Thermal Comfort Database II
(~78 k field observations across 41 studies; Földváry Ličina et al., 2018). The model predicts the
(TSV − PMV) residual and adds it back to the physics estimate.
from comfortkit import predict
result = predict(
model="ml",
ta=22.0,
tr=22.0,
vel=0.1,
rh=60.0,
met=1.2,
clo=0.5,
# optional — improve accuracy when available
t_out=8.0, # outdoor monthly mean temp (°C)
cooling_strategy="Naturally Ventilated",
building_type="Office",
)
print(result.pmv) # -0.62
print(result.ppd) # 13.7
print(result.model) # "ml_pmv"
print(result.ci_95) # (-1.34, -0.10)
The three optional fields — t_out, cooling_strategy, building_type —
capture adaptive-comfort effects that the standard PMV formula ignores.
They are optional: if omitted, the model falls back to dataset medians /
unknown-category encodings. The correction is served via ONNX Runtime
with no LightGBM dependency at inference time.
Personalised comfort (v0.3)
POST /v1/feedback accepts an occupant thermal preference vote and updates a
per-user Beta-Bernoulli posterior (Dirichlet-Multinomial, K=2) warm-started from
the aggregate prior. GET /v1/comfort/{user_id} returns the current personalised
prediction.
# Submit a vote: this occupant wants it Cooler
curl -X POST http://localhost:8000/v1/feedback \
-H "Content-Type: application/json" \
-d '{
"user_id": "desk-42",
"vote": 1,
"heart_rate": 74.0,
"t_net": 27.5,
"rh_net": 65.0,
"clothing": "Light",
"met": "sitting"
}'
{
"user_id": "desk-42",
"n_votes": 1,
"p_cooler": 0.4907,
"prediction": 0,
"prediction_label": "Not Cooler"
}
# Retrieve the personalised prediction after enough votes have accumulated
curl http://localhost:8000/v1/comfort/desk-42
{
"user_id": "desk-42",
"p_cooler": 0.8731,
"prediction": 1,
"prediction_label": "Cooler",
"n_votes": 47,
"confidence": "medium"
}
The prior is weak (5 pseudo-counts from the Dorn study aggregate).
Roughly 10–20 votes are enough to pull the posterior away from the global mean;
by 50 votes, users with atypical preferences are predicted correctly >90% of the
time. Contextual features (heart_rate, t_net, rh_net, clothing, met)
are stored in the audit log for future feature-conditioned extensions.
Vote data is stored in SQLite by default (COMFORTKIT_DB env var to override).
See CONTRIBUTING.md for the Postgres migration path before production deployment.
Roadmap
| Version | Scope |
|---|---|
| v0.1 | PMV adapter + bootstrap CI + FastAPI REST + PyPI ✓ |
| v0.2 | ML correction layer trained on ASHRAE Global Thermal Comfort DB II + ONNX export ✓ |
| v0.2.1 | SET adapter + adaptive ASHRAE 55 adapter + CE bug fix (pythermalcomfort 3.9.2) ✓ |
| v0.2.2 | Conformal prediction CI calibrated on real TSV votes; q=2.77, 95.3% coverage ✓ |
| v0.3 | Personalised comfort — Bayesian update from occupant votes via /v1/feedback ✓ |
| v1.0 | Stable API contract, full docs site, Zenodo DOI |
Contributing
See CONTRIBUTING.md. Good first issues are labelled
good-first-issue.
We especially welcome: additional model adapters (EN 16798 adaptive, UTCI), BMS/sensor platform integrations, and occupant comfort survey datasets for the v0.2 ML layer.
Citation
If you use comfortkit in research, please cite:
@software{comfortkit,
author = {Kottapalli, Kalyan},
title = {comfortkit: Thermal comfort prediction with uncertainty bounds and REST API},
year = {2026},
url = {https://github.com/K2alyan/comfortkit},
}
A Zenodo DOI will be registered at v1.0.
Data & Acknowledgements
ASHRAE Global Thermal Comfort Database II
Used to train the v0.2 ML correction layer (~78 k field observations, 41 studies).
If you use model="ml" in your work, please cite:
Földváry Ličina, V., Cheung, T., Zhang, H., de Dear, R., Parkinson, T., Arens, E., & Schiavon, S. (2018). Development of the ASHRAE Global Thermal Comfort Database II. Building and Environment, 142, 502–512. https://doi.org/10.1016/j.buildenv.2018.06.022
Data: https://www.kaggle.com/datasets/claytonmiller/ashrae-global-thermal-comfort-database-ii
Dorn longitudinal study
Used to derive the warm-start prior for the v0.3 personalised comfort model
(20 users, Singapore, Apr–Nov 2020) and as a reference dataset for the v0.4
zone aggregation validation. If you use POST /v1/feedback or ZoneAggregator
in your work, please cite:
Tartarini, F., Schiavon, S., Quintana, M., & Miller, C. (2022). Indoor microclimate and thermal comfort of a Singaporean hawker centre and office building: A longitudinal study. Indoor Air, 32, e13160. https://doi.org/10.1111/ina.13160
Code: https://github.com/FedericoTartarini/dorn-longitudinal-tc-study
ENTH longitudinal dataset
Used for the v0.4 zone aggregation proof-of-concept validation (17 participants, 3 NUS SDE buildings, Mar–Apr 2021). If you use or extend the zone aggregation validation in your work, please cite:
Quintana, M., Abdelrahman, M., Frei, M., Tartarini, F., & Miller, C. (2021). Cohort comfort models — Personal thermal comfort models using a smart footwear system in an open-plan office. SenSys 2021, pp. 556–559. https://doi.org/10.1145/3485730.3493693
Data: https://zenodo.org/record/5502441
pythermalcomfort
comfortkit uses pythermalcomfort as its computational backend for all physics models (PMV/PPD, adaptive ASHRAE 55, SET, UTCI). If comfortkit is useful to you, consider starring that project too. If you publish results from any physics-based model, please cite:
Tartarini, F., & Schiavon, S. (2020). pythermalcomfort: A Python package for thermal comfort research. SoftwareX, 12, 100578. https://doi.org/10.1016/j.softx.2020.100578
License
MIT © Kalyan Kottapalli
Project details
Release history Release notifications | RSS feed
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 comfortkit-0.5.0.tar.gz.
File metadata
- Download URL: comfortkit-0.5.0.tar.gz
- Upload date:
- Size: 114.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
bb225a977285b9f584f4824548f7f72379ab4521dd6904491e2a6497328ccd5a
|
|
| MD5 |
30a0d8c518f21898d083e1771e930ba7
|
|
| BLAKE2b-256 |
949bc8325b7bbf23b7e531f22e90ed35eb2a08af6e7290a32dfcddb510bad8be
|
Provenance
The following attestation bundles were made for comfortkit-0.5.0.tar.gz:
Publisher:
publish.yml on K2alyan/comfortkit
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
comfortkit-0.5.0.tar.gz -
Subject digest:
bb225a977285b9f584f4824548f7f72379ab4521dd6904491e2a6497328ccd5a - Sigstore transparency entry: 1392627543
- Sigstore integration time:
-
Permalink:
K2alyan/comfortkit@e30eb998aef01d2cd6343dafd9e551e2edd2a3e6 -
Branch / Tag:
refs/tags/v0.5.0-release - Owner: https://github.com/K2alyan
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@e30eb998aef01d2cd6343dafd9e551e2edd2a3e6 -
Trigger Event:
push
-
Statement type:
File details
Details for the file comfortkit-0.5.0-py3-none-any.whl.
File metadata
- Download URL: comfortkit-0.5.0-py3-none-any.whl
- Upload date:
- Size: 78.9 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
e883dc7b8179bf21c844593a947959c34c99156041179b174129f3365467e6a2
|
|
| MD5 |
26bf844851a678216f4d990b869d2ea8
|
|
| BLAKE2b-256 |
11869dfdeb8689bac61c7cb31a6e4dbd68e9687c1621fa46cf1c3a72b9e779b6
|
Provenance
The following attestation bundles were made for comfortkit-0.5.0-py3-none-any.whl:
Publisher:
publish.yml on K2alyan/comfortkit
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
comfortkit-0.5.0-py3-none-any.whl -
Subject digest:
e883dc7b8179bf21c844593a947959c34c99156041179b174129f3365467e6a2 - Sigstore transparency entry: 1392627546
- Sigstore integration time:
-
Permalink:
K2alyan/comfortkit@e30eb998aef01d2cd6343dafd9e551e2edd2a3e6 -
Branch / Tag:
refs/tags/v0.5.0-release - Owner: https://github.com/K2alyan
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@e30eb998aef01d2cd6343dafd9e551e2edd2a3e6 -
Trigger Event:
push
-
Statement type: