Skip to content

Build a NetworkModelResult straight from a result file - #699

Draft
jpalm3r wants to merge 4 commits into
read-sqlitefrom
network-model-from-path
Draft

Build a NetworkModelResult straight from a result file#699
jpalm3r wants to merge 4 commits into
read-sqlitefrom
network-model-from-path

Conversation

@jpalm3r

@jpalm3r jpalm3r commented Aug 10, 2026

Copy link
Copy Markdown
Collaborator

NetworkModelResult was the only model result class that could not be built from a file. PointModelResult, GridModelResult, DfsuModelResult and even the network's own NodeModelResult all take a path, so a network workflow needed an extra import and an extra line for no reason.

network_model = ms.NetworkModelResult("model.res1d", item="WaterLevel")
multi_obs = ms.NodeObservation.from_multiple(data="calibration.dfs0", db="model.sqlite")
cc = ms.match(multi_obs, network_model)

A Network still works, unchanged.

The extension picks the constructor

ADR-012 keeps one Network constructor per modelling product, and rejected guessing the product from the file. This reads the extension anyway, one layer up, for the reason that objection does not reach here: the tables already map each extension to exactly one product — .res is EPANET's and nobody else's — and the answer is reported in mr.network. Unreadable formats come back with the refusal messages already written for them, so .out still explains where SWMM keeps its topology. The ADR records the exemption.

Network.from_mike / from_epanet stay the only way in when a file needs named companions or selective loading, and the guide says so.

An EPANET file collects its companions

An EPANET run writes up to three files, and the .res alone gives no reach lengths at all — so a network built from the path would silently be one on which reach matching cannot work. A path picks up the .resx and .inp sharing its folder and stem.

The companion validation already refuses a .resx from a different run. Since these were never asked for, a failure now names the file that was picked up and the explicit call that skips it.

Bug fix: companion names arrive in two encodings

Found on a real MIKE+ EPANET model. mikeio1d decodes .res text as UTF-8 but .resx text as the Windows ANSI codepage, so a node named ØST in one file is ØST in the other:

The '.resx' companion holds nodes ['TOELHØJVEJ1', 'TOELHØJVEJ2',
'Vandværk_Vest', 'ØST'] that are absent from the '.res' network, so
the two files do not describe the same model.

The four Danish tank names were enough to make one model look like two. This predates the path constructor — from_epanet(res, resx=...) fails identically — and the louder half was not the worse half: the merge looks companions up by the main file's name, so had validation passed, those tanks would have lost their Volume and Volume Percentage without a word.

A companion is now keyed by the main file's names. The repair is used only when it produces a name the .res actually has, so a companion from a genuinely different model still raises as before.

Notes for review

  • The path branch imports modelskill.network inside the branch, so modelskill.model.network stays importable without networkx and mikeio1d (ADR-010).
  • The encoding to re-read as is a guess — cp1252 then latin-1 — which is why a candidate is checked against the main file before it is used. A wrong guess falls back to the name as it came.
  • Tests cover the extension mapping, companion discovery with each subset of the three files, and the name repair. The encoding tests use fakes; no fixture with non-ASCII names is committed.

Stack created with GitHub Stacks CLIGive Feedback 💬

@jpalm3r jpalm3r changed the title network model from path Build a NetworkModelResult straight from a result file Aug 10, 2026
@jpalm3r jpalm3r closed this Aug 10, 2026
@jpalm3r
jpalm3r force-pushed the network-model-from-path branch from 74700cd to db99d3c Compare August 10, 2026 13:26
@jpalm3r jpalm3r reopened this Aug 10, 2026
@jpalm3r
jpalm3r force-pushed the network-model-from-path branch from 8d3d606 to 37bda9e Compare August 10, 2026 13:51
jpalm3r and others added 4 commits August 10, 2026 16:02
_network_from_path picks the constructor from the extension, and an EPANET
.res also gets the .resx and .inp sitting beside it: without the .inp no
reach has a length, so reach matching cannot work at all.

A companion the caller never asked for is named in the error when the load
fails, along with the explicit call that skips it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Every other model result class takes a path, so a network workflow needed an
extra import and an extra line for no reason. A Network object still works.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The MIKE+ example loses a line: the whole workflow now starts from a result
file path. ADR-012 records why the model result layer may read the extension
table where Network may not.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
mikeio1d decodes '.res' text as UTF-8 but '.resx' text as the Windows ANSI
codepage, so a node named 'ØST' in one file is 'ØST' in the other. Four
Danish tank names in a real EPANET model were enough to make the two files
look like different models and fail validation.

Worse when validation passed: the merge looks locations up by the main
file's name, so those nodes lost their companion quantities silently.

The repair is only used when it produces a name the main file actually has,
so a companion from a genuinely different model still raises.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@jpalm3r
jpalm3r force-pushed the network-model-from-path branch from 37bda9e to a5dec82 Compare August 10, 2026 14:02
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