Skip to content

Bump 1.0.7#19

Merged
cayossarian merged 34 commits intomainfrom
bump_1.0.7
Mar 29, 2026
Merged

Bump 1.0.7#19
cayossarian merged 34 commits intomainfrom
bump_1.0.7

Conversation

@cayossarian
Copy link
Copy Markdown
Member

No description provided.

Design for replacing the scattered energy balance logic in the
simulator engine with a modular, component-based energy system.
Components (GridMeter, PVSource, BESSUnit, LoadGroup) resolve on
a PanelBus with role-based ordering and conservation enforcement.
12-task phased plan: build energy system in isolation (Phase 1),
wire into engine (Phase 2), eliminate old paths (Phase 3), and
remove dead code (Phase 4).
Remove BSEE update()/integrate_energy() and related dead constants,
fields, and imports now that EnergySystem handles all power-flow
resolution and SOE bookkeeping.  The engine syncs results back to
BSEE each tick; BSEE retains only identity and grid-state properties.
The BatteryStorageEquipment class was a thin facade that held identity
properties, schedule resolution, and grid state derived from
forced_offline.  All of these are now provided directly by BESSUnit
(identity + schedule) and EnergySystem (grid_state, dominant_power_source,
islandable).  The circular state-sync from SystemState back to BSEE each
tick is eliminated — the engine reads values straight from the energy
system's resolved state.
Circuits without a recorder_entity are user-added and did not exist
in the baseline system.  The Before pass now returns 0 power for
these circuits instead of letting the behavior engine synthesise
values that leak into the Before graph.
Replace _aggregate_modeling_at_ts with two focused methods:
- _collect_circuit_powers_at_ts: pure per-circuit power collection
- _powers_to_energy_inputs: converts circuit powers to PowerInputs

Both Before and After passes now follow the same pattern: collect
circuit powers, feed into an EnergySystem, read derived values.
The Before pass only includes recorder-backed circuits (baseline
system). The After pass includes all current circuits. No if-guards
needed — circuit participation is determined by set membership.
Remove bess_requested_w from PowerInputs — the energy system now
sets discharge/charge power to the max inverter rate for the
scheduled state. The GFE throttle and SOE bounds naturally limit
actual power to what the home needs, matching how a Powerwall or
similar residential BESS operates.
New modes: Self-Consumption (default, discharge to offset grid
import, charge from PV excess only), Time-of-Use (user-set hourly
schedule), Backup Only (hold at full SOC, discharge during outages).

Removed solar-gen and solar-excess modes and all associated two-pass
tick machinery. Self-consumption charges only from actual PV excess,
never from grid.
The After label was hiding the breakdown when exported < 0.5 kWh,
which happens when BESS absorbs all PV excess. Now both Before and
After always show the full breakdown for clarity.

Also hide discharge presets, active days, and hourly schedule when
charge mode is Self-Consumption or Backup Only — those controls
only apply to Time-of-Use mode.
Battery entities now show only installation-relevant fields: Name,
Nameplate Capacity, Backup Reserve, Charge Power, Discharge Power.
Hidden: Energy Profile (Typical/Min/Max Power), Priority, Relay
Behavior — these are fixed for a real BESS installation.
The wait cursor was only applied to individual trigger elements, so
users could still click elsewhere during start/stop/restart, clone,
and modeling setup.  Add a body.page-busy class that blocks all
interaction page-wide for these slow paths.
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR bumps to 1.0.7 and replaces the simulator’s scattered grid/PV/BESS power-flow logic with a new component-based EnergySystem used by snapshots, dashboard summary, and modeling, alongside UI updates for new battery charge modes and improved busy-state handling.

Changes:

  • Introduce span_panel_simulator.energy (bus/components/types/system) plus comprehensive 3-layer test suite for dispatch/islanding/SOE behavior.
  • Refactor DynamicSimulationEngine to delegate power-flow resolution to EnergySystem (and remove legacy BSEE path).
  • Update dashboard UI/config defaults to new BESS charge modes (self-consumption/custom/backup-only) and add page-level busy overlay.

Reviewed changes

