Smart India Hackathon 2026 Problem 26143 Team AlgoRise Technical Report v1.0

OilTrace:
the investigation loop

Detecting an oil slick is the easy half. This is the system that asks who put it there, and then tries to prove itself wrong.

Canonical incident
Levantine Basinincident-mediterranean-001
Observation
26 Aug 202412:00:00 UTC · Oil/00067
Slick area
266.9 km²35.63533 N, 34.87040 E
Drift engine
OpenDriftOpenOil · 1000 particles
Backend suite
195 / 1950 failed · 0 skipped
01

Executive summary

What the system is, what it produces, and the one sentence that separates it from a detector.

OilTrace turns a single satellite observation of an oil slick into a defensible investigation. A detector answers where is the oil. OilTrace answers where did it come from, who was there, and does the physics actually support that story, and it reports the uncertainty in every one of those answers instead of collapsing to a single confident origin.

The system is a four-stage pipeline. A two-stage deep-learning detector segments a slick from Sentinel-1 SAR and rejects look-alikes. An OpenDrift/OpenOil Lagrangian model runs the observed slick backwards through real ocean currents and winds to reconstruct a probable source region and time window. A deterministic attribution module correlates that space–time window against AIS vessel traffic and ranks candidates by four explainable evidence components. Finally, and this is the part that makes it an investigation rather than a guess, the system runs a forward counterfactual for each surviving candidate: if this vessel had released oil at its attributed release state, would the simulated oil actually reproduce the observed slick?

That last stage is what lets OilTrace discriminate rather than merely rank. On the canonical Eastern Mediterranean scenario, the top-ranked candidate's forward simulation lands 100% inside the observed slick with a 3.79 km centroid offset; the second candidate, attributed a non-trivial score by the same scorer on the same source region, lands 6% inside and misses by 13.67 km. The scoring did not decide that. The physics did.

81–84%Alert precision
two-stage detector
64–67%Detection rate
slicks ≥ 10 ha
4Evidence components
weighted, explainable
100% / 6%Counterfactual containment
candidate 1 vs 2
< 60 sFull pipeline
detection → verdict

How to state the headline number

Always say precision and recall together: “81% of the alerts we raise are real oil, and we detect roughly two-thirds of significant slicks.” Quoting precision alone is how a project gets taken apart in questioning. The trade-off between the two is a single tunable threshold, not a hidden weakness. See §05.

02

Problem & context

Why detection alone does not close a pollution case.

Operational satellite services already detect oil at sea. Europe's CleanSeaNet has done so for years, combining SAR imagery with AIS traffic and drift models. But the output of such a service is an alert package handed to a human analyst, who then performs the reasoning that links slick to source to ship. That reasoning is slow, expert-dependent, and rarely reproducible.

Three gaps follow from that:

  • The source is never computed, only inferred. A slick observed at noon has been drifting for hours. Its centroid is not the discharge point, and the difference is routinely tens of kilometres.
  • Proximity is treated as evidence. The nearest vessel on the AIS plot is not necessarily the responsible one; the responsible one may have left the frame entirely.
  • Nothing tests the hypothesis. Once a candidate is named, no step in the conventional workflow attempts to falsify it.

OilTrace closes all three by making each one an explicit, automated, inspectable stage, and by keeping the uncertainty visible right through to the interface. The probable source is a region, not a pin. The candidate list is a ranking with an evidence breakdown, not a verdict. The counterfactual is a physical consistency test, not a proof of guilt. Those distinctions are enforced in the code, the API schema, and the on-screen labels alike.

Scope of claim

OilTrace produces investigative support, not a legal finding. Every claim in this report about a specific vessel is a statement about physical compatibility with an observation, and the system's own output strings say exactly that.

03

The investigation loop

Four stages, one direction of evidence, and a deliberate closing of the circle.

The pipeline is best read as a loop rather than a line. AI identifies the observable phenomenon; physics reconstructs the hidden source hypothesis; AIS supplies the candidate human activity in that space-time window; and physics is then run a second time, forwards, to test whether the candidate can reproduce the original observation. The loop closes on the same slick it started from, which is precisely what makes the result checkable.

OBSERVATIONSentinel-1 SAR sceneOil/00067Two-stage detectorsegmentation + scene gateSlick polygon266.9 km2 · conf 0.768BACKWARD PHYSICSCMEMS currentsERA5 10 m windOpenOil backward1000 particles · 6 hKDE 95% HDRsource region 06:00-12:00ZATTRIBUTIONAIS tracksFilter150 km · ±12 hScore4 componentsRanked candidates+ release stateFORWARD COUNTERFACTUALRe-seed at releasereal AIS pointForward drift15 min step to 12:00ZPredicted footprintDoes the predicted footprint reproduce the observed slick?SUPPORTEDphysically consistentNOT SUPPORTEDcandidate weakenedyesnoobservation constraint
Fig. 3.1End-to-end investigation loop. The observed slick enters once as the thing to be explained, and returns at the end as the thing the counterfactual is scored against.

Ownership of each stage

StageModuleMethod classOutput artefact
1 · DetectiondetectionLearned (CNN, two-stage)Slick polygon + confidence
2 · HindcasthindcastPhysical (Lagrangian)Source region + time window
3 · AttributionattributionDeterministic scoringRanked candidates + release states
4 · CounterfactualcounterfactualPhysical (Lagrangian)Predicted footprint + agreement metrics

Deliberately, only stage 1 is learned. Stages 2 and 4 are physics with published provenance, and stage 3 is a transparent weighted rule set whose every term can be read off the response. A judge or an auditor can disagree with a weight; they cannot be told “the network decided”.

04

System architecture

A FastAPI service holding the science, a React client holding nothing but the rendering.

The single most important architectural rule in OilTrace is that the backend is the sole source of truth. Earlier iterations of the client carried their own oil-drift approximation for animation purposes; that engine has been deleted outright. The frontend now renders backend trajectories, backend geometries, backend scores and backend timestamps, and computes nothing physical of its own. Where it draws particles, they are a deterministic seeded visualisation of a backend trajectory, and the interface says so.

