traberphandClaude Opus 5.5 77a594c3af Add heatpump compressor cycle analysis web app
Static page that plots Home Assistant on/off history of the heatpump
compressor with pan/zoom, per-phase hover durations and statistics
(min/max/mean/median, total on-time, duty cycle) for the visible range.
English/German UI. Served by nginx in Docker with basic auth, CSP and
a healthcheck; Plotly is vendored locally.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-26 10:21:07 +02:00

Heatpump cycle analysis

A small static web app that visualises when a heat pump compressor is running, based on a Home Assistant history export (binary_sensor.* on/off states).

  • Interactive timeline (pan, zoom, mouse-wheel, quick ranges 6 h / 24 h / 3 d / 7 d)
  • Hover an on-phase to see its start, end and duration
  • Statistics for the visible range: number of phases, min / max / mean / median duration, total on-time, duty cycle, mean pause between phases
  • Table of all on-phases in the visible range
  • English / German UI (auto-detected from the browser, switchable, remembered)
  • Light / dark mode following the OS setting
  • Served by nginx in Docker, protected with HTTP basic auth; no external requests (Plotly is vendored in html/vendor/)

Quick start

# 1. put your export next to docker-compose.yml
cp /path/to/export.csv history.csv

# 2. create the login (prompts for the password)
./set-password.sh admin

# 3. start
docker compose up -d

Open http://localhost:8080 and log in.

Run set-password.sh before the first docker compose up. If nginx/.htpasswd does not exist, Docker creates an empty directory in its place and nginx rejects every login. The script removes that directory automatically; restart the container afterwards.

Configuration

Set via environment or a .env file next to docker-compose.yml:

Variable Default Meaning
HEATPUMP_PORT 8080 Host port
HEATPUMP_CSV ./history.csv CSV file served as history.csv

Change the password with ./set-password.sh <user> followed by docker compose restart. Replacing the CSV needs no restart; reload the page.

CSV format

Home Assistant's history download (History → ⋮ → Download data):

entity_id,state,last_changed
binary_sensor.heatpump_compressor,on,2026-09-17T03:00:19.068Z
binary_sensor.heatpump_compressor,off,2026-09-17T03:08:01.062Z

Only the state and last_changed columns are used; column order doesn't matter. A different CSV can also be opened ad hoc with Load CSV…. It is parsed in the browser and not uploaded.

How phases are computed

  • An on-phase starts at the first on and ends at the next state that is not on (off, unknown, unavailable, …). Repeated on rows are merged.
  • A phase still running at the end of the data is not counted.
  • Statistics include the phases that start inside the visible range. Total on-time and duty cycle are clipped to the range.
  • unknown / unavailable periods are shown as gaps in the timeline.
  • All times are shown in the browser's local time zone.

Project layout

docker-compose.yml          nginx container, volumes, healthcheck
set-password.sh             writes nginx/.htpasswd (apr1 hash via openssl)
nginx/default.conf          basic auth, gzip, CSV alias, /healthz
nginx/security-headers.conf CSP and other headers, included per location
html/index.html             markup
html/style.css              styles (light/dark tokens)
html/i18n.js                English / German strings
html/app.js                 CSV parsing, statistics, chart, UI wiring
html/vendor/                Plotly 2.35.2 + German locale

nginx/.htpasswd and history.csv are git-ignored.

Operations

  • Health: GET /healthz (no auth) returns ok; used by the Docker healthcheck.
  • TLS: the container serves plain HTTP. Basic auth sends credentials in clear text, so outside a trusted LAN put it behind a TLS-terminating reverse proxy (Traefik, Caddy, nginx, …).
  • Updating Plotly:
    v=2.35.2
    curl -fLo html/vendor/plotly.min.js        https://cdn.jsdelivr.net/npm/plotly.js-dist-min@$v/plotly.min.js
    curl -fLo html/vendor/plotly-locale-de.js  https://cdn.jsdelivr.net/npm/plotly.js@$v/dist/plotly-locale-de.js
    

Adding a language

Add an entry to I18N in html/i18n.js (copy en, translate, set locale), add a <button data-lang="xx"> in index.html, and, for translated chart controls, vendor plotly-locale-xx.js and include it in index.html.

S
Description
No description provided
Readme
1.6 MiB
Languages
JavaScript 70.2%
CSS 13.2%
HTML 8.4%
Shell 6.3%
Dockerfile 1.9%