Scenario testing
SkillDev toolsEnd-to-end scenario tests in tests/scenarios/ — creating a scenario from a user's diagnostics download, running them, updating outputs.json snapshots, and reading a scenario diff. Use when a scenario test fails, when reproducing a user-reported optimization result, or when adding a regression scenario.
Available today. Use it from your connected AI after setup.
No other account needed.
Connect ahel once, and every AI you use reads what you have installed.
Then ask your AI: use the Scenario testing skill
What this skill tells your AI
The instructions your AI receives, as published by hass-energy/haeo in .agents/skills/scenarios/SKILL.md and read by ahel’s review.
Scenario tests are end-to-end integration tests with realistic configurations and time-frozen states.
Structure
tests/scenarios/
├── test_scenarios.py # Centralized parameterized test runner
├── conftest.py # Shared fixtures
├── syrupy_json_extension.py # Snapshot format
├── visualization.py # Debugging visualizations
└── scenario*/
├── config.json # Hub and element configuration
├── environment.json # Timestamp for time freezing
├── inputs.json # HA sensor states to inject
└── outputs.json # Expected sensor outputs (snapshot)
Auto-discovery
Test runner automatically discovers all scenario*/ folders using Path.glob("scenario*/").
No registration needed - just create a new folder.
Scenario files
config.json
Hub configuration and element participants:
{
"tier_1_count": 12,
"tier_1_duration": 5,
"participants": {
"Battery": {
"element_type": "battery",
"name": "Battery"
}
}
}
environment.json
Captured runtime context. The optimization_start_time is used as the test's freeze timestamp:
{
"ha_version": "2024.1.0",
"haeo_version": "0.1.0",
"timezone": "UTC",
"diagnostic_request_time": "2024-01-15T12:00:00+00:00",
"diagnostic_target_time": null,
"optimization_start_time": "2024-01-15T12:00:00+00:00",
"optimization_end_time": "2024-01-15T12:00:00+00:00",
"horizon_start": "2024-01-15T12:00:00+00:00"
}
inputs.json
Home Assistant sensor states to inject before optimization. Array of state objects with keys:
entity_id: Entity ID stringstate: State value as stringattributes: Object withunit_of_measurement,forecast, etc.
[
{
"entity_id": "sensor.solar_power",
"state": "3500",
"attributes": {
"unit_of_measurement": "W"
}
}
]
outputs.json
Snapshot of expected sensor outputs after optimization. This file is generated and updated by the test runner.
Time freezing
The test runner extracts the timestamp from environment.json and uses freezegun for deterministic results.
All datetime operations during the test see this frozen time.
Running scenarios
Scenarios carry the scenario marker and are deselected from the default test run:
# Run all scenarios
uv run pytest -m scenario
# Run specific scenario
uv run pytest -m scenario -k scenario1
# Update snapshots
uv run pytest -m scenario --snapshot-update
Build the frontend card first
Scenario tests render topology SVGs by shelling out to the card's scenario export script.
Without a build, every scenario fails with Card export script not found, which reads like an optimizer regression and is not one:
npm --prefix frontend/haeo-forecast-card ci
npm --prefix frontend/haeo-forecast-card run build
CI does this before its scenario job, and uv run check does it automatically.
Running scenarios by hand means doing it yourself once.
Creating new scenarios
The usual path is a user's diagnostics download, which becomes a regression test directly:
- Ask the user for Settings → Devices & Services → HAEO → Download Diagnostics
- Create
tests/scenarios/<name>/and save the download as<name>/scenario.json - Run the scenario suite once — the runner splits the unified file into the four files and deletes the original
- Review the generated
outputs.jsonand commit
Name the folder for the behavior under test rather than the next number in sequence, since discovery is a glob.
To hand-author a scenario instead:
- Create new folder:
tests/scenarios/scenario_name/ - Add
config.jsonwith hub and element configuration - Add
environment.jsonwith freeze timestamp - Add
inputs.jsonwith sensor states to inject - Run tests -
outputs.jsonwill be generated - Review and commit the generated outputs
Snapshot format
Each scenario has its own outputs.json file capturing the sensor states after optimization.
Snapshots include state values, attributes, forecasts, and metadata.
Reading a scenario diff
A changed outputs.json means optimizer behavior changed.
Decide first whether that is intended — --snapshot-update is not a way to make a test pass.
- Values shift slightly everywhere: usually a solver tie-break or blend weight change. Confirm the objective value is unchanged before accepting.
- One element's sensors change: narrow to that adapter's
outputs()or its model element. - Shadow prices change but power does not: a constraint was added, removed, or reformulated. The primal solution is the same but the duals moved.
- Optimization status becomes failed: the model is infeasible. Look for a newly over-constraining constraint or a policy compilation change.
Debugging scenarios
Visualizations are regenerated into scenario*/visualizations/ on each run and are deterministic enough to commit.
Open them to see the network topology and the dispatch plan.
To optimize a scenario without Home Assistant in the loop:
uv run diag --file tests/scenarios/scenario1/ # rerun the optimization
uv run diag --file tests/scenarios/scenario1/ --compare # stored vs recomputed, side by side
uv run diag --file tests/scenarios/scenario1/ -o # stored outputs only
To browse the same data in a live Home Assistant instance, use uv run sim scenario1.
See local simulation.
Signals
- GitHub stars
- 65
- Forks
- 21
- Last commit
- Aug 2026
Advanced
- Catalog kind
- skill
- Gateway key
scenarios- Source
- github.com/hass-energy/haeo