BROWSER / React 19 + ViteApp shellstage machine 0 to 3React-Leaflet mapDeckOilOverlayobserved slick, staticDriftCloudOverlayhindcast + forward cloudsInvestigationListstepper + comparison tableSuspectPanelevidence breakdownbackendApi.jsnormalize + AIS interpolationFASTAPI SERVICE/api/v1 routerservices/detectionservices/hindcastservices/vesselsservices/attributionservices/counterfactualservices/replayPydantic schemascontract enforcementMODELS & DATASAR detectorsegmentation + scene gateOpenDrift OpenOilCached NetCDF readersCMEMS currents · ERA5 windAIS track storeHTTPS JSONin-process
Fig. 4.1Component architecture. Every physical quantity crosses the boundary in one direction only, server to client.

Design rules enforced in code

RuleEnforcement
No client-side physicsAll local drift/current/wind modules deleted; Simulation/particles.js retains only geometry sampling and deterministic PRNG
No fabricated scoresAttribution weights and component values are read from the response evidence_breakdown; hardcoded demo values removed
Coordinate disciplineGeoJSON is [lon, lat], Leaflet is [lat, lon]; conversion happens in exactly one helper, latLngRingFromGeometry
Layer separationObserved, backward and forward clouds are three independent state objects; no array is mutated between stages
Vessel positions are observationsVessel markers interpolate between the two surrounding real AIS points via aisPositionAt; a vessel is never moved to the estimated source
Failure is visibleAn ErrorBoundary wraps the app; backend failures surface the raw reason rather than silently producing an empty stage

Why this rule matters scientifically

If the client can synthesise a plausible-looking particle cloud, then a demonstration proves nothing about the model; it proves the animation works. Deleting the second engine was the single change that made every on-screen result traceable to an API response.

05

Stage 1: SAR detection

A segmentation network to find dark slicks, and a scene-context classifier to throw out the things that merely look like them.

Synthetic-aperture radar sees oil as a dark patch: the film damps capillary waves and the surface stops backscattering. The problem is that a great many other things also damp capillary waves: algal blooms, low-wind shadows, rain cells, natural surfactant slicks, current fronts. Classical single-threshold detectors and single-stage segmenters fail on exactly these look-alikes, and they fail with confidence.

Two-stage design

OilTrace splits the task. A segmentation network proposes dark-region masks with pixel-level geometry. A second, scene-context classifier then evaluates the surrounding scene and gates the proposal; it is the component that rejects the look-alike family. The operating point of the gate is a single threshold τ, and moving it trades precision against detection rate along one axis: “harbour-master mode” (few, very reliable alerts) at one end, “wide-net mode” (catch more, tolerate more noise) at the other.

Sentinel-1 GRD scenePreprocesscalibrate · speckle filter · tileStage A: segmentation CNNchampion run E5_focalCandidate dark-region masks+ per-region geometryStage B: scene-context classifieroil vs look-alikeACCEPTemit slick polygon + confidenceREJECTalgae · wind shadow · rain cellscore >= tauscore < tauDetection responsepolygon · area km2 · centroid · confidence
Fig. 5.1Two-stage detector. Stage A maximises geometric recall; stage B buys precision back by rejecting look-alikes that no single-stage model separates reliably.

The three-beat case for the detector

  1. It is precise enough to act on. Roughly eight in ten alerts raised are real oil. Precision is the number an operations room lives by, because every false alert costs a dispatch decision. 81–84% is the direct product of the two-stage design.
  2. It generalises past its training data. Zero-shot on an external public dataset, the model reaches Dice 0.65 on Sentinel-1 and 0.58 on PALSAR, a different radar band entirely. The threshold frozen before that evaluation lands within 0.03 of the oracle threshold for that dataset. That answers “did you just overfit one dataset?” before it is asked.
  3. All of it is checkable. The scene-level split was committed to version control before training began. A 450-scene test set has been sealed and never evaluated; it will be run exactly once, before final submission. Every number in this section is reproducible from the repository.

Measured detection performance

MetricValueProtocol note
Alert precision81 – 84%Two-stage, validation-measured; range spans the τ operating band
Detection rate, slicks ≥ 10 ha64 – 67%Operationally significant slicks only; quote alongside precision
Localization error≈ 85 mCentroid offset on the demonstration scene
Area accuracy≈ ± 1%Demonstration scene
Clean-water false alarms0On the tested no-oil scenes
Cross-sensor Dice (Sentinel-1)0.65Zero-shot, external dataset
Cross-sensor Dice (PALSAR L-band)0.58Zero-shot, different radar band
Throughput10 – 18 s / sceneCPU-only, no GPU required
OPERATING BAND · GATE THRESHOLD τ 50%65%80%90%100% PRECISION 81 – 84% DETECTION ≥ 10 ha 64 – 67% ← wide-net mode (τ low) harbour-master mode (τ high) →
Fig. 5.2The two headline numbers, plotted as measured bands rather than a fitted curve. Raising τ moves precision right and detection rate left; the bands show the range actually measured, not interpolated points.

Training record: champion runs

RunRoleDicePrecisionRecall ≥ 10 haMacro-acc.
E5_focalSegmentation champion0.3500.4710.667n/a
scene-contextLook-alike gaten/a0.813 – 0.845n/a0.731

The pixel-Dice question, answered before it is asked

A Dice of 0.350 will look low to anyone who has read the SAR-segmentation literature, where ~0.75 is common. The difference is the validation protocol, not the model: our validation set is deliberately trap-heavy, loaded with the look-alikes that inflate everyone else's numbers when they are excluded. We report the harder number by choice. Say it in exactly that order: “0.35 on our trap-heavy set; roughly 0.75 under the standard literature protocol.”

Deployment status: do not blur this

The two-stage precision figures are validation-measured. The deployed demonstration currently runs the single-stage model. Either the gate is deployed before the presentation, or it is presented as “measured, deploying”. There is no third framing.

06

Stage 2: Backward hindcast

Running real ocean physics in reverse to reconstruct where the oil must have been.

A slick observed at 12:00 UTC has been drifting, spreading and weathering for hours before the satellite passed. Its observed centroid is an artefact of that history. To recover the discharge location, the observed slick is seeded as a particle cloud at the observation time and integrated backwards through the same current and wind fields that transported it.

Why OpenDrift / OpenOil

  • It is an oil model, not a generic tracer. OpenOil carries the oil-specific processes (surface advection, wind drag, entrainment, spreading) rather than treating oil as a passive particle.
  • It is Lagrangian. The output is a cloud of particles with individual trajectories, which is exactly the representation needed to express uncertainty as a region rather than a point.
  • It runs backwards natively. Time-reversed integration is a supported mode, not a hand-rolled inversion.
  • It is open, published and reproducible. A judge can read the model documentation; we are not asking anyone to trust an in-house integrator.
  • It accepts standard CF-compliant NetCDF forcing, the same products an operational agency would use.

Environmental forcing

FieldProductRole in the model
Ocean currentsCopernicus Marine Service, Mediterranean Sea PhysicsDominant advection term; sets the bulk displacement of the cloud
10 m windECMWF ERA5 hourly single-levelWind drag on the surface film; controls stretching and the along-wind bias

Forcing files are supplied as CF-generic NetCDF readers. The service resolves a configured list of candidate paths per domain, deduplicates them, checks existence, and opens each reader inside a guarded block so that a single unreadable file degrades the run rather than killing the process. Crucially, those readers are opened once per backend process and cached. The reason why is the subject of §13.

Method

Observed slick polygon2024-08-26 12:00:00ZSeed 1000 particlesinside observed geometryOpenOil backward integration6 hours, reverse timeParticle positions at 06:00ZKernel density estimateover final particle positions95% highest-density regionSource region centroid35.61349 N, 34.82728 ESourceRegion responsegeometry · centroid · windowCMEMS currentsERA5 10 m wind
Fig. 6.1Backward hindcast. The 95% contour is computed over the final backward particle positions, the distribution of where the oil could have originated.

Configuration of record

ParameterValue
Particle count1 000
Backward duration6 h
Observation time2024-08-26 12:00:00 UTC
Reconstructed source window06:00 → 12:00 UTC
Observed slick centroid35.63533 N, 34.87040 E
Source region centroid35.61349 N, 34.82728 E
Region definition95% KDE highest-density region

What the 95% actually means

The source region carries a value of 0.95. That is density mass: 95% of the simulated backward particle distribution falls inside that polygon. It is not a calibrated probability that the true discharge occurred there, and the interface labels it KDE density mass: 95% for that reason. Calling it “95% probability this is the source” would be a false statement about a quantity we have not calibrated. See §15.

One consequence deserves stating plainly, because it looks like a bug and is not. Over six hours in this scenario, the cloud's coherent displacement is roughly 4.5 km while the observed slick is about 13.6 km across. The reconstruction therefore appears to barely move relative to its own size. That ratio is the physics of a weak-current, low-wind summer Levantine basin; the correct response was to improve the legibility of the visualisation, not to amplify the displacement.

07

Stage 3: AIS attribution

Four evidence components, fixed published weights, and a ranking that refuses to name a single ship.

The attribution module, internally codenamed Nimit, takes the reconstructed source region and window from stage 2 together with AIS tracks for vessels in the area, and produces a ranked candidate list. It is a deterministic scoring system, not a machine-learning model. Every number in its output can be recomputed by hand from the inputs.

Filtering before scoring

Vessels that are physically impossible are removed before any score is computed, both for cost and for clarity of the resulting ranking.

FilterThresholdEffect
Distance150 kmTrack never approaches the source polygon → eliminated
Time± 12 hNo AIS points near the source window → eliminated
Trajectory segmentation30 min gapAIS gaps split the track; long gaps are never spliced into one path
Minimum segment length3 pointsShorter segments carry too little geometry to score

The four evidence components

Spatial: exponential distance decay

spatial = exp(−d / λ) // d = min distance, track → source polygon, in km // λ = 10 km default, or uncertainty_radius_km if supplied

The distance used is the minimum from any point on the track to any point in the polygon, computed in a projected (UTM) coordinate system so the result is in metres. Centroid-to-centroid distance was rejected deliberately: it misleads badly when a trajectory grazes the edge of a large polygon.

Temporal: Gaussian timing penalty

if vessel has an AIS point inside [start, end]: temporal = 1.0 else: Δh = hours to the nearest window edge temporal = exp(−Δh² / (2·τ²)) // τ = 2 h

A vessel inside the window pays no penalty. Outside it, the score falls smoothly. 1 h out scores ≈ 0.88, 2 h out ≈ 0.61, 4 h out ≈ 0.135, 6 h out ≈ 0.011. The Gaussian shape means small timing offsets are forgiven and large ones are not, and the function is continuous at the window edge.

SPATIAL · exp(−d/10) TEMPORAL · exp(−Δh²/8) λ = 10 km → 0.37 1.00.50.0 01020304050 distance to source polygon (km) in window = 1.0 τ = 2 h → 0.61 1.00.50.0 0123456 hours outside the source window
Fig. 7.1Response curves for the two continuous evidence components, with the parameter values of record marked. Both are heuristics chosen for shape, not fitted to spill data.

Trajectory: binary intersection

trajectory = 1.0 if any(segment.intersects(source_polygon) for segment in segments) else 0.0

Deliberately binary. The question it answers is “did this hull physically cross the suspected source area?”, and since the spatial component already supplies a continuous proximity measure, giving trajectory partial credit would double-count distance. A ship that skirts the edge scores zero here and is still rewarded by the spatial term.

AIS reliability: data completeness, not guilt

coverage = hours_with_data / window_hours gap_penalty = max_gap_minutes / 60 // capped at 60 min reliability = coverage × (1 − 0.5 × gap_penalty)

Worked example: full hourly coverage with a worst gap of 15 minutes gives 1 × (1 − 0.5 × 0.25) = 0.875. This is a data-quality indicator. It says how much the AIS evidence for this vessel can be trusted, and nothing whatsoever about whether the vessel discharged oil.

Combining the components

overall_score = 100 × ( 0.35·spatial + 0.30·temporal + 0.20·trajectory + 0.15·ais_reliability )
WEIGHT ALLOCATION · FROZEN MVP SPECIFICATION 35%30%20%15% SpatialTemporalTrajectoryReliability exp(−d/λ)exp(−Δh²/2τ²)0 or 1data quality Design choices from the frozen spec, not learned or statistically optimised.
Fig. 7.2Where the score comes from. Spatial and temporal evidence dominate by design; trajectory and AIS reliability act as secondary modifiers.
Score bandConfidence labelBehaviour
≥ 70HighReported as a strong candidate
40 – 69MediumReported with the ambiguity visible
< 40LowSets no_strong_candidate = true; candidates still returned

A score is not a probability

An overall_score of 74.1 does not mean a 74.1% chance the vessel is responsible. It is a relative attribution score on a 0–100 scale that ranks vessels by weight of evidence. The system never presents it as a likelihood of guilt, and neither should any spoken presentation of it.

Release-state selection

Ranking is not the end of the stage. For each surviving candidate the module must choose a concrete release state, a location and a time, to hand to the forward model. The rule is deliberately conservative:

  1. Among AIS points that fall inside [start_time_utc, end_time_utc], take the one closest in distance to the source region.
  2. If the vessel never entered the window, take the point closest in time to the window, preferring the spatially nearest among ties.
  3. Guarantee release_time_utc ≤ observation_time_utc. Only points at or before the slick observation are ever considered.

The consequence matters: the release location is always a real, timestamped AIS position. It is never the source-region centroid, never the slick centroid, never a synthesised point. That single rule is what keeps the forward counterfactual an independent test rather than a circular one.

FrontendHindcastAttributionForward simPOST /hindcastSourceRegion {polygon, window}POST /attribute {source_region, vessels[]}filter 150 km, ±12 h, 30 min gapsscore 4 evidence componentsrank + classify confidenceselect release state (real AIS point)AttributeResponse {candidates[]}loop: every eligible candidatePOST /forward {release_location, release_time}predicted_footprintPOST /counterfactualcontainment · jaccard · centroid distance
Fig. 7.3Attribution sequence. Note the loop: every eligible candidate is tested, not only the top-ranked one, the change that turned a demonstration into an experiment.
08

Stage 4: Forward counterfactual

The falsification step: re-run the physics forwards and see whether the candidate's story survives.

Everything to this point produces a hypothesis. Stage 4 tests it. For a candidate vessel with an attributed release state, the model seeds oil at that real AIS position and time, integrates forwards to the observation time through the same forcing fields, and compares the predicted footprint against the actual observed slick.

This is a counterfactual in the strict sense: it asks what the world would look like if this candidate were responsible, and then checks whether that world matches the one the satellite photographed.

Two methodological corrections

The first implementation of this stage produced weak agreement for every candidate, which read as a tuning problem. It was not. It was two modelling errors, and fixing them raised every candidate's metrics together, the signature of a corrected method rather than an inflated one.

ErrorWhy it was wrongCorrection
Instantaneous releaseOil was seeded at a single instant. Operational discharges are continuous: a vessel underway leaks along its track, producing an elongated slick that a point release can never reproduce.Seeding accepts a release duration; particles are distributed across [release_time, release_end], clamped to the observation time.
Jaccard as the sole metricJaccard is intersection over union. A small, correctly-placed predicted footprint inside a large observed slick scores badly, because the metric conflates size disagreement with position disagreement.Added predicted_containment = intersection ÷ predicted area. It answers the actual question: did the predicted oil land inside the observed slick?
jaccard = |predicted ∩ observed| / |predicted ∪ observed| predicted_containment = |predicted ∩ observed| / |predicted| centroid_distance_km = great-circle distance between footprint centroids trajectory_reaches_slick = boolean, true if the path enters the observed geometry

Evidence is graded Strong when spatial_agreement ≥ 0.3 or when predicted_containment ≥ 0.7 and the trajectory reaches the slick. Both metrics are reported side by side; neither is quietly dropped.

Testing every candidate, and testing them twice

Two design decisions make this stage an experiment rather than a demonstration:

  • Every eligible candidate is tested, not just the top-ranked one. A test that only ever runs on the winner cannot demonstrate discrimination.
  • Two hypotheses are run per candidate. The candidate-specific test uses each vessel's own attributed release state. A second common-release-time sensitivity test releases every candidate at the same reference instant.

Why the common-time test scores zero for everybody

Releasing all candidates at 06:00 UTC, the start of the reconstructed source window, yields 0% containment across the board, and that is the physically correct answer. For oil to be at the source at 06:00, a vessel had to be there at 06:00. MT CYPRUS SUN was 21 km south at that moment (≈ 35.42 N) and only reached the source area at 09:15. The common-time test is therefore a sensitivity probe, not a competing verdict, and the interface shows both results rather than picking the flattering one.

Results on the canonical scenario

CandidateMMSIAttributionContainmentCentroid offsetReaches slickReading
MT CYPRUS SUN21100000174.1100%3.79 kmYesPhysically consistent
MV LEVANT STAR211000002n/a6%13.67 kmNoNot supported
FV KARPASIA211000003n/an/an/an/aUntestable: no AIS coverage in the window
COUNTERFACTUAL DISCRIMINATION PREDICTED OIL LANDING INSIDE OBSERVED SLICK MT CYPRUS SUN 100% MV LEVANT STAR 6% CENTROID OFFSET FROM OBSERVED SLICK · LOWER IS BETTER MT CYPRUS SUN 3.79 MV LEVANT STAR 13.67 km km
Fig. 8.1The same scorer, the same source region, the same forcing, and a separation of 94 percentage points in containment. This is the figure that shows OilTrace discriminates rather than confirms.

FV KARPASIA is worth dwelling on. It is not excluded because it scored badly; it is excluded because it cannot be tested: it has no AIS coverage inside the source window from which to derive a release state. The system reports that as “unavailable” rather than as a low score, because those are different epistemic states and conflating them would be dishonest.

Figures of record

Earlier internal documentation cites a Jaccard of ≈ 0.179, a centroid distance of ≈ 2.43 km, and an attribution score of 75.42 for the top candidate. Those are pre-correction values from an earlier run, recorded before continuous release seeding and the containment metric were introduced. The values in the table above are the current live backend figures and are the ones of record; any presentation material still carrying the earlier set should be updated. Similarly, one presentation slide shows the slick centroid as 35.6333 N, while the canonical backend value is 35.63533 N, and that is the figure to standardise on.

09

Replay & visualization

Three evidence layers that are never allowed to merge, and a clock that runs in the direction the physics runs.

The interface has one scientific obligation: an observer must be able to tell, at every instant, which of three fundamentally different things they are looking at. Conflating them, which the earlier build did, makes a demonstration look impressive and mean nothing.

Observed SAR slick

What the satellite actually detected. Fixed geometry, fixed colour, never animated, never faded, never regenerated. Particles are sampled strictly inside the detection GeoJSON, never a bounding box.

Backward hindcast

Modelled historical drift from the observed slick toward a probable source. Starts covering the observed slick at 12:00 UTC and translates coherently backwards to ≈ 06:00 UTC.

Forward counterfactual

If this candidate released oil at the backend's release state, where would it go? Begins at the real AIS release position and runs forwards to the observation time.

Stage machine

The client holds one authoritative UTC investigation clock, and each stage interprets it correctly: backward time decreases, forward time increases, attribution can freeze on an evidence window. Progress is never derived from an array index.

Stage 0Detection loadedVISIBLE LAYERSObserved slick onlyStage 1Hindcast completeVISIBLE LAYERS+ backward cloud+ source regionStage 2Attribution completeVISIBLE LAYERS+ AIS tracks+ ranked candidatesStage 3Counterfactual completeVISIBLE LAYERS+ forward cloud+ comparison tableRun hindcastRun attributionRun forwardReset analysis
Fig. 9.1Investigation stage machine. Map layers are scoped to stages, so nothing appears before the analysis that produced it has actually run.

Rules the renderer obeys

  • Vessels are observations, not inferences. A vessel marker at time T is interpolated between the two real AIS points bracketing T. A vessel is never relocated to the probable source, and outside its track window it is simply not drawn.
  • The backward and forward clouds never share particle positions. They are separate state objects; no array is reused between modes.
  • The forward path is never drawn from the ship to the source. Doing so would visually assert the conclusion the counterfactual is supposed to test.
  • Playback speed is honest. A full physical interval takes roughly 60 seconds at 1×. Changing speed changes only how fast the real timestamps are displayed; it never stretches or alters simulated time.
  • A permanent legend is on screen. The three-layer key is always visible, so no observer has to infer what a colour means.

Replay endpoint

PropertyValue
Replay window2024-08-25 18:00 UTC → 2024-08-26 12:00 UTC
Frame interval30 minutes
Frame count37
Wall-clock at 1×≈ 60 s
10

API contract

Ten endpoints under one versioned prefix, with Pydantic enforcing the schema at the boundary.

All routes are mounted under /api/v1. Request and response bodies are Pydantic models, so contract violations fail at the boundary with a structured error rather than propagating into the physics.

MethodPathTagPurpose
GET/healthSystemService liveness and configuration summary
GET/pingSystemMinimal reachability probe
POST/detectDetectionRun the SAR detector on a scene; return slick geometry and confidence
GET/health/mlDetectionModel-service health, separate from API health
POST/hindcastHindcastBackward drift; returns source region, window and backward trajectory
POST/forwardHindcastForward drift from a release state; returns the predicted footprint
GET/vesselsVesselsCandidate vessels with AIS tracks for the incident window
POST/attributeAttributionFilter, score and rank candidates; emit release states
POST/counterfactualDriftCompare a predicted footprint against the observed slick
GET/replay/{id}ReplayTimestamped frames for the incident replay

Attribution request (abbreviated)

POST /api/v1/attribute
{
  "incident_id": "incident-mediterranean-001",
  "source_region": {
    "candidate_regions": [{
      "geometry": { "type": "Polygon", "coordinates": [[ /* [lon, lat] rings, closed */ ]] },
      "centroid":   { "lat": 35.61349, "lon": 34.82728 },
      "start_time_utc": "2024-08-26T06:00:00Z",
      "end_time_utc":   "2024-08-26T12:00:00Z",
      "probability":    0.95          // KDE density mass, NOT a source probability
    }]
  },
  "vessels": [{
    "mmsi": "211000001",
    "name": "MT CYPRUS SUN",
    "vessel_type": "Tanker",
    "track_points": [
      { "timestamp_utc": "2024-08-26T09:15:00Z",
        "position": { "lat": 35.6135, "lon": 34.8273 },
        "sog": 10.5, "cog": 45.0, "navigation_status": 0 }
      /* ... 2+ points required, in time order ... */
    ],
    "track_geometry": { "type": "LineString", "coordinates": [ /* [lon, lat] */ ] },
    "ais_gaps": []
  }],
  "uncertainty_radius_km": 10.0
}

Attribution response (shape)

Angle brackets mark per-run values rather than captured constants; the score and identity below are from the canonical run.

{
  "incident_id": "incident-mediterranean-001",
  "source_region": { /* echoed */ },
  "candidates": [{
    "mmsi": "211000001",
    "name": "MT CYPRUS SUN",
    "rank": 1,
    "overall_score": 74.1,            // relative score, NOT a probability
    "confidence": "High",
    "evidence": {                            // each component reported separately,
      "spatial":         <float 0..1>,      // so overall_score can be recomputed by hand
      "temporal":        <float 0..1>,
      "trajectory":      <0.0 or 1.0>,
      "ais_reliability": <float 0..1>
    },
    "release_location": { "lat": 35.6135, "lon": 34.8273 },
    "release_time_utc": "2024-08-26T09:15:00Z",
    "forward_request": { /* ready to POST to /forward */ }
  }],
  "no_strong_candidate": false
}

Reading the evidence breakdown

The four evidence values are the raw component scores in [0, 1] before weighting. Multiply them by 0.35 / 0.30 / 0.20 / 0.15, sum and scale by 100 and you get overall_score exactly. The frontend reads the weights from the response rather than assuming them, so a backend weight change propagates to the interface without a client release.

11

Data model

The objects that cross the API boundary, and the constraints the schema enforces on them.

Incidentincident_idobservation_time_utcsceneslickSatelliteScenescene_idsensoracquired_utcReplayFrametimestamp_utcvessel_positionsoil_extentSlickslick_idgeometry : Polygonarea_km2centroidml_confidenceSourceRegionidslick_idgenerated_at_utccandidate_regionsCandidateRegiongeometry : Polygoncentroidstart_time_utcend_time_utcprobabilityVesselmmsi : str(9)namevessel_typetrack_geometryais_gapsTrackPointtimestamp_utcpositionsog / cognavigation_statusCandidatemmsirankoverall_scoreconfidencerelease_time_utcEvidenceBreakdownspatialtemporaltrajectoryais_reliabilityForwardSimulationRequestvessel_mmsirelease_locationrelease_time_utcrelease_duration_minutesForwardResultpredicted_footprinttrajectorytrajectory_timestamps_utcCounterfactualResultjaccardpredicted_containmentcentroid_distance_kmtrajectory_reaches_slickevidence_grade1*11..*hindcast1..*attributionscores111compared against the observed slick
Fig. 11.1Domain model. The two associations into CounterfactualResult are the loop closing: a forward result is only meaningful when compared back against the slick that started the investigation.

Constraints the schema enforces

FieldConstraintRationale
mmsi9-digit stringMMSI is an identifier, not an integer; leading zeros are significant
geometryClosed GeoJSON ring, [lon, lat]Prevents the axis-order class of bug at the boundary
track_points≥ 2, strictly time-orderedA single point cannot form a trajectory segment
probability0.0 – 1.0Density mass fraction; see §06 on interpretation
predicted_containment0.0 – 1.0, default 0.0Backwards-compatible addition; older clients see a valid field
release_duration_minutes≥ 0.0, nullableNull means “until observation time”, continuous release by default
release_time_utcobservation_time_utcYou cannot release oil after the satellite photographed it
12

Demonstration walkthrough

The canonical Eastern Mediterranean incident, minute by minute.

The demonstration runs a single incident end to end against the live backend, with no scripted values anywhere in the client. Every number that appears on screen came out of an API response during the run.

UTC06:00Zsource window opens09:15ZCYPRUS SUN at source12:00ZSentinel-1 detects slickRECONSTRUCTED HISTORY AND OBSERVATIONINVESTIGATION (OPERATOR ACTIONS)Step 1: Run hindcast1000 particles · 6 h backStep 2: Run attribution3 vessels scoredStep 3: Run forwardevery candidate tested
Fig. 12.1Demonstration timeline. Rows above the observation are reconstructed by the model; rows below are operator actions in the interface.

What an observer sees, step by step

  1. Load detection. The observed slick appears in oxide red at its true geometry. Nothing else is on the map: no vessels, no regions, no clouds. Analysis that has not run is not shown.
  2. Run hindcast. A teal reconstruction cloud appears covering the observed slick at 12:00, then translates coherently backwards as the clock counts down to 06:00. The probable source region is drawn and stays visible, labelled KDE density mass: 95%.
  3. Run attribution. AIS tracks appear. Three vessels are scored; the panel shows each one's four evidence components and the weight applied to each. MT CYPRUS SUN ranks first at 74.1.
  4. Run forward. Each eligible candidate is simulated in turn, with a progress counter. A green forward cloud starts at each candidate's real AIS release position, not at the source region, and drifts toward the observation time.
  5. Read the comparison table. Containment, centroid offset and reach are shown side by side for every candidate tested, plus the common-release-time sensitivity result and an explicit “unavailable” for the untestable vessel.
  6. Reset analysis. Clears detection, hindcast, attribution, forward results, replay and clock in one action, returning to stage 0 for a clean second run.

The moment worth pointing at

When the forward cloud for MV LEVANT STAR drifts visibly away from the red observed slick while MT CYPRUS SUN's lands on top of it, the audience is watching the system attempt to falsify a candidate and succeed. That is the difference between a detector and an investigation, and it is visible without reading a single number.

13

Reliability engineering

A native segmentation fault, four ruled-out hypotheses, and a nine-line fix.

During multi-candidate testing the backend began dying intermittently, not raising a Python exception but terminating with a macOS SIGSEGV / EXC_BAD_ACCESS. A process that dies mid-demonstration is a project-ending failure, so this was treated as a first-class engineering problem rather than a flake.

Diagnosis

The crash reports gave a consistent native stack:

nc_inq_varname → NC4_HDF5_inq_var_all → H5Dget_create_plist → H5F_addr_decode

The fault was inside libhdf5, reached through libnetCDF, the layer beneath OpenDrift's NetCDF readers. The trigger was that _build_forcing_readers() constructed fresh readers on every /hindcast and /forward call. Four HDF5-backed files were opened per request, and superseded readers were closed later at garbage-collection time. HDF5 in this configuration is not thread-safe, and the interleaving of opens with GC-timed closes corrupted its internal state.

Intermittent backend deathSIGSEGV / EXC_BAD_ACCESSRead the native crash stacklibhdf5 reached via libnetCDFnc_inq_varname to H5F_addr_decodeOut of memory?Ruled outpeak 1.9 GB is theinherent working setFile-descriptor leak?Ruled outFD count stable at 309/2Concurrent requests?Ruled outendpoints are async def,serializedRepeated open/close churn?ROOT CAUSE4 HDF5 files opened perrequest, closed by GCFix: open the forcing readers once per processand cache them under a lockVerifiedopened once · HDF5 objects stable · 195/195 tests · 2 workflows on one PID · 0 crashes
Fig. 13.1Fault isolation. Three plausible explanations were measured and eliminated before the fourth was accepted. The memory peak, the descriptor count and the concurrency model were each checked against evidence rather than assumed.

The fix

# app/services/hindcast.py
_FORCING_READERS: list | None = None
_FORCING_LOCK = threading.Lock()

def _build_forcing_readers() -> list:
    global _FORCING_READERS
    with _FORCING_LOCK:
        if _FORCING_READERS is not None:
            return list(_FORCING_READERS)
        # ... unchanged path resolution, dedupe, existence check, guarded open ...
        _FORCING_READERS = readers
        logger.info("Opened %d forcing reader(s); cached for process lifetime", len(readers))
        return list(_FORCING_READERS)

The reader-selection logic is untouched. Readers are opened once per backend process and handed out under a lock; callers receive a copy of the list so they cannot mutate the cache. No physics, particle count, scientific calculation or API contract was changed.

Verification

CheckResult
Forcing files opened per processOncePass
HDF5 open-object count across repeated runsStable ≈ 498 kPass
Backend test suite195 passed · 0 failed · 0 skippedPass
Consecutive full workflows on one process2 × complete, single PIDPass
New crash reports after the fix0Pass

Multi-domain forcing

The same refactor introduced configurable per-domain forcing paths (DRIFT_FORCING_FALLBACK_CURRENTS_PATHS, DRIFT_FORCING_FALLBACK_WIND_PATHS) resolved by a settings validator. One deployment can therefore serve several geographic domains (the Mediterranean demonstration scenario and any other region whose forcing has been provisioned) without a code change.

14

Validation & results

Everything that has been run, and what it demonstrated.

TestConfigurationResult
Standalone OpenDrift verification12 steps, 10 particles, 3-hour testIntegration confirmed independently of the APIPass
Production hindcast6 h backward, 1000 particlesSource region + window produced via the live API pathPass
Forward simulation2.75 h run, 15-minute timestepPredicted footprint + timestamped trajectoryPass
Counterfactual, candidate 1MT CYPRUS SUN, attributed release state100% containment · 3.79 km · reaches slickPass
Counterfactual, candidate 2MV LEVANT STAR, attributed release state6% containment · 13.67 km · missesPass
Counterfactual, untestableFV KARPASIA, no AIS in windowReported unavailable, not scored lowPass
Replay37 frames, 30-minute intervalFrame timestamps consistent with simulation timesPass
Backend test suiteFull run195 passed, 0 failed, 0 skippedPass
Process stabilityRepeated end-to-end workflows, one PIDNo native crash after the forcing-reader fixPass

What the counterfactual result does and does not establish

Does: demonstrate that the pipeline can distinguish between two candidates that the same attribution scorer considered worth testing, using physics rather than score arithmetic; and that the discrimination is large (100% vs 6% containment, 3.79 km vs 13.67 km).

Does not: establish that MT CYPRUS SUN discharged oil. The AIS fleet in this demonstration is synthetic. A strong counterfactual result raises support for a hypothesis; it is not a finding of responsibility, and the system's own output strings say so.

15

Limitations

Stated in full, because a system that reasons about evidence has no business hiding its own.

  • The source region is an uncertainty representation, not ground truth. It is derived from the simulated particle distribution and inherits every error in the forcing fields.
  • The 95% KDE/HDR value is density mass, not calibrated probability. It must never be spoken as “95% probability this is the source”.
  • Attribution scores are relative, not probabilistic. A score of 74.1 ranks a vessel; it does not quantify likelihood of guilt. Calibrating to probabilities would require historical incidents with known responsible vessels. See the calibration plan below.
  • The weights are design choices. 35 / 30 / 20 / 15 comes from the frozen MVP specification and reflects team judgement about relative importance, not statistical optimisation.
  • The evidence kernels are uncalibrated heuristics. λ = 10 km and τ = 2 h were chosen for shape and scale. The spatial kernel assumes isotropic uncertainty and ignores currents and winds entirely; a physics-based spatial likelihood would be strictly better.
  • AIS reliability is ad hoc. It mixes a fractional coverage term with a linear gap term without statistical justification, and treats all gaps as equivalent regardless of context.
  • No AIS-spoofing or dark-vessel handling. The system assumes AIS is honest and present. A vessel that switches off its transponder is invisible to attribution, which is exactly the behaviour a deliberate polluter would exhibit.
  • Trajectory evidence is binary. A momentary clip of the polygon boundary scores identically to a long transit through it.
  • Single-UTM-zone projection. The zone is picked from the source polygon's centroid, so a track spanning zones may be distorted.
  • Per-particle trajectories are not exposed to the client. Some on-screen clouds are deterministic visualisations built from backend representative trajectories rather than the full particle ensemble.
  • The demonstration AIS fleet is synthetic. Attribution results demonstrate the method and the interface, not a real-world finding.
  • Forcing coverage is demonstration-scoped. Operational deployment requires automated acquisition, quality control and coverage checking for current and wind products.
  • The detector's two-stage figures are validation-measured, and the deployed demo currently runs single-stage. See §05.

Calibration plan

The scores are not probabilities, but they could be made into them. Given a corpus of past incidents with known responsible vessels, one would compute the raw component scores for the true vessel and for the other vessels present, then fit a logistic regression or isotonic calibration mapping overall_score to an estimated probability of responsibility. Ranking quality would be evaluated by ROC-AUC and precision-at-1 under cross-validation. Absent that data, the scores are used qualitatively and labelled as such throughout.

16

Comparison to prior work

Where OilTrace sits relative to an operational service and to the research literature.

CapabilityOilTraceCleanSeaNet / Copernicus (EMSA)Academic prototypes
SAR detectionTwo-stage: segmentation + look-alike gateOperational SAR-based detectionCommon; many published studies
Backward hindcastCore stage: source region and time window from OpenOilAvailable as an added service alongside forecastingUsed in some work (e.g. NOAA GNOME / TAP)
AIS / vessel dataIngested and scored against the source window (synthetic fleet in the prototype)AIS and VMS integrated as supporting informationOften considered, e.g. AIS anomaly detection
AttributionDeterministic, weighted, fully explainable evidence breakdownProvides supporting information; final identification by human analystsBayesian and ML approaches exist but are not standardised
Forward validationAutomated counterfactual for every eligible candidateDrift model available but user-triggeredResearch frequently stops at identification
AutomationFully automated once inputs are suppliedHuman-in-the-loop by designVaries; few end-to-end automated systems
ExplainabilityHigh: every weight and component is in the responseLower; operators interpret multiple data layersMixed: some explainable rules, some black boxes
Handling of ambiguityReturns ranked candidates and preserves uncertainty end-to-endMay list multiple contacts, no standardised top-kSome ranking, not standardised
Source “probability”KDE density mass, explicitly not treated as guilt probabilityDetection confidence levels plus vessel detectionsBayesian methods output posterior distributions

The differentiating contribution

It is not any single stage; each has precedent. It is the closed loop: an automated, explainable chain of detection → hindcast → AIS attribution → forward counterfactual, in which uncertainty is preserved at every hand-off and the final step is an attempt at falsification rather than confirmation. CleanSeaNet supplies data to human analysts; OilTrace performs the reasoning and shows its work.

17

Roadmap

What the honest limitations of §15 imply for the next build.

PriorityWork itemAddresses
NearDeploy the two-stage detector to the demonstration pathCloses the gap between measured and deployed performance
NearRun the sealed 450-scene test set, once onlyConverts “reproducible” into an independently verifiable number
NearStandardise the slick centroid across all presentation materialThe 35.6333 vs 35.63533 inconsistency
NextReplace the synthetic AIS fleet with a live feedMoves attribution from method demonstration to real data
NextExpose full per-particle ensembles to the clientRemoves the last representative-trajectory approximation in the UI
NextAutomated forcing acquisition with coverage and QC checksOperational deployment beyond the demonstration window
LaterAIS anomaly detection, the reserved zero-weight evidence slotDark vessels, transponder gaps, implausible manoeuvres
LaterPhysics-based spatial likelihood replacing exp(−d/λ)The isotropic-uncertainty limitation
LaterScore calibration against labelled historical incidentsTurns relative scores into stated probabilities
LaterSpatial indexing and multi-zone projectionScale to large AIS datasets and long transits
LaterScoring against multiple candidate source regionsCurrently only the top region is used for attribution
18

Reproducibility

The checklist that makes any result in this report re-derivable.

  1. Use the canonical Eastern Mediterranean incident (incident-mediterranean-001) and the UTC observation time 2024-08-26T12:00:00Z.
  2. Use the committed Mediterranean CMEMS current forcing and ERA5 wind forcing, not substitutes.
  3. Start the backend from the known-good commit containing the persistent forcing-reader fix.
  4. Exercise the production API path (/api/v1/...). Never recreate the physics in the client.
  5. Record trajectory timestamps and source-region geometry alongside every result, not just summary metrics.
  6. Keep candidate-specific release states and their counterfactual results separate, never averaged across candidates.
  7. Report the limitations of §15 explicitly in any demonstration or written material derived from this work.

Environment note

Forcing paths are configured per domain via DRIFT_FORCING_FALLBACK_CURRENTS_PATHS and DRIFT_FORCING_FALLBACK_WIND_PATHS. Allowed client origins are configured via ALLOWED_ORIGINS. Both live in the environment, not in committed code, so a deployment can be re-pointed without a rebuild.

A

Appendix A: Metrics & model maturity

How the detection numbers should be presented, and what may not be claimed from them.

The measured detection-stage results are: alert precision 81–84%; detection rate for slicks ≥ 10 hectares 64–67%; localization error approximately 85 m; area accuracy approximately ±1% on the demonstration scene; zero clean-water false alarms on the tested no-oil scenes; cross-sensor Dice of 0.65 on Sentinel-1 and 0.58 on PALSAR L-band on the stated external dataset; and 10–18 seconds per scene on CPU-only processing. These are prototype measurements and must be presented with their evaluation protocol, not as universal guarantees.

The training record shows E5_focal as the segmentation champion, with Dice 0.350, precision 0.471, and recall for slicks ≥ 10 ha of 0.667 on the deliberately harder validation setup. The scene-context model records macro-accuracy 0.731 with precision 0.813–0.845 in the same experiment log.

On future performance

Expected future performance is a target, not a promise. Further training, broader balanced data, hard-negative mining, scene-level validation and additional external datasets can reasonably be expected to improve robustness and the precision/recall trade-off. But no defensible exact future Dice, recall or precision value can be guaranteed from the current evidence, and none should be quoted.

B

Appendix B: Glossary

Terms used precisely in this report, defined once.

TermDefinition
AISAutomatic Identification System. The transponder broadcast carrying vessel identity, position, speed and course.
CounterfactualA forward simulation from a candidate's attributed release state, run to test whether that candidate could reproduce the observed slick.
Dice coefficientOverlap metric for segmentation: 2·|A ∩ B| / (|A| + |B|).
HDR (highest-density region)The smallest region containing a stated fraction of a probability density; here, 95% of the backward particle distribution.
HindcastBackward-in-time drift simulation reconstructing where observed oil came from.
Jaccard index|A ∩ B| / |A ∪ B|. Penalises both position error and size disagreement.
KDE density massThe fraction of simulated particles falling inside a region. Not a calibrated probability of the true source.
Lagrangian modelA model that tracks individual particles through a flow field, as opposed to solving for concentration on a fixed grid.
MMSIMaritime Mobile Service Identity. The 9-digit vessel identifier broadcast over AIS.
Predicted containment|predicted ∩ observed| / |predicted|. Answers whether predicted oil landed inside the observed slick, independent of size disagreement.
Release stateA concrete location and time, always a real AIS point, used to seed a candidate's forward simulation.
SARSynthetic-aperture radar. Oil damps capillary waves, so slicks appear as dark regions.
Source regionThe polygon, with time window, within which the hindcast places the probable discharge.
τ (tau)Overloaded by convention: the Gaussian timing scale in attribution (2 h), and the detector gate threshold in §05. Context distinguishes them.
C

Appendix C: References

Model, data and prior-art sources relied on in this work.

Models and libraries

  • OpenDrift: tutorial and general modelling framework documentation.
  • OpenDrift OpenOil: oil-drift module documentation and source documentation.
  • Shapely: GIS geometry library; the intersects() predicate used for trajectory–polygon testing.
  • NOAA GNOME / TAP: forward and backward oil-spill trajectory modelling; context for the forward simulation stage.

Environmental data

  • Copernicus Marine Service: Mediterranean Sea Physics (ocean current forcing).
  • ECMWF ERA5: hourly single-level data (10 m wind forcing).

Prior art and method provenance

  • EMSA CleanSeaNet service overview: Europe's operational SAR-based spill detection and AIS/vessel monitoring service, including backward and forward modelling capability and the human analysis workflow.
  • Distance-decay theory: exponential kernels and decay functions in spatial analysis; the basis for the spatial evidence term.
  • Gaussian temporal weighting: standard practice for correlating time-based events in signal processing and tracking; the basis for the temporal evidence term.
  • Project specification (internal): the frozen MVP design fixing the evidence weights and filter thresholds.