You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
src/hapi holds 24 modules and 13,170 lines, and every one but four sits directly in the package root. The two
existing groupings are rrm/ and parameters/.
The flat root is the visible problem. rrm/ is the worse one: "rainfall-runoff model" currently spans three
different layers — the four conceptual models and their ABC, the distributed engine (distrrm.py), and the
calibration parameter distributor (parameters.py) — 3,883 lines under one name that describes only the first.
rrm/ misleads. A reader looking for the HBV models finds the spatial engine and the optimiser's parameter
mapper next to them.
Two unrelated parameters modules collide — rrm/parameters.py maps the optimiser's flat vector onto the
3D per-cell array; parameters/parameters.py downloads Beck et al. (2016) sets from FigShare. Nothing but
the name connects them, and telling them apart currently takes prior knowledge.
Affected locations
File
Lines
What it actually is
src/hapi/inputs.py
2199
four independent classes plus the raster-reading helpers
src/hapi/catchment.py
1453
CatchmentandLake
src/hapi/rrm/parameters.py
875
spatial parameter distribution
src/hapi/parameters/parameters.py
654
FigShare download
src/hapi/rrm/distrrm.py
311
the per-cell loop and spatial routing — not a conceptual model
src/hapi/runs.py vs run.py
306 / 392
validated models vs entry points; the names are a coin flip
Motivation
The grouping below is derived from the measured runtime import graph (module-scope imports only; TYPE_CHECKING
edges excluded, since they do not constrain layout), not from a taxonomy. Fan-in, highest first:
Checked mechanically against every module-scope edge: 0 upward, 0 sideways, 0 unplaced. The property this buys
is that model/ and engine/ are the same rank and never import each other — the #217 split, now enforceable as a
test or import-linter contract rather than a docstring.
Blast radius
hapi/__init__.py re-exports nothing but __version__, so the module path is the public API and every move is
breaking. Reference counts across src, tests, docs, examples:
Plus 18 docs/api/*.md mkdocstrings paths, two [[tool.mypy.overrides]] module lists, and three [project.scripts] entry points. No packaging change needed — [tool.setuptools.packages.find] include = ["hapi", "hapi.*"] already covers new subpackages.
Suggested sequencing
Fewest references first, one git mv + import rewrite per commit, suite green at each step: data → core → conceptual → simulation → engine → calibration → model. Then, as separate commits, the
two splits that need judgement rather than a move: inputs.py into five files, catchment.py into catchment.py + lake.py.
Out of Scope
Any behaviour change. Moves, renames and import rewrites only.
A permanent home for dem.py. It is slated to migrate to digital-rivers (DEM.hand() supersedes hru_hand; convert_flow_direction_to_cell_indices supersedes flow_direction_index). inputs/dem.py is a way
station, not a design decision.
Splitting results.py's rendering into simulation/rendering.py — worth doing, separate issue.
Size:L Rationale: mechanical but wide — roughly 150 referencing files across seven move commits, plus docs, mypy and
entry-point updates. The two file splits can be deferred to keep it inside L.
Definition of Done
Compatibility stance decided: break the import paths outright (house precedent — the CamelCase→snake_case
rename shipped with no shim), or start re-exporting the public names from hapi/__init__.py
The eight subpackages exist and rrm/ is gone
A test or import-linter contract asserts the rank order, and specifically that model/ never imports engine/
docs/api/*.md, the mypy overrides and the three console scripts all updated; mkdocs build --strict clean
The architecture docs are rewritten, and the note distinguishing the two parameters modules is no
longer needed
Full suite, mypy and ruff green at every commit, not just at the end
Context
src/hapiholds 24 modules and 13,170 lines, and every one but four sits directly in the package root. The twoexisting groupings are
rrm/andparameters/.The flat root is the visible problem.
rrm/is the worse one: "rainfall-runoff model" currently spans threedifferent layers — the four conceptual models and their ABC, the distributed engine (
distrrm.py), and thecalibration parameter distributor (
parameters.py) — 3,883 lines under one name that describes only the first.Problem / Current Behaviour
Three concrete costs:
hapi.protocolsexists so the run layer never importsCatchment; that decoupling(PR refactor: split the builder from the run layer in Catchment/Run/Calibration #217) is recorded only as prose in a docstring. Nothing stops the next change reintroducing the edge.
rrm/misleads. A reader looking for the HBV models finds the spatial engine and the optimiser's parametermapper next to them.
parametersmodules collide —rrm/parameters.pymaps the optimiser's flat vector onto the3D per-cell array;
parameters/parameters.pydownloads Beck et al. (2016) sets from FigShare. Nothing butthe name connects them, and telling them apart currently takes prior knowledge.
Affected locations
src/hapi/inputs.pysrc/hapi/catchment.pyCatchmentandLakesrc/hapi/rrm/parameters.pysrc/hapi/parameters/parameters.pysrc/hapi/rrm/distrrm.pysrc/hapi/runs.pyvsrun.pyMotivation
The grouping below is derived from the measured runtime import graph (module-scope imports only;
TYPE_CHECKINGedges excluded, since they do not constrain layout), not from a taxonomy. Fan-in, highest first:
The most-imported modules are not the biggest ones — they are the shared vocabulary. Any grouping that scatters
them produces upward imports.
Proposed Solution
Renames that remove real ambiguity:
runs.py→simulation/validated.py,rrm/distrrm.py→engine/distributed.py,rrm/parameters.py→calibration/distribution.py,parameters/parameters.py→data/figshare.py,hapi_warnings.py→core/silence.py,conceptual.py→conceptual/setup.py.Rank order — a module may import its own package or any lower rank:
Checked mechanically against every module-scope edge: 0 upward, 0 sideways, 0 unplaced. The property this buys
is that
model/andengine/are the same rank and never import each other — the #217 split, now enforceable as atest or import-linter contract rather than a docstring.
Blast radius
hapi/__init__.pyre-exports nothing but__version__, so the module path is the public API and every move isbreaking. Reference counts across
src,tests,docs,examples:Plus 18
docs/api/*.mdmkdocstrings paths, two[[tool.mypy.overrides]]module lists, and three[project.scripts]entry points. No packaging change needed —[tool.setuptools.packages.find] include = ["hapi", "hapi.*"]already covers new subpackages.Suggested sequencing
Fewest references first, one
git mv+ import rewrite per commit, suite green at each step:data→core→conceptual→simulation→engine→calibration→model. Then, as separate commits, thetwo splits that need judgement rather than a move:
inputs.pyinto five files,catchment.pyintocatchment.py+lake.py.Out of Scope
dem.py. It is slated to migrate todigital-rivers(DEM.hand()supersedeshru_hand;convert_flow_direction_to_cell_indicessupersedesflow_direction_index).inputs/dem.pyis a waystation, not a design decision.
results.py's rendering intosimulation/rendering.py— worth doing, separate issue.Relationship to existing issues
parametersmodules. That is the wrong remedy: they share nothing but the name —one distributes a calibration vector over a grid, the other downloads rasters from FigShare. Renaming them apart
(
calibration/distribution.py,data/figshare.py) resolves the confusion merge both parameters modules into one module. #130 is really about. This issue shouldclose merge both parameters modules into one module. #130, or merge both parameters modules into one module. #130 should be re-scoped.
BaseHBVModel#142, Replace parameter vectors with structured objects #143, Deduplicate hydrological routines across HBV variants #145, Add unit tests for all hydrologic routines #146 all editrrm/hbv*.py, which this moves toconceptual/. Landing the HBVcluster first means re-moving freshly changed files; landing this first changes those issues' paths. Worth
deciding the order rather than discovering it in a conflict.
Effort Estimate
Size:
LRationale: mechanical but wide — roughly 150 referencing files across seven move commits, plus docs, mypy and
entry-point updates. The two file splits can be deferred to keep it inside
L.Definition of Done
rename shipped with no shim), or start re-exporting the public names from
hapi/__init__.pyrrm/is gonemodel/never importsengine/docs/api/*.md, the mypy overrides and the three console scripts all updated;mkdocs build --strictcleanparametersmodules is nolonger needed
mypyandruffgreen at every commit, not just at the end