feat(twin): declare topology, street layer and slack setpoint in the grid config - #50
Merged
Merged
Conversation
…grid config
The single topology re-base (bd 4os.7, 4os.14) needs a study to pin its
partition, siting and slack setpoint as data, and its streets as a file. This
commit adds the mechanism only: a config that declares none of it builds
exactly what it built before.
- config["topology"]: lv_assignment, max_customers_per_transformer (a cited
limit that wins over the sized count), block_penalty_km2,
snap_transformers_to_streets, street_layer {path, sha256}. Unknown keys and
contradictory options are refused by name before any IO. Explicit builder
arguments still override the config.
- config["external_grid"]["vm_pu"]: the slack setpoint, 1.0 pu when absent,
bounded to [0.9, 1.1]. _build_uniform_pandapower_network copied only part of
the config and would have dropped this block; the offline build test caught
that.
- Street layer on disk: write_street_snapshot / tools/snapshot_streets.py write
the OSM layer once; load_street_snapper_from_file reads it with no network and
refuses a digest mismatch. Because the digest sits in the config, a changed
layer changes the config hash every topology cache keys on.
- The validation report gains topology_options only when the config declares
topology/external_grid or an argument overrides it; lv_assignment gains
capacity_source (declared or sized_count).
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
JSON has no comments, and a declared per-transformer limit should say where it comes from. The key carries no behaviour; a test pins that options with and without a note resolve equal. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
PR CI found two tests (test_real_geojson, test_with_geojson_generator) whose configs carried an older external_grid shape: voltage_kv, va_degree, p_mw, q_mvar. Nothing in the repository reads any of them; the builder always created the slack at 1.0 pu. The new validator refused them as unknown keys. They stay refused, since accepting keys that configure nothing is the trap the validator exists for, but the error now names them as never read, so a config in that shape learns it can delete them. The two test configs drop the dead block, which changes nothing they build: the slack stays at 1.0 pu. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
nilsonfh
force-pushed
the
feat/topology-rebase-config
branch
from
September 15, 2026 19:30
3a31c16 to
7b78363
Compare
nilsonfh
added a commit
that referenced
this pull request
Sep 16, 2026
…it moves (#52) * chore(rebase): declare the Quebec-grounded topology on the SDK and flagship configs The data half of the single topology re-base (bd 4os.7, 4os.14, 4os.6). The flagship's pins and CALIBRATION entry follow in this branch after the full run; nothing here is re-pinned yet. - ev_hosting_flex synthetic_network_config.json: - capacitated LV assignment, at most 10 homes per MV/LV unit; - block penalty 0.005 and street siting, both from a pinned street layer; - external_grid.vm_pu 1.04. The limit comes from Hydro-Quebec's own guide (100 kVA serves up to ten houses), cross-checked on metered HQ electric-heated homes (100 kVA at nameplate: 9 at P95, 11 at P50). The study rates each unit from its kVA ladder, so the partition limit is the largest residential unit's. 1.04 is the slackVmPu the study already declares. - inputs/streets.geojson: 5103 drive_service segments written by tools/snapshot_streets.py, sha256 declared in the config, with ODbL attribution in inputs/README.md and a streetLayer entry in project.yaml. - configs/grid/config.json (SDK default): - capacitated, at most 20 per 210 kVA unit (20 metered HQ electric-heated homes peak at 190.6 kW P95 against 205.8 kW usable); - external_grid 1.04; - no street layer, since the config serves arbitrary footprints. Removes sizing_capacity_kva and mv_hv.count, which nothing reads (bd 4os.6). - Tests that copy either config for an unrelated purpose now strip its topology block, so they keep testing what they tested. Measured through PR #50's API on the real footprints: - flagship: homes per unit 1-10 (mean 5.99, unchanged), forced block crossings 308 -> 38, 503 of 540 clusters inside one block, every transformer on a street; - SDK default at 1.04 pu: 0 transformers above 100%, 0 LV buses below 0.95. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(ev_hosting_flex): stop declaring outputs/json as an output of prepare_workspace prepare_workspace runs `gridalyn project prepare-workspace`, which creates the SDK's default output directories (data, figures, manifests, operations, reports, cache). outputs/json is not one of them. The stage declared it anyway, and since the runner enforces declared outputs, a run on a clean tree fails at stage 1: "stage 'prepare_workspace' exited 0 but did not produce outputs/json". Every run so far used a checkout where an earlier run had already left outputs/json behind, so none of them saw the failure. The first cold run in a fresh worktree did (re-base run, eff31e8b). Nothing reads the directory before a stage writes into it: every writer goes through ProjectScript.write_json, which creates the parent. The declaration was false, so it is removed rather than made true by creating an empty directory. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * chore(ev_hosting_flex): re-base 42 pins on the topology re-base run, and record why One cold full run from committed code: 25 stages, no stage filter, manifest completed, git_commit 6de591bd, 2026-09-15 19:02-22:26Z, in a dedicated worktree. Regression is 94/94 valid against the re-based pins, the flagship sweep is 157 passed, and check_calibration_claims gates 33 claims OK. WHAT MOVED (42 of 94). Not the hosting headlines: annual.*, cred.*, insurance.*, coldcoupling.* and flexincentive.* are value-identical, because the worked example is still a 6-home 71.25 kW physical twin (idx-0 now, idx-10 before). What moved is everything computed over the fleet and over the whole network: pf 6, netchar 5, cluster 6, perf 2, congestion 3, phase 2, voltage 2, voltage_net 4, nonwires 4, fleet 8. The two numbers to carry: voltage_net.p_undervolt_at_ref 0.0 -> 0.11273 (first risk 1.563 -> 0.943 EV/home) fleet.n_at_risk_at_1ev_static 500 -> 506 WHY. The four model changes of bd 4os.7 / 4os.14, declared in the study's grid config: a capacity-limited LV partition at 10 homes per unit (Hydro-Quebec's subdivision guide, cross-checked on metered HQ data), transformers sited on the street instead of on the building they serve, a block penalty that cuts partition-forced block crossings 308 -> 38, and a slack declared at 1.04 pu instead of an implicit 1.0. The undervoltage move is the honest consequence of modelling service drops as conductor rather than as 1 m stubs. RECORDED. CALIBRATION.md carries a dated section with the evidence, the per-family moves and the limits (including that per-change attribution was NOT measured: the four moved together in one run). REBASE_LOG.md carries the entry and the new digest. README findings that quoted moved numbers are updated. tests/test_ev_hosting_flex_phase.py asserted a balanced min voltage <= 1.01 pu, which pinned the old implicit 1.0 slack. It now bounds by the declared setpoint. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
First half of the single topology re-base (bd 4os.7, 4os.14). Mechanism only: a config that declares none of this builds exactly what it built before. Measured on the real footprints through this branch: the undeclared SDK and flagship configs give today's numbers (43 / 134.6% / 2342 buses < 0.95; 209 / 180.7% / 2283), and #42's explicit arguments give #42's numbers.
What a grid config can now declare
topologylv_assignment:kmeansorcapacitatedmax_customers_per_transformer: a limit a study can cite, which wins over the sized countblock_penalty_km2snap_transformers_to_streetsstreet_layer {path, sha256}: path relative to the footprints file's directoryUnknown keys and contradictory options are refused by name, before any IO. Explicit builder arguments still override the config.
external_grid.vm_pu: the slack setpoint. It defaults to 1.0 pu and is bounded to [0.9, 1.1]._build_uniform_pandapower_networkcopied only part of the config and would have dropped this block silently; the offline build test caught that.Streets as a pinned input
A live OSM fetch is not reproducible.
write_street_snapshot(andtools/snapshot_streets.py) writes the layer once, deterministically: the same bytes under any file name, and list-valued OSM fields flattened so OGR does not skip them on read.load_street_snapper_from_filereads it with no network and refuses a digest mismatch. Because the digest lives in the config, a changed layer also changes the config hash every topology cache keys on.Report
topology_optionsonly when the config declarestopologyorexternal_grid, or an argument overrides them.lv_assignmentgainscapacity_source(declaredorsized_count).Why the limit is declared: the Quebec evidence (bd 4os.7)
The flagship already sizes each unit's kVA from a ladder in-study, so its partition limit is the largest residential unit's count, not its 75 kVA placeholder. The config values and the flagship re-base come in a follow-up PR.
Checks
tests/test_topology_config.py(offline, hand-built streets), capacitated assignment, street siting, the generator, network adapters, builder relocation, flagship topology, hygiene, doc, import-hygiene, layer-direction and twin test files.gridalyn120 = baseline.🤖 Generated with Claude Code