Copilot reviewed 28 out of 28 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
src/span_panel_simulator/engine.py Integrates EnergySystem into snapshots, dashboard summary, and modeling passes; removes solar-excess two-pass logic.
src/span_panel_simulator/energy/types.py Adds core dataclasses/enums for energy dispatch inputs/outputs and configs.
src/span_panel_simulator/energy/components.py Implements Grid/PV/BESS/Load components including SOE integration and scheduling.
src/span_panel_simulator/energy/bus.py Adds role-ordered bus resolution with conservation accounting.
src/span_panel_simulator/energy/system.py Implements EnergySystem.from_config() and tick() dispatch including charge modes and islanding behavior.
src/span_panel_simulator/energy/__init__.py Re-exports energy public API.
src/span_panel_simulator/bsee.py Deleted legacy BSEE implementation.
src/span_panel_simulator/behavior_mutable_state.py Removes solar-excess mutable state field.
tests/test_energy/test_components.py Layer 1 unit tests for components and SOE integration.
tests/test_energy/test_bus.py Layer 2 integration tests for bus conservation and dispatch interactions.
tests/test_energy/test_scenarios.py Layer 3 scenario tests for islanding, charge modes, modeling deltas, and independence.
tests/test_energy/__init__.py Test package init.
src/span_panel_simulator/dashboard/defaults.py Updates default battery behavior to self-consumption and new inverter rates/schedules.
src/span_panel_simulator/dashboard/config_store.py Updates charge-mode defaults and validation to new modes.
src/span_panel_simulator/config_types.py Updates BatteryBehavior.charge_mode literal types.
src/span_panel_simulator/dashboard/templates/partials/battery_profile_editor.html Updates charge mode options and hides schedule UI when not applicable.
src/span_panel_simulator/dashboard/templates/partials/entity_edit.html Hides fields that don’t apply to battery entities.
src/span_panel_simulator/dashboard/templates/partials/modeling_view.html Always displays imported/exported breakdown in labels.
src/span_panel_simulator/dashboard/templates/partials/panels_list_rows.html Adds page-level busy state during clone.
src/span_panel_simulator/dashboard/templates/base.html Adds global HTMX busy-state handling and extends busyFetch().
src/span_panel_simulator/dashboard/static/dashboard.css Adds body.page-busy interaction blocking + cursor styling.
span_panel_simulator/CHANGELOG.md Adds 1.0.7 release notes.
docs/superpowers/specs/2026-03-28-component-energy-system-design.md Adds design spec for the component energy system.
docs/superpowers/plans/2026-03-28-component-energy-system.md Adds implementation plan for the refactor.
Comments suppressed due to low confidence (1)

