feat(web): billing-demand card for C&I sites - #852
Conversation
The 5 kW MaxCommandW fallback was a compile-time constant, silently clamping any C&I-scale battery whose driver lacked explicit limits. site.max_command_w overrides it; values above 5000 require the new site.profile: commercial so a typo can't lift a home site's clamp. Resolution order everywhere: driver limit, site default, constant. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Signed-off-by: Sanjin Naidu <sanjin@sanrowconsulting.com>
New pure go/internal/tariff package: seasonal peak/standard/offpeak bands per day class (holidays price as sundays), minute-accurate window resolution, planner-slot rendering with time-weighted rates, demand-window flags and billing-cycle anchoring. Config gains the tariff: block plus site.currency, site.nmd_kva, site.assumed_power_factor and site.backup_reserve. Schema and interpreter only — planner/dispatch wiring lands separately. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Signed-off-by: Sanjin Naidu <sanjin@sanrowconsulting.com>
SA C&I demand charges bill on kVA; the site meter's real power alone can't see it. SiteApparentPowerVA picks the best available method: per-phase V×I (currents the fuse guard already requires, measured voltage when fresh), sqrt(P²+Q²) from reactive telemetry in either signed-net or DSMR import/export spellings, else |P|/assumed_power_ factor. Stale metrics fall through so they can't fabricate demand. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Signed-off-by: Sanjin Naidu <sanjin@sanrowconsulting.com>
Integrates site apparent power over clock-aligned utility windows (sample-and-hold, 30 min default), classifies each interval against the tariff's demand bands, and records the highest counted interval per billing cycle as the demand-charge peak. Persists intervals and the peak so a restart mid-cycle keeps the peak-so-far; only observes while the site meter is fresh so gaps never fabricate demand. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Signed-off-by: Sanjin Naidu <sanjin@sanrowconsulting.com>
The contractual boundary next to the fuse card's physical one: running demand window with band and billed state, billing-cycle peak, NMD utilization with near/over coloring, and demand cost so far. Pure view-model in demand-math.js under node --test; the element removes itself when /api/demand 404s so residential dashboards are untouched. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Signed-off-by: Sanjin Naidu <sanjin@sanrowconsulting.com>
|
Blocking web finding: the card inserts This UI also depends on the demand values from #848, which currently count export and gaps incorrectly. After those fixes, this needs a rendered browser check and screenshot; source tests alone do not establish the result. |
|
Thank you @Sanjin-Maker for taking the work through to the UI. The UI must come after the tariff values are accepted. This version also sends the configurable currency value through We are closing this branch with the stack. A future UI issue should link the accepted API contract, use safe text rendering and include rendered checks for normal, missing and invalid states. |
Summary
Stacked on #848 (
/api/demand) — review the last commit until it lands. @erikarenhill for /web/.Dashboard card for tariff-configured C&I sites, placed beside the fuse card deliberately: the fuse is the site's physical boundary, NMD is its contractual one.
FtwElement, shadow styles from:roottokens (both themes), no dependencies. View-model math lives indemand-math.js, pure and covered bynode --test./api/demand404s and the element removes itself — dashboard byte-identical.Not yet verified visually against a live tariff-configured stack (needs a C&I dev config); the view-model derivations are unit-tested and the markup follows
ftw-savings-card's patterns. Happy to attach a screenshot once a commercial-profile sim config exists.Verification
npm test: 262 pass (11 new: utilization/progress, near/over thresholds, uncounted-window immunity, new-peak flag, no-NMD and empty payloads, formatting, cost). DCO signed.🤖 Generated with Claude Code