Metadata-Version: 2.4
Name: comfortkit
Version: 0.5.0
Summary: Thermal comfort prediction API — PMV, adaptive ASHRAE 55, EN 16798 — with uncertainty bounds and a deployable REST endpoint
Project-URL: Homepage, https://github.com/K2alyan/comfortkit
Project-URL: Documentation, https://K2alyan.github.io/comfortkit
Project-URL: Repository, https://github.com/K2alyan/comfortkit
Project-URL: Issues, https://github.com/K2alyan/comfortkit/issues
Project-URL: Changelog, https://github.com/K2alyan/comfortkit/blob/main/CHANGELOG.md
Author-email: Kalyan Kottapalli <kalyan.kottapalli@poppy.com>
License: MIT
License-File: LICENSE
Keywords: ASHRAE 55,EN 16798,IEQ,PMV,building science,decarbonization,indoor environment quality,thermal comfort
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: Intended Audience :: Science/Research
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Scientific/Engineering
Classifier: Topic :: Scientific/Engineering :: Atmospheric Science
Requires-Python: >=3.10
Requires-Dist: numpy<2.3,>=1.21
Requires-Dist: pydantic>=2.0
Requires-Dist: pythermalcomfort>=3.9
Provides-Extra: api
Requires-Dist: fastapi>=0.111; extra == 'api'
Requires-Dist: httpx>=0.27; extra == 'api'
Requires-Dist: uvicorn[standard]>=0.29; extra == 'api'
Provides-Extra: dev
Requires-Dist: fastapi>=0.111; extra == 'dev'
Requires-Dist: httpx>=0.27; extra == 'dev'
Requires-Dist: ipykernel; extra == 'dev'
Requires-Dist: lightgbm>=4.3; extra == 'dev'
Requires-Dist: mapie>=1.3; extra == 'dev'
Requires-Dist: mypy>=1.10; extra == 'dev'
Requires-Dist: onnxmltools>=1.12; extra == 'dev'
Requires-Dist: onnxruntime>=1.18; extra == 'dev'
Requires-Dist: pre-commit>=3.7; extra == 'dev'
Requires-Dist: pyarrow>=14.0; extra == 'dev'
Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
Requires-Dist: pytest-cov>=5.0; extra == 'dev'
Requires-Dist: pytest>=8.0; extra == 'dev'
Requires-Dist: ruff>=0.4; extra == 'dev'
Requires-Dist: scikit-learn>=1.4; extra == 'dev'
Requires-Dist: skl2onnx>=0.5; extra == 'dev'
Requires-Dist: uvicorn[standard]>=0.29; extra == 'dev'
Provides-Extra: docs
Requires-Dist: mkdocs-material>=9.5; extra == 'docs'
Requires-Dist: mkdocstrings[python]>=0.25; extra == 'docs'
Provides-Extra: ml
Requires-Dist: onnxruntime>=1.18; extra == 'ml'
Provides-Extra: ml-train
Requires-Dist: lightgbm>=4.3; extra == 'ml-train'
Requires-Dist: mapie>=1.3; extra == 'ml-train'
Requires-Dist: onnxmltools>=1.12; extra == 'ml-train'
Requires-Dist: onnxruntime>=1.18; extra == 'ml-train'
Requires-Dist: pyarrow>=14.0; extra == 'ml-train'
Requires-Dist: scikit-learn>=1.4; extra == 'ml-train'
Requires-Dist: skl2onnx>=0.5; extra == 'ml-train'
Description-Content-Type: text/markdown

# comfortkit

![PyPI](https://img.shields.io/pypi/v/comfortkit)
![Python](https://img.shields.io/pypi/pyversions/comfortkit)
![License](https://img.shields.io/github/license/K2alyan/comfortkit)
![CI](https://img.shields.io/github/actions/workflow/status/K2alyan/comfortkit/ci.yml?label=CI)

A Python library and REST API for thermal comfort prediction with uncertainty bounds
and personalised comfort learning.
Wraps [pythermalcomfort](https://github.com/CenterForTheBuiltEnvironment/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](https://comfort.cbe.berkeley.edu) 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

```bash
pip install comfortkit
```

```python
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

```bash
docker compose up
```

```bash
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
  }'
```

```json
{
  "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](https://github.com/CenterForTheBuiltEnvironment/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](https://comfort.cbe.berkeley.edu) 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%.

```python
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:

```python
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](https://www.kaggle.com/datasets/claytonmiller/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.

```python
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.

```bash
# 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"
  }'
```

```json
{
  "user_id": "desk-42",
  "n_votes": 1,
  "p_cooler": 0.4907,
  "prediction": 0,
  "prediction_label": "Not Cooler"
}
```

```bash
# Retrieve the personalised prediction after enough votes have accumulated
curl http://localhost:8000/v1/comfort/desk-42
```

```json
{
  "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](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](CONTRIBUTING.md). Good first issues are labelled
[`good-first-issue`](https://github.com/K2alyan/comfortkit/labels/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:

```bibtex
@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](https://github.com/CenterForTheBuiltEnvironment/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
