Code Flow: From “Run Model” to Results¶
This chapter is the implementation-side companion to the theory chapters (Drifting Risk Calculations, Ship-Ship Collision Calculations, Powered Grounding and Allision). Those chapters explain what OMRAT calculates – the formulas, the physical meaning of each term, and the inputs the analyst supplies. This chapter explains how those formulas are executed inside the code: which function the GUI invokes, what that function calls next, and how results flow back to the result line-edits on the main dialog.
The goal is that a developer can read this chapter alongside the source and confidently trace any line in a calculation result back to the code that produced it.
Reading map¶
This chapter gives the common frame (GUI button, background task, phase order, progress reporting). Each accident type’s detailed call tree lives in its own chapter:
Code Flow: Drifting Model –
run_drifting_modelinternals.Code Flow: Ship-Ship Collision Model –
run_ship_collision_modelinternals.Code Flow: Powered Grounding & Allision (Cat II) – powered grounding + allision internals.
Pair each of these with its theory counterpart:
Background orchestration: CalculationTask¶
CalculationTask subclasses qgis.core.QgsTask so QGIS can
schedule it and show progress in the task-manager tray. Everything
inside its run() method executes on the background thread.
Class: compute/calculation_task.py:12 – CalculationTask
Progress plumbing¶
Before invoking the four risk models, run() installs a
progress wrapper on the Calculation object:
def progress_wrapper(completed, total, message) -> bool:
if self.isCanceled():
return False # signal the calc to stop
if total > 0:
self.setProgress(int(completed / total * 100))
self._update_description(message) # shown in QGIS task tray
self.progress_updated.emit(completed, total, message)
return True # continue
self.calc.set_progress_callback(progress_wrapper)
Every risk model calls self._report_progress(phase, phase_progress,
message) at key milestones (see
compute/run_calculations.py:_report_progress). That helper converts
(phase, 0.0..1.0) into an overall 0–100 percentage using fixed
phase weights:
Phase |
Band |
What it covers |
|---|---|---|
|
0 – 40 % |
Probability hole + min-distance pre-computation |
|
40 – 60 % |
Shadow polygons + per-obstacle edge geometry |
|
60 – 90 % |
Traffic cascade (per-ship lookups, cheap) |
|
90 – 100 % |
Result-layer creation |
Progress conversion: compute/run_calculations.py:80 – _report_progress()
The four phases, in order¶
CalculationTask.run() executes four phases sequentially on the
same Calculation instance. Each phase writes its results into
attributes of self.calc and also pushes formatted numbers straight
into the result line-edits on the main dialog.
# |
Method on |
Source |
Outputs written |
|---|---|---|---|
1 |
|
|
|
2 |
|
|
|
3 |
|
|
|
4 |
|
|
|
Between every phase, run() checks self.isCanceled() so the user
can stop a long calculation. If a phase raises an exception, run()
stores the message on self.error_msg and returns False; the
finished() callback then emits calculation_failed on the main
thread.
Where the inputs come from¶
Inside every phase, one dict (data) acts as the single source of
truth. Its keys map onto specific UI widgets / files:
Key |
What it holds |
|---|---|
|
Per-leg geometry, direction labels, lateral distributions
( |
|
Per-leg, per-direction matrices of frequency, speed, draught, beam, height. Rows = ship types; columns = LOA bins. Populated by the traffic tab or AIS import. |
|
List of |
|
List of |
|
Blackout rate |
|
Causation factors per accident type ( |
|
LOA bin definitions and ship-type names, used by the ship-collision model to estimate ship beam from LOA and to label the traffic matrix rows. |
The GatherData helper reads each
widget and builds this dict in one shot:
gd.get_all_for_save(). The same dict format is what
Storage serialises to .omrat files, so
a loaded project can be handed to run_calculation without
conversion.
UI -> dict: omrat_utils/gather_data.py – GatherData
The Calculation facade¶
Calculation is an empty class that
pulls in five mixins at import time. Each mixin holds one clearly
scoped piece of the pipeline:
class Calculation(
DriftingModelMixin,
ShipCollisionModelMixin,
PoweredModelMixin,
DriftingReportMixin,
VisualizationMixin,
):
"""Main calculation facade -- composes all model mixins."""
Facade: compute/run_calculations.py:44 – Calculation
The mixins communicate only through attributes on self, which means
each risk-model mixin can be tested in isolation with a mock parent.
See tests/test_cascade_minimal.py for the smallest possible
end-to-end drive.
Mixin |
Source |
Covered in |
|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
Report markdown generation (written by the drifting model) |
|
|
Map / plot helpers invoked after a run finishes |
Completion and UI fan-out¶
When all four phases finish (or one fails), QGIS runs the task’s
finished(result) method on the main thread. The main-thread handler
emits whichever of these signals applies:
calculation_finished(self.calc)-> connected inomrat.pyto_on_calculation_finished(), which:Calls
_auto_save_run()to persist the finished run to the history GeoPackage (see “Run-history persistence” below).Calls
refresh_previous_runs_table()so theTWPreviousRunstable on the Results tab picks up the new entry.(via
VisualizationMixin) redraws the result layers, writes the drifting report Markdown file if a path is configured, and refreshes the overview panel.
calculation_failed(error_msg)-> connected to_on_calculation_failed(), which surfaces the error in the message log.
Because the line-edits on the main dialog were already set from inside
the background phases, the user typically sees the numerical results
before the finished signal fires.
Run-history persistence¶
_auto_save_run() delegates to
omrat_utils.run_history.RunHistory, which writes a single
GeoPackage at the user’s app-data location (see
.omrat data format for the schema). The RunHistory
class is intentionally QGIS-soft: every method except
load_run_layers is plain sqlite3, so the persistence layer is
fully unit-testable without a QGIS instance
(tests/test_run_history.py covers save / list / get / compare /
delete with 28 tests).
The table on the Results tab (TWPreviousRuns) is populated by
refresh_previous_runs_table() and supports a right-click context
menu with Load on map, Compare selected, and Delete.
File -> Manage previous runs… is a shortcut to the same view.
Persistence: omrat_utils/run_history.py – RunHistory
Completion handler: omrat.py:635 – _on_calculation_finished()