Skip to content

feat(web): billing-demand card for C&I sites - #852

Closed
Sanjin-Maker wants to merge 5 commits into
srcfl:masterfrom
Sanjin-Maker:agent/demand-widget
Closed

feat(web): billing-demand card for C&I sites#852
Sanjin-Maker wants to merge 5 commits into
srcfl:masterfrom
Sanjin-Maker:agent/demand-widget

Conversation

@Sanjin-Maker

Copy link
Copy Markdown
Contributor

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.

  • Running demand-integration window: kVA average, completion %, TOU band chip with a billed/unbilled marker.
  • Billing-cycle peak and utilization of NMD, with near (≥85%) / over (≥100%) color states on figures and meter; an uncounted (off-peak) window never triggers severity or the new-peak flag.
  • Demand cost so far (peak × rate, minor→major units) and a callout when the running counted window is setting a new peak.
  • Buildless per house style: vanilla custom element on FtwElement, shadow styles from :root tokens (both themes), no dependencies. View-model math lives in demand-math.js, pure and covered by node --test.
  • Residential sites: /api/demand 404s 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

Sanjin-Maker and others added 5 commits August 6, 2026 22:07
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>
@frahlg

frahlg commented Aug 7, 2026

Copy link
Copy Markdown
Member

Blocking web finding: the card inserts site.currency from config/API directly into innerHTML. That value is operator-controlled and can inject markup or script into the dashboard. Validate it as a supported ISO currency code before storage and render it as text, or escape it at the sink.

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.

frahlg commented Aug 8, 2026

Copy link
Copy Markdown
Member

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 innerHTML, and it has not had the required human browser review.

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.

@frahlg frahlg closed this Aug 8, 2026
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.

2 participants