Hydrographic Network UML Diagrams#

Scope#

These diagrams document the software architecture around the hydrographic network concept introduced to unify:

  • the loaded reference network from data.hydrography,

  • the DEM-derived generated network from geographic.river_network,

  • the persisted run features used later by display and comparison layers.

They focus on responsibilities, runtime handoffs, and the difference between canonical concepts and technical preprocessing bundles.

Code map#

  • hydromodpy/spatial/geographic/core/hydrographic_network.py: canonical network classes and naming contract.

  • hydromodpy/spatial/geographic/core/hydrographic_network_comparison.py: comparison result and geometric metrics.

  • hydromodpy/spatial/geographic/core/river_network.py: DEM-derived preprocessing outputs (RiverNetworkProducts).

  • hydromodpy/spatial/geographic/core/derived_features.py: geographic bundle that carries canonical network roles.

  • hydromodpy/workflow/steps/prepare_solver.py and hydromodpy/spatial/geographic/store_ingestion.py: persistence into the catalog.

  • hydromodpy/results/run.py: read facade and comparison accessors.

  • hydromodpy/display/figures/hydrographic_network*.py: standalone and comparison figures.

Class Diagram#

Use this diagram when the main question is: “which class is canonical, which one is technical, and which one is only a comparison result?”

@startuml
title Hydrographic Network - Class Responsibilities
left to right direction
skinparam classAttributeIconSize 0
skinparam wrapWidth 180

class "LoadResult\nhydrography" as HydrographyLoadResult {
  +fields
}

class "FieldRecord\nhydrography_streams" as HydrographyFieldRecord {
  +data
  +metadata.raster_path
  +metadata.vector_path
}

class RiverNetworkProducts {
  +streams_tif
  +active_streams_tif
  +network_shp
  +summary_json
  +river_mesh_trace
  --
  +hydrographic_network_generated_shp
  +hydrographic_network_generated_summary_json
}

class RiverMeshTrace

class HydrographicNetwork {
  +role
  +vector_path
  +raster_path
  +crs
  +metrics
  +metadata
  +river_mesh_trace
  --
  +from_hydrography_load_result(...)
  +from_river_network_products(...)
}

class HydrographicNetworks {
  +reference
  +generated
  +simulated_active
}

class HydrographicNetworkComparison {
  +reference_gdf
  +candidate_gdf
  +reference_missing_gdf
  +candidate_extra_gdf
  +reference_coverage_ratio
  +candidate_match_ratio
  +length_f1_ratio
  +to_metrics_record(...)
}

class StreamNetworkMetrics {
  +cell_field_network_distance_metrics(...)
}

class GeographicDerivedFeatures {
  +surface_topo
  +boundaries
  +rivers
  +hydrographic_networks
}

class Run {
  +available_hydrographic_network_roles()
  +has_hydrographic_network(role)
  +hydrographic_network(role)
  +hydrographic_network_comparison(...)
  +cell_field_active_mask(...)
  +cell_field_active_metrics(...)
  +cell_field_network_overlap_metrics(...)
  +cell_field_network_distance_metrics(...)
  +release_flux_network_overlap_metrics(...)
  +release_flux_network_distance_metrics(...)
}

HydrographyLoadResult --> HydrographyFieldRecord : contains
HydrographyFieldRecord --> HydrographicNetwork : converted into\nrole=\"reference\"
RiverNetworkProducts --> HydrographicNetwork : converted into\nrole=\"generated\"
RiverNetworkProducts --> RiverMeshTrace : exposes
HydrographicNetwork --> RiverMeshTrace : may carry one
HydrographicNetworks o-- HydrographicNetwork : bundles
GeographicDerivedFeatures o-- RiverNetworkProducts : technical bundle
GeographicDerivedFeatures o-- HydrographicNetworks : canonical bundle
Run --> HydrographicNetworks : reads persisted roles
Run --> HydrographicNetworkComparison : computes
Run --> StreamNetworkMetrics : delegates distance metrics
HydrographicNetworkComparison --> HydrographicNetwork : compares two networks

note bottom of RiverNetworkProducts
Legacy-compatible technical output bundle
from geographic.river_network preprocessing
end note

note bottom of HydrographicNetwork
Canonical cross-layer concept used by
storage, display and comparison
end note

note bottom of Run
User-facing read facade over persisted runs.
Comparison is only available when both
reference and generated roles exist.
end note

note bottom of StreamNetworkMetrics
Module hydromodpy.results.views (lazy distance metrics).
The current distance metric is planar and
cell-based; it is not yet the downslope
DEM-routing criterion.
end note

note bottom of StreamNetworkMetrics
Module hydromodpy.results.views (lazy distance metrics).
The current distance metric is planar and
cell-based; it is not yet the downslope
DEM-routing criterion.
end note
@enduml

Runtime Component Diagram#

