Skip to content

chore(rebase): the Quebec-grounded topology re-base, and the 42 pins it moves - #52

Merged
nilsonfh merged 3 commits into
mainfrom
feat/topology-rebase-data
Sep 16, 2026
Merged

nilsonfh merged 3 commits into
mainfrom
feat/topology-rebase-data

Conversation

@nilsonfh

Copy link
Copy Markdown
Contributor

The data half of the single topology re-base (bd 4os.7, 4os.14, 4os.6), on the mechanism merged in #50. This one moves the flagship's numbers: 42 of its 94 pins, re-based on one cold full run and recorded with their rationale.

What the study now declares

projects/ev_hosting_flex/inputs/synthetic_network_config.json:

  • topology.lv_assignment: capacitated with max_customers_per_transformer: 10
  • topology.block_penalty_km2: 0.005 and snap_transformers_to_streets: true
  • topology.street_layer: streets.geojson pinned by sha256
  • external_grid.vm_pu: 1.04 — the setpoint studyConfig.slackVmPu already declared for the study's own solves

configs/grid/config.json (SDK default): capacitated at 20 per 210 kVA unit, external_grid.vm_pu 1.04, no street layer (it serves arbitrary footprints). It also drops sizing_capacity_kva and mv_hv.count, which nothing reads (bd 4os.6).

Why 10 homes, and not a formula

Three independent sources agree, and none of them is this feeder:

Source Says
Hydro-Québec, subdivision design guide a 100 kVA unit "pourra desservir jusqu'à dix maisons"
datasets/hq, 573 metered electric-heated homes 100 kVA at nameplate holds 9 homes (P95) to 11 (P50), by annual hourly coincident peak
Vérificateur général 2022 + HQ 2024 filings ~680,000 aerial transformers for 4.6 M subscriptions: at most ~6-7 customers each, against this fleet's mean of 5.99

The study rates each unit from its own transformerKvaLadder, so its partition limit is the largest residential unit's, not the 75 kVA placeholder's.

What the re-base did to the network

before after
homes per MV/LV unit 1-12 1-10 (mean 5.99, unchanged)
block crossings forced by the partition 308 38
clusters inside a single block 244 / 540 503 / 540
transformer position on the building it serves on the street
slack implicit 1.0 pu, neutral taps declared 1.04 pu

What it did to the results

Hosting headlines did not move: firm 11 EVs, flexible 16, every cred.* and insurance.* pin value-identical, because the worked example is still a 6-home 71.25 kW physical twin.

The two numbers to carry:

  • voltage_net.p_undervolt_at_ref 0.0 → 0.113, first risk 1.563 → 0.943 EV/home, binding bus on a 10-home cluster. The old "no undervoltage risk at 1 EV/home" was a property of service drops modelled as 1 m stubs.
  • fleet.n_at_risk_at_1ev_static 500 → 506, with deferrals 73 → 66; under hourly_kt, 185 → 199 and 182 → 195. Deferral value rises with them: nonwires.total_deferral_npv 6,359.52 → 14,448.16 $.

Losses rise 3.24% → 4.15% at 0 EV: the new LV conductor being modelled rather than assumed away.

Verification

  • One cold run from committed code: 25 stages, no stage filter, manifest completed, git_commit 6de591bd, 19:02-22:26Z, in a dedicated worktree with PYTHONPATH forced to it.
  • Regression 94/94 valid against the re-based pins; flagship sweep 157 passed; check_calibration_claims --check gates 33 claims OK; sense-check valid, score 1.0.
  • CALIBRATION.md carries a dated section with the evidence, the per-family moves and the limits — including that per-change attribution was not measured, since the four changes moved together in one run.
  • REBASE_LOG.md carries the entry and the new digest.

Also here

  • fix(ev_hosting_flex): prepare_workspace declared outputs/json, which project prepare-workspace never creates. Every earlier run reused a checkout where a previous run had left the directory behind; the first cold run in a fresh worktree failed at stage 1. The false declaration is removed.
  • 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.

Follow-ups, not in this PR

  • The flagship-reproduce verification receipt: its gate requires the commit it names to lead to HEAD, and a squash merge discards this branch's commits. It gets updated to the merge commit right after this lands.
  • Rebuilding the shipped default twin instance on the new SDK default config (bd 4os.6).

🤖 Generated with Claude Code

nilsonfh and others added 3 commits September 15, 2026 22:47
…agship 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>
…pare_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>
…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>
@nilsonfh
nilsonfh merged commit 9781415 into main Sep 16, 2026
7 checks passed
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.

1 participant