Turqoa Docs

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 TierScore RangeExamples
Tier 1 (Low Risk)0--20Norway, UK, Japan, Singapore
Tier 2 (Moderate)21--50Panama, Liberia, Marshall Islands
Tier 3 (Elevated)51--75Bolivia, Moldova, Tanzania
Tier 4 (High Risk)76--100Sanctioned 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.

BehaviorScore Impact
Consistent transmissions0
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 TypeBase Score
Passenger10
Container ship20
General cargo30
Tanker (crude/chemical)40
Fishing vessel35
Pleasure craft25
Tug/pilot5
Unknown/unreported70

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.

DeviationScore
On expected route0
Minor deviation (< 1 NM)15
Moderate deviation (1--5 NM)40
Major deviation (> 5 NM)70
No destination declared50
Heading away from declared destination80

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 StatusScore
Within expected duration0
1.5x expected duration30
2x expected duration60
3x+ expected duration90
Loitering with no declared purpose80

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:

ConditionMinimum Composite Score
Watchlist match >= 9075
AIS disabled (dark vessel)60
Sanctioned flag state70

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"