Skip to content

fix(units): store watts and 0-1 fractions in core - #962

Merged
frahlg merged 4 commits into
masterfrom
si-core-units
Aug 19, 2026
Merged

fix(units): store watts and 0-1 fractions in core#962
frahlg merged 4 commits into
masterfrom
si-core-units

Conversation

@frahlg

@frahlg frahlg commented Aug 19, 2026

Copy link
Copy Markdown
Member

Summary

Björn's 18.96 kW roof showed ~2046 kW PV because a v2 POA path treated kWp as watts. A later nameplate clamp hid the display. It did not store the right unit.

This PR makes core SI:

  • Power in W, energy in Wh, irradiance in W/m², fractions in 0–1.
  • PV arrays store rated_w. kwp is a YAML load door only, then cleared.
  • SoC, plugin SoC, boost reserves, vehicle SoC, calendar EV targets, V2X envelope, and shadow SoC are 0–1. Field names drop _pct.
  • kWp and 0–100 remain only at doors: UI display, Home Assistant %, appproto permille, calendar titles (80%), Tesla verify, optimizer protocol v1 soc_pct JSON, forecast.solar URL.
  • Inbound JSON still accepts the old keys (soc_pct, kwp) and folds them. New writes use the canonical names.
  • Nameplate / 105% / fuse clamps stay as physics gates. They are not the unit fix.
  • go/internal/units plus consistency_test.go fail the build if kWp or soc_pct returns to those core structs.

HTTP contracts that change (same binary ships the web UI):

  • /api/loadpoints and schedule/boost/SoC writes: current_soc, target_soc, soc, min_battery_soc as 0–1.
  • Plan JSON already uses soc 0–1 (from the first commit).
  • V2X envelope: min_reserve_soc, departure_target_soc.

Persisted loadpoint schedules and boost leases with soc_pct: 80 hydrate as 0.80 on load.

Overlapping open PRs

These have files in common. They should rebase onto this after it lands, or say so:

Test plan

  • make verify (pre-commit)
  • make verify-all (pre-push, including linux/arm64)
  • go test ./internal/units — harness: RatedW, no kWp on forecast.Array, loadpoint/plan JSON is 0–1, legacy soc_pct:80 hydrates to 0.80
  • go test ./internal/loadpoint ./internal/telemetry ./internal/calendar ./internal/mpc ./internal/api
  • npm test (374 pass), including weather rated_w and planner 0–1 SoC fields
  • Human visual check of Settings → Weather arrays, Plan chart SoC, Loadpoints schedule/boost (CODEOWNERS: web)

Why this is not another clamp

The evening POA for 18960 W at ~108 W/m² is ~2 kW. Treating 18960 as kWp yields megawatts. Storing watts makes the nameplate gate almost idle. The gate stays for stale SQLite rows and sensor glitches.

frahlg added 2 commits August 19, 2026 13:14
PV arrays use rated_w. SoC, planner bounds, and related settings are
0-1. kWp and 0-100 exist only at UI, HA, appproto, calendar titles, and
the forecast.solar URL. Config load still folds the old keys. A units
package plus consistency tests reject kWp and soc_pct leaking back into
core structs.
Core stored EV SoC as 0-100 while the battery planner already used 0-1.
That mix, not a missing clamp, is what let percent-scale values leak
into watt math. Loadpoint state, schedules, boost leases, vehicle
telemetry, calendar intents, V2X envelopes, and shadow SoC are now
fractions. 0-100 remains only at UI, HA, appproto, calendar titles,
Tesla verify, and optimizer protocol v1. The units harness fails if
_pct names or kWp return to those core structs.

Nameplate ceilings stay as physics gates. They are not the unit fix.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 57da5e0200

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread web/heating.js
var GROUPS = [
{ title: 'Power & electrical', items: [
{ key: 'hp_energy_log_current_power_consumption', label: 'Total power now', fmt: fmtKW, info: "The whole heat pump's instantaneous electrical draw right now — compressor + fan + circulation pumps + electronics." },
{ key: 'hp_energy_log_current_power_consumption', label: 'Total power now', fmt: fmtPower, info: "The whole heat pump's instantaneous electrical draw right now — compressor + fan + circulation pumps + electronics." },

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Update the driver pin before reading bulk metrics as watts

The pinned drivers/BUNDLED_SOURCE.json commit is unchanged, and HostEnv.emitMetric still forwards metric values and units without conversion, so the currently bundled myUplink/NIBE drivers continue emitting these generic power metrics in kW. Switching the card to fmtPower therefore renders a real 2.5 kW reading as roughly 3 W, and the newly changed driver tests will fail against the populated pinned snapshot; update the source driver and pin, or normalize this boundary in core, before changing the consumer.

AGENTS.md reference: AGENTS.md:L12-L18

Useful? React with 👍 / 👎.

Comment thread web/heating.js
Comment on lines +347 to +350
function cell(wh) {
if (wh == null) return '<span class="ftw-hp-acc">accumulating</span>';
var kwh = wh / 1000;
return (Math.round(kwh * 10) / 10).toLocaleString('en-US') + ' kWh';

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Normalize existing energy samples before subtracting Wh counters

On an upgraded site with stored NIBE history, existing samples under hp_energy_consumed_kwh/hp_energy_produced_kwh remain kWh, while the proposed driver behavior writes Wh under the same metric IDs; metricID only relabels the unit and does not transform old samples. Consequently energyDeltas subtracts an old kWh baseline from a new Wh lifetime counter, and this division reports a huge phantom period total—approximately the pump's lifetime consumption—until every requested period starts after the upgrade. Migrate or scale historical samples, or introduce new metric names instead of mixing scales in one series.

Useful? React with 👍 / 👎.

Comment thread web/diagnose.js Outdated
Comment on lines +438 to +439
const socPct = socPercent(sl.soc);
const lpSocPct = socPercent(sl.loadpoint_soc);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve legacy SoC keys when rendering diagnostic history

For any diagnostic persisted before this upgrade, /api/mpc/diagnose/at returns the stored JSON verbatim, whose slots contain soc_pct and loadpoint_soc_pct rather than the new keys. Reading only sl.soc and sl.loadpoint_soc makes all historical SoC cells and chart points disappear; idle-EV snapshots are also misclassified because the new lpActive predicate ignores their legacy loadpoint SoC. Add a legacy-key fallback with percent-to-fraction conversion so the time-travel view remains usable across the upgrade.

Useful? React with 👍 / 👎.

Slot directives, live PV surplus caps, and the operator absorb cap
already stored 0-1. The Pct suffix made them look like 0-100. Rename
the fields, convert golden dump keys, and fail the units harness if
those names return.
Pinned myUplink/NIBE Lua still emit vendor kilo-units. Convert at the
host door so core stores W/Wh without waiting on a pin bump. Diagnose
reads legacy soc_pct keys; heating period totals scale mixed kWh/Wh
history.
@frahlg
frahlg merged commit f258a4d into master Aug 19, 2026
15 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant