Risk Scoring
Turqoa assigns every tracked vessel a composite risk score from 0 to 100. The score is computed in real time from eight weighted factors and updated with each AIS position report. Risk scores drive automated alerting, drone dispatch, and operator prioritization.
Risk Scoring Model
The model evaluates each vessel independently against its current context (zone, speed, history, watchlists). Scores are recalculated on every AIS update and whenever new intelligence data becomes available.
Composite Risk Score (0-100)
Flag State ........... 15%
AIS Behavior ......... 15%
Vessel Type .......... 10%
Historical Incidents . 15%
Speed Anomaly ........ 10%
Route Deviation ...... 10%
Watchlist Match ...... 15%
Dwell Time ........... 10%
Risk Factors
1. Flag State (15%)
Evaluates the risk associated with a vessel's country of registry. Flag states are scored based on international maritime compliance records, port state control detention rates, and intelligence assessments.
| Flag Tier | Score Range | Examples |
|---|---|---|
| Tier 1 (Low Risk) | 0--20 | Norway, UK, Japan, Singapore |
| Tier 2 (Moderate) | 21--50 | Panama, Liberia, Marshall Islands |
| Tier 3 (Elevated) | 51--75 | Bolivia, Moldova, Tanzania |
| Tier 4 (High Risk) | 76--100 | Sanctioned states, unregistered |
flag_state_scoring:
source: paris_mou_lists # paris_mou_lists | custom | combined
custom_overrides:
- flag: PA # Panama
score: 30
- flag: LR # Liberia
score: 25
unknown_flag_score: 80
update_frequency: monthly
2. AIS Behavior (15%)
Analyzes the quality and consistency of AIS transmissions. Gaps, spoofing indicators, and "dark" periods raise the score.
| Behavior | Score Impact |
|---|---|
| Consistent transmissions | 0 |
| Short gaps (< 5 min) | +10 |
| Medium gaps (5--30 min) | +30 |
| Long gaps (> 30 min) | +60 |
| Position spoofing indicators | +80 |
| AIS disabled (dark vessel) | +100 |
| Identity mismatch (MMSI reuse) | +90 |
ais_behavior_scoring:
gap_thresholds:
short_gap_seconds: 300
medium_gap_seconds: 1800
long_gap_seconds: 3600
spoofing_detection:
enabled: true
max_speed_knots: 50 # flag positions implying > 50 kn
max_position_jump_nm: 2.0 # flag jumps > 2 NM between updates
dark_vessel_detection:
enabled: true
correlation_with_radar: true # check radar for unmatched tracks
3. Vessel Type (10%)
Assigns a baseline risk based on the vessel category. Certain vessel types carry inherently higher security risk.
| Vessel Type | Base Score |
|---|---|
| Passenger | 10 |
| Container ship | 20 |
| General cargo | 30 |
| Tanker (crude/chemical) | 40 |
| Fishing vessel | 35 |
| Pleasure craft | 25 |
| Tug/pilot | 5 |
| Unknown/unreported | 70 |
4. Historical Incidents (15%)
Checks the vessel's MMSI and IMO against Turqoa's incident database. Previous security events, detentions, or compliance violations increase the score.
def score_history(mmsi: str) -> float:
incidents = incident_db.query(mmsi=mmsi, years=5)
score = 0
for incident in incidents:
age_years = (now() - incident.date).days / 365
decay = max(0.2, 1.0 - (age_years * 0.15)) # decay over time
severity_scores = {
"security_breach": 80,
"detention": 60,
"compliance_violation": 40,
"minor_infraction": 20,
"advisory": 10,
}
score += severity_scores.get(incident.type, 30) * decay
return min(score, 100)
5. Speed Anomaly (10%)
Detects vessels moving unusually fast or slow for the area. Expected speeds are derived from zone configuration and vessel type.
speed_anomaly_scoring:
zone_speed_limits:
approach_channel:
min_knots: 3
max_knots: 10
anchorage:
min_knots: 0
max_knots: 2
open_water:
min_knots: 5
max_knots: 25
scoring:
within_limits: 0
minor_deviation_percent: 20 # score: 30
major_deviation_percent: 50 # score: 70
extreme_deviation_percent: 100 # score: 100
6. Route Deviation (10%)
Compares the vessel's actual track against its declared destination and expected route. Significant deviations trigger elevated scores.
| Deviation | Score |
|---|---|
| On expected route | 0 |
| Minor deviation (< 1 NM) | 15 |
| Moderate deviation (1--5 NM) | 40 |
| Major deviation (> 5 NM) | 70 |
| No destination declared | 50 |
| Heading away from declared destination | 80 |
7. Watchlist Match (15%)
Cross-references vessel identifiers (MMSI, IMO, name, owner) against intelligence watchlists. This is the highest-impact single factor.
watchlist_scoring:
sources:
- name: sanctions_list
type: ofac
match_score: 100
update_frequency: daily
- name: interpol_vessels
type: interpol
match_score: 95
update_frequency: daily
- name: port_authority_watchlist
type: custom
file: /data/watchlists/port_authority.csv
match_score: 70
update_frequency: weekly
- name: historical_suspects
type: internal
match_score: 60
matching:
exact_mmsi: true
exact_imo: true
fuzzy_name: true
fuzzy_threshold: 0.85
owner_matching: true
8. Dwell Time (10%)
Scores vessels based on how long they remain in or near sensitive zones relative to expected durations.
| Dwell Status | Score |
|---|---|
| Within expected duration | 0 |
| 1.5x expected duration | 30 |
| 2x expected duration | 60 |
| 3x+ expected duration | 90 |
| Loitering with no declared purpose | 80 |
Composite Scoring Algorithm
The final score is a weighted sum of all eight factors:
def compute_composite_score(vessel, zone_context, config):
weights = config.get("weights", DEFAULT_WEIGHTS)
factors = {
"flag_state": (score_flag_state(vessel.flag), weights["flag_state"]),
"ais_behavior": (score_ais_behavior(vessel.ais_history), weights["ais_behavior"]),
"vessel_type": (score_vessel_type(vessel.type_code), weights["vessel_type"]),
"historical": (score_history(vessel.mmsi), weights["historical"]),
"speed_anomaly": (score_speed(vessel.sog, zone_context), weights["speed_anomaly"]),
"route_deviation": (score_route(vessel.position, vessel.destination), weights["route_deviation"]),
"watchlist": (score_watchlist(vessel.mmsi, vessel.imo), weights["watchlist"]),
"dwell_time": (score_dwell(vessel.mmsi, zone_context), weights["dwell_time"]),
}
composite = sum(score * weight for score, weight in factors.values())
# Apply floor from any critical factor
critical_floor = 0
if factors["watchlist"][0] >= 90:
critical_floor = 75 # watchlist match forces minimum score
return round(max(composite, critical_floor), 1)
Critical Factor Override
Certain factor scores force a minimum composite score regardless of other factors:
| Condition | Minimum Composite Score |
|---|---|
| Watchlist match >= 90 | 75 |
| AIS disabled (dark vessel) | 60 |
| Sanctioned flag state | 70 |
Custom Risk Weights
Override the default weight distribution per site or zone:
risk_weights:
# Default weights
default:
flag_state: 0.15
ais_behavior: 0.15
vessel_type: 0.10
historical: 0.15
speed_anomaly: 0.10
route_deviation: 0.10
watchlist: 0.15
dwell_time: 0.10
# Override for LNG terminal - prioritize watchlist and AIS behavior
zone_overrides:
- zone: lng_terminal
weights:
flag_state: 0.10
ais_behavior: 0.20
vessel_type: 0.05
historical: 0.10
speed_anomaly: 0.10
route_deviation: 0.10
watchlist: 0.25
dwell_time: 0.10
# Override for anchorage - prioritize dwell time
- zone: outer_anchorage
weights:
flag_state: 0.15
ais_behavior: 0.10
vessel_type: 0.10
historical: 0.10
speed_anomaly: 0.05
route_deviation: 0.10
watchlist: 0.15
dwell_time: 0.25
Weights must always sum to 1.0. The system validates this on configuration load and rejects invalid weight sets.
Alert Thresholds
Configure which risk score ranges trigger which responses:
alert_thresholds:
- range: [0, 25]
level: low
actions:
- log_event
- range: [26, 50]
level: medium
actions:
- log_event
- notify_operator
- range: [51, 75]
level: high
actions:
- log_event
- notify_operator
- dispatch_drone: alarm_verification
- notify_supervisor
- range: [76, 100]
level: critical
actions:
- log_event
- notify_all_operators
- dispatch_drone: perimeter_sweep
- escalate_to: [security_manager, port_authority, coast_guard]
- lock_gates: all
Hysteresis
To prevent alert flapping, thresholds include configurable hysteresis:
hysteresis:
enabled: true
upgrade_hold_seconds: 10 # score must stay above threshold for 10s to upgrade
downgrade_hold_seconds: 60 # score must stay below threshold for 60s to downgrade
Querying Risk Scores via API
Get Current Score for a Vessel
curl https://api.turqoa.com/v1/maritime/risk/vessels/123456789 \
-H "Authorization: Bearer $TURQOA_API_KEY"
{
"mmsi": 123456789,
"vessel_name": "MV Nordic Star",
"composite_score": 42.3,
"level": "medium",
"factors": {
"flag_state": { "score": 25, "weight": 0.15, "weighted": 3.75 },
"ais_behavior": { "score": 10, "weight": 0.15, "weighted": 1.50 },
"vessel_type": { "score": 30, "weight": 0.10, "weighted": 3.00 },
"historical": { "score": 60, "weight": 0.15, "weighted": 9.00 },
"speed_anomaly": { "score": 0, "weight": 0.10, "weighted": 0.00 },
"route_deviation": { "score": 15, "weight": 0.10, "weighted": 1.50 },
"watchlist": { "score": 70, "weight": 0.15, "weighted": 10.50 },
"dwell_time": { "score": 130, "weight": 0.10, "weighted": 13.00 }
},
"updated_at": "2026-04-06T10:32:15Z"
}
Get All High-Risk Vessels
curl "https://api.turqoa.com/v1/maritime/risk/vessels?min_score=51&sort=score_desc" \
-H "Authorization: Bearer $TURQOA_API_KEY"