Use this diagram when the main question is: “where does the network come from, how is it persisted, and who consumes it?”

@startuml
title Hydrographic Network - Runtime Components
left to right direction
skinparam componentStyle rectangle
skinparam wrapWidth 180

package "Input and preprocessing" {
  component "HydrographyManager\nloaded reference input" as HydrographyManager
  component "LoadResult\nFieldRecord hydrography_streams" as HydrographyLoadResult
  component "build_river_network_products(...)\nDEM-derived preprocessing" as RiverBuild
  component "RiverNetworkProducts" as RiverNetworkProducts
}

package "Canonical geographic layer" {
  component "HydrographicNetwork\nrole = reference / generated / ..." as HydrographicNetwork
  component "HydrographicNetworks\nbundle of available roles" as HydrographicNetworks
  component "GeographicDerivedFeatures" as GeographicDerivedFeatures
}

package "Persistence and reading" {
  component "prepare_solver.py\nreference persistence" as ReferencePersistence
  component "store_ingestion.py\ngenerated persistence" as StoreIngestion
  database "SimulationCatalog" as SimulationCatalog
  component "Run facade" as Run
}

package "Consumers" {
  component "Standalone figures\nreference / generated" as SingleFigures
  component "HydrographicNetworkComparison" as Comparison
  component "Comparison figures\noverlay / missing / extra" as ComparisonFigures
  component "Comparison exports\nhydrographic_network_metrics.csv" as ComparisonExports
}

HydrographyManager --> HydrographyLoadResult
RiverBuild --> RiverNetworkProducts

HydrographyLoadResult --> HydrographicNetwork : from_hydrography_load_result(...)
RiverNetworkProducts --> HydrographicNetwork : from_river_network_products(...)
HydrographicNetwork --> HydrographicNetworks : stored by role
HydrographicNetworks --> GeographicDerivedFeatures

GeographicDerivedFeatures --> ReferencePersistence : reference role
GeographicDerivedFeatures --> StoreIngestion : generated role
ReferencePersistence --> SimulationCatalog
StoreIngestion --> SimulationCatalog

SimulationCatalog --> Run
Run --> SingleFigures
Run --> Comparison : when both roles exist
Comparison --> ComparisonFigures
Comparison --> ComparisonExports

note bottom of Run
If only one role exists:
- standalone figure remains available
- comparison API raises explicit error
- comparison figures/exports are skipped
end note
@enduml

End-to-End Sequence Diagram#

Use this diagram when the main question is: “what is the order of operations from loading/generation to comparison?”

@startuml
title Hydrographic Network - End-to-End Runtime Sequence
autonumber
skinparam wrapWidth 180
skinparam maxMessageSize 180

actor User
participant "workflow / Project" as Workflow
participant "HydrographyManager" as HydroMgr
participant "geographic.river_network\npreprocessing" as GeoRiver
participant "HydrographicNetwork\nfactory methods" as Factory
participant "GeographicDerivedFeatures" as Features
participant "SimulationCatalog" as Catalog
participant "Run" as Run
participant "Comparison / figures" as Compare

User -> Workflow : run one simulation / overview / comparison case

