Oil-spill consequence – catastrophe rates

After the per-accident-type frequencies are calculated (drifting, powered, collision), OMRAT can roll them up into annual catastrophe exceedance frequencies: how often, on average per year, an oil spill larger than a user-defined size is expected to happen.

The module is reached via the Consequence menu in the plugin toolbar. Four dialogs configure the inputs; the actual calculation runs as the last phase of Run Model and writes its result into the “Catastrophe results” table on the Run Analysis tab.

At a glance

For each ship cell (ship_type, length_interval) the model multiplies:

  • the annual accident frequency (events/year, from the per-cell breakdown the four model phases emit),

  • a conditional spill probability (rows 0-3 = “no spill”, “small spill”, “medium spill”, “catastrophic spill”, in percent),

  • a spill fraction of the ship’s oil-onboard quantity (also in percent), and

  • the cell’s oil onboard (m3).

It then counts each (accident, level) contribution toward every catastrophe threshold whose quantity it exceeds. The output is a list of {level_name, quantity, exceedance} rows ordered by quantity ascending.

The four input dialogs

Open each from Consequence > … in the menu bar.

Maximum oil onboard

A ship-type x length-interval matrix in m3. Defaults are:

  • Tankers: 80 x average_length (m3) per cell, where average length is the midpoint of the cell’s length interval and the open-ended top bin uses min + 50.

  • Everything else: 100 m3 per cell.

Override any cell directly; the dialog uses spinboxes ranged 0 to 107, two decimals, 10 m3 step.

Spill probability per accident

8 rows (one per accident category) x 4 columns (no spill / small / medium / catastrophic) in percent. Each row must sum to 100% +/- 0.05% – the dialog refuses to save until it does.

Defaults:

  • Drifting accidents: [98, 2, 0, 0] – almost all benign.

  • All other accidents: [97, 1, 1, 1].

Spill fraction per accident

Same 8 x 4 shape; each cell is the fraction of the ship’s oil onboard that escapes for the given (accident, spill-level) combination, in percent. No row constraint – you can choose any spill-fraction distribution. Default is [0, 10, 30, 100] for every accident.

Catastrophe levels

A list of {name, quantity_m3} rows; at least two are required. The Run Analysis “Catastrophe results” table will have one row per catastrophe level, sorted by quantity ascending. Defaults are Minor / Major / Catastrophic at 50 / 500 / 5000 m3.

Worked example

Suppose a single tanker cell sees 0.001 powered groundings per year, the tanker’s oil onboard is 100 000 m3, and the spill probability for powered grounding is the default [97, 1, 1, 1] with fractions [0, 10, 30, 100].

Per year the cell produces:

  • 0.001 x 0.97 = 9.7e-4 “no spill” events (volume 0).

  • 0.001 x 0.01 = 1e-5 “small spill” events of 10 000 m3.

  • 0.001 x 0.01 = 1e-5 “medium spill” events of 30 000 m3.

  • 0.001 x 0.01 = 1e-5 “catastrophic” events of 100 000 m3.

With the default thresholds:

  • >50 m3 (“Minor”) fires 3e-5 times/yr (all three spill levels).

  • >500 m3 (“Major”) fires 3e-5 times/yr (the smallest spill is already 10 000 m3).

  • >5000 m3 (“Catastrophic”) fires 3e-5 times/yr.

The table renders one row per level with the per-year exceedance.

Headless validation

For batch / CI use, compute.consequence.validate_consequence runs the same constraints the dialogs enforce (row sums, minimum two catastrophe levels, non-negative values) and returns a structured report instead of popping a UI:

from compute.consequence import validate_consequence
rep = validate_consequence(consequence_block)
if not rep.ok:
    print("\n".join(rep.errors))

Behind the scenes

  • Code: compute/consequence.py:compute_catastrophe_exceedance.

  • Defaults / reshaping: omrat_utils/consequence_defaults.py.

  • UI handler (live state, dialog wiring, row-sum validation): omrat_utils/handle_consequence.py.

  • Result table: omrat_utils/accident_results_mixin.py ._populate_catastrophe_results_table writes into the TWCatastropheResults widget.

Per-cell accident frequencies feeding the consequence calc come from each model phase’s by_cell dict (or by_cell_allision / by_cell_grounding for drifting). Pair-wise collisions split each pair contribution 50/50 between the participating cells; bend distributes proportionally to per-cell traffic on the leg.

Limitations and TODOs

  • Tankers split between laden and ballast voyages would carry different spill volumes – not yet modelled. Today every voyage uses the configured oil_onboard.

  • Merging collisions share the IWRAP causation factor with crossing. The model already separates them in by_cell based on a 30 deg crossing-angle threshold, but to be a fully separate accident type merging needs its own pc entry.