src/span_panel_simulator/engine.py:1396

  • The compute_modeling_data() docstring still references applying “BSEE” in the After pass, but this implementation now builds and ticks EnergySystem instances instead. Please update the docstring to match the current behavior so future changes don’t rely on outdated assumptions.
    async def compute_modeling_data(self, horizon_hours: int) -> dict[str, Any]:
        """Compute Before/After modeling data over recorder history.

        Performs **read-only** passes — no runtime state is mutated.
        **Before** uses HA recorder replay wherever ``recorder_entity`` data
        exists (ignores ``user_modified``), with site power **without** BESS.
        **After** uses current templates (SYN / overrides) and applies BSEE
        for grid and battery traces.

PV curtailment: when islanded, hybrid inverters now reduce output to
match load + achievable BESS charge, mirroring real MPPT setpoint
behavior. Prevents unbalanced bus states during grid outages with
high solar production.

Bug fixes from code review:
- SOE power limits now return watts (not watt-seconds) using
  conservative bounds over the max integration interval
- islandable flag consulted in tick() for PV online decisions
- Custom TOU mode resolves schedule internally instead of relying
  on externally pre-computed state
- Hybrid inverter detection reads template priority field to match
  config_store persistence
- Modeling docstring updated to reflect EnergySystem usage

Tightened energy module encapsulation: PowerInputs no longer carries
bess_scheduled_state — all scheduling is resolved inside
EnergySystem.tick(). Engine only provides raw measurements.
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 30 out of 31 changed files in this pull request and generated 3 comments.

Comments suppressed due to low confidence (1)

src/span_panel_simulator/engine.py:1518

  • In compute_modeling_data(), battery_power_arr is derived from EnergySystem.tick(), but per-circuit series (circuit_arrays_after) are still populated from powers_a (behavior engine output). For battery circuits, the behavior engine returns near-idle power in self-consumption/backup-only, so overlays and per-circuit graphs can contradict the battery series. Consider overwriting the bidirectional (battery) circuit’s entry in powers_a (and possibly powers_b) with the energy system’s effective battery magnitude so circuit overlays stay consistent with the resolved system state.
            # --- After pass: all current circuits ---
            powers_a = self._collect_circuit_powers_at_ts(
                ts,
                cloned_behavior,
                all_circuit_ids,
                use_recorder_baseline=False,
            )
            inputs_a = self._powers_to_energy_inputs(powers_a)

            state_a = after_energy_system.tick(ts, inputs_a)
            grid_after = state_a.grid_power_w
            batt_after = state_a.bess_power_w
            if state_a.bess_state == "discharging":
                batt_after = -batt_after

            site_power_arr.append(round(grid_before, 1))
            pv_before_arr.append(round(pv_before, 1))
            grid_power_arr.append(round(grid_after, 1))
            pv_after_arr.append(round(state_a.pv_power_w, 1))
            battery_power_arr.append(round(batt_after, 1))
            battery_before_arr.append(round(batt_before, 1))

            for cid in self._circuits:
                circuit_arrays_before[cid].append(round(powers_b.get(cid, 0.0), 1))
                circuit_arrays_after[cid].append(round(powers_a.get(cid, 0.0), 1))

Clone dialog now checks for filename collisions before prompting,
pre-filling a safe auto-suffixed name. If the user manually types an
existing filename, an in-page modal with an explicit Overwrite button
appears instead of silently replacing the file. Clone-from-panel also
gains a confirmation step with overwrite/rename options.

PV curtailment during islanding is now reflected back to producer
circuit snapshots so dashboard power readings stay consistent with
the resolved energy system state. Hybrid inverter type derivation
fixed to use PV config rather than template priority.
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 30 out of 31 changed files in this pull request and generated 2 comments.

Defines architecture for OpenEI URDB integration, simulator-wide
rate caching, cost engine, and modeling view cost display.
Modeling view: replace inline kWh labels with a summary table above each
chart showing Full Horizon and Visible Range rows.  The After table
includes a Difference column with color-coded delta from Before.

Fix BESSUnit.integrate_energy() to loop in sub-steps of
_MAX_INTEGRATION_DELTA_S instead of capping and discarding the
remainder, so 3600s modeling steps integrate correctly.

Update compute_modeling_data() docstring to reflect current EnergySystem
usage.
10-task plan covering rate types, resolver, cost engine, cache,
OpenEI client, API endpoints, engine wiring, and modeling view UI.
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 30 out of 31 changed files in this pull request and generated 5 comments.

Comments suppressed due to low confidence (1)

src/span_panel_simulator/engine.py:1120

  • During forced grid-offline shedding, all bidirectional circuits are exempted as “battery” (continue). This will incorrectly exempt EVSE circuits (also bidirectional) from shedding and from being zeroed when the panel should be dead. Gate this exemption on battery_behavior.enabled (or on _find_battery_circuit()/device_type) rather than energy_mode == "bidirectional".
                    # Battery: never shed
                    if circuit.energy_mode == "bidirectional":
                        continue

…uits

Add _is_battery_circuit() to identify the configured BESS by checking
battery_behavior.enabled.  Both _collect_power_inputs() and
_powers_to_energy_inputs() now use this instead of blanket
energy_mode != "bidirectional", so other bidirectional circuits
(e.g. EVSE with V2G) are correctly treated as load.
The difference column now compares imported kWh (what the user pays
for) between Before and After, rather than net energy.  A reduction
in imports shows green, an increase shows red.
@cayossarian cayossarian merged commit 89a1d8a into main Mar 29, 2026
2 checks passed
@cayossarian cayossarian deleted the bump_1.0.7 branch March 29, 2026 04:09
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