alt reference network is loaded
  Workflow -> HydroMgr : load data.hydrography
  HydroMgr --> Workflow : LoadResult
  Workflow -> Factory : from_hydrography_load_result(...)
  Factory --> Workflow : HydrographicNetwork(role=\"reference\")
  Workflow -> Features : attach reference role
  Workflow -> Catalog : persist hydrographic_network_reference
else no reference source configured
  Workflow -> Features : keep reference = None
end

alt generated network is enabled
  Workflow -> GeoRiver : build_river_network_products(...)
  GeoRiver --> Workflow : RiverNetworkProducts
  Workflow -> Factory : from_river_network_products(...)
  Factory --> Workflow : HydrographicNetwork(role=\"generated\")
  Workflow -> Features : attach generated role
  Workflow -> Catalog : persist hydrographic_network_generated
else geographic.river_network disabled or empty
  Workflow -> Features : keep generated = None
end

User -> Run : available_hydrographic_network_roles()
Run -> Catalog : list_geographic_features(...)
Catalog --> Run : available canonical roles

alt both reference and generated exist
  User -> Run : hydrographic_network_comparison(...)
  Run -> Compare : compare_hydrographic_networks(...)
  Compare --> Run : HydrographicNetworkComparison
  Run --> User : comparison metrics / figures available
else one role is missing
  User -> Run : hydrographic_network_comparison(...)
  Run --> User : explicit error: missing role(s)
  User -> Run : hydrographic_network(existing_role)
  Run --> User : standalone role still readable
end
@enduml

Availability Activity Diagram#

Use this diagram when the main question is: “what happens when only one role exists, or when neither role exists?”

@startuml
title Hydrographic Network - Availability and Output Branching
start

:Build / load hydrographic-network inputs;

if (reference role available?) then (yes)
  :Persist / expose\nhydrographic_network_reference;
else (no)
  :reference = absent;
endif

if (generated role available?) then (yes)
  :Persist / expose\nhydrographic_network_generated;
else (no)
  :generated = absent;
endif

if (reference and generated both available?) then (yes)
  :Enable comparison API;
  :Expose comparison figures;
  :Allow hydrographic_network_metrics export;
else (no)
  if (at least one role available?) then (yes)
    :Expose standalone figure(s)\nfor available role(s);
    :comparison exports skipped;
    :comparison API raises explicit\nmissing-role error if called;
  else (no)
    :No hydrographic-network\nfigures or comparison outputs;
  endif
endif

stop
@enduml

Persistence And Run API Sequence Diagram#

Use this diagram when the main question is: “how do the persisted features reach the Run API, and what errors should a developer expect when a role is missing?”

@startuml
title Hydrographic Network - Persistence and Run API Sequence
autonumber
skinparam wrapWidth 180
skinparam maxMessageSize 180

participant "WorkflowContext\nloaded_data + geographic_features" as Ctx
participant "prepare_solver.py" as ReferencePersistence
participant "store_ingestion.py" as StoreIngestion
database "SimulationCatalog" as Catalog
participant "Run" as Run
participant "Display / comparison caller" as Caller

alt loaded reference hydrography exists
  Ctx -> ReferencePersistence : _persist_reference_hydrographic_feature(...)
  ReferencePersistence -> Catalog : write_geographic_feature(\n\"hydrographic_network_reference\", gdf)
else no loaded reference hydrography
  ReferencePersistence --> Ctx : skip reference persistence
end

alt generated geographic network exists
  Ctx -> StoreIngestion : _ingest_river_network(...)
  StoreIngestion -> Catalog : write_geographic_feature(\n\"hydrographic_network_generated\", gdf)
else no generated geographic network
  StoreIngestion --> Ctx : skip generated persistence
end

Caller -> Run : available_hydrographic_network_roles()
Run -> Catalog : list_geographic_features(sim_id)
Catalog --> Run : canonical feature names present
Run --> Caller : [reference? generated? simulated_active?]

alt caller asks for one existing role
  Caller -> Run : hydrographic_network(role)
  Run -> Catalog : read_geographic_feature(...)
  Catalog --> Run : GeoDataFrame
  Run --> Caller : GeoDataFrame
else caller asks for one missing role
  Caller -> Run : hydrographic_network(role)
  Run --> Caller : KeyError with available roles
end

alt both reference and generated exist
  Caller -> Run : hydrographic_network_comparison(...)
  Run -> Catalog : read reference + generated
  Run --> Caller : HydrographicNetworkComparison
else one role is missing
  Caller -> Run : hydrographic_network_comparison(...)
  Run --> Caller : ValueError\nmissing role(s), available roles listed
end
@enduml

Persistence And Run API Notes#

From a developer point of view, the most important operational split is:

  • prepare_solver.py persists the loaded reference role when data.hydrography produced one usable vector network.

  • store_ingestion.py persists the generated role when the geographic preprocessing produced one usable DEM-derived network.

  • SimulationCatalog stores those features under canonical names only.

  • Run does not guess. It first exposes the stored roles through available_hydrographic_network_roles() and has_hydrographic_network(...), then only enables comparison if both roles are present.

This means the runtime contract is intentionally asymmetric:

  • persistence is tolerant and simply skips missing roles,

  • reading one missing role raises a clear KeyError,

  • comparing two roles when one is absent raises a clear ValueError,

  • figure availability follows the same rule through run.display_capabilities.

Which UML Diagrams Are Worth Maintaining?#

For this topic, the highest-value diagrams are:

  1. A class diagram for the distinction between HydrographicNetwork, HydrographicNetworks, HydrographicNetworkComparison, and RiverNetworkProducts.

  2. A component diagram for the source -> canonical object -> persistence -> consumer chain.

  3. A sequence diagram for the end-to-end runtime path, including the “missing role” branch.

  4. An activity diagram for the availability branches and output gating.

  5. A developer-facing sequence diagram for persistence and Run behavior.

These three diagrams answer the most common maintenance questions:

  • where should a new field live?

  • where should a conversion happen?

  • when should a role be present or absent?

  • who is allowed to compare or display the networks?

Lower-value diagrams for this topic are:

  • state-machine diagrams, because these objects do not have a rich internal lifecycle;

  • deployment diagrams, because there is no interesting multi-node deployment question here;

  • exhaustive inheritance diagrams, because the hydrographic-network stack is composition-oriented rather than inheritance-heavy.

If more detail is needed later, the next useful addition would be one small package-level component diagram focused only on:

  • prepare_solver.py

  • store_ingestion.py

  • Run

  • the display figure registry

  • the comparison export orchestrator