gomd 2.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- gomd/__init__.py +380 -0
- gomd/__main__.py +7 -0
- gomd/cli.py +76 -0
- gomd/client.py +1372 -0
- gomd/dynamics.py +629 -0
- gomd/maritime.py +952 -0
- gomd/ocean.py +1646 -0
- gomd/spatial.py +222 -0
- gomd/stats.py +1086 -0
- gomd-2.1.0.dist-info/METADATA +277 -0
- gomd-2.1.0.dist-info/RECORD +15 -0
- gomd-2.1.0.dist-info/WHEEL +5 -0
- gomd-2.1.0.dist-info/entry_points.txt +2 -0
- gomd-2.1.0.dist-info/licenses/LICENSE +21 -0
- gomd-2.1.0.dist-info/top_level.txt +1 -0
gomd/maritime.py
ADDED
|
@@ -0,0 +1,952 @@
|
|
|
1
|
+
"""
|
|
2
|
+
gomd.maritime — Maritime Decarbonization, IMO CII, EU ETS / FuelEU Maritime,
|
|
3
|
+
Poseidon Principles Ship Finance Alignment, and Paris MoU PSC Risk Models
|
|
4
|
+
============================================================================
|
|
5
|
+
Implements regulatory and econometric formulas for international shipping:
|
|
6
|
+
1. IMO Carbon Intensity Indicator (CII) Attained AER, Required CII, and A-E Rating
|
|
7
|
+
(Resolutions MEPC.336(76), MEPC.337(76), MEPC.338(76), MEPC.354(78))
|
|
8
|
+
2. EU ETS Maritime Directive (2023/959) & FuelEU Maritime (2023/1805) Carbon Cost
|
|
9
|
+
3. Poseidon Principles Climate Alignment Delta (Delta_PP %) & Green Loan Pricing
|
|
10
|
+
4. Paris MoU / Med MoU New Inspection Regime (NIR) Ship Risk Profile & Detention Probability
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
import math
|
|
16
|
+
from typing import Any, Dict, Optional, Union
|
|
17
|
+
|
|
18
|
+
# Official IMO MEPC.364(79) / EEDI Carbon Conversion Factors C_F (t CO2 / t fuel)
|
|
19
|
+
IMO_CF_FACTORS: Dict[str, float] = {
|
|
20
|
+
"hfo": 3.114,
|
|
21
|
+
"vlsfo": 3.151,
|
|
22
|
+
"lfo": 3.151,
|
|
23
|
+
"mdo": 3.206,
|
|
24
|
+
"mgo": 3.206,
|
|
25
|
+
"lng": 2.750,
|
|
26
|
+
"lpg_propane": 3.000,
|
|
27
|
+
"lpg_butane": 3.030,
|
|
28
|
+
"methanol": 1.375,
|
|
29
|
+
"ethanol": 1.913,
|
|
30
|
+
"bio_methanol": 0.275,
|
|
31
|
+
"e_ammonia": 0.000,
|
|
32
|
+
"e_hydrogen": 0.000,
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
# IMO MEPC.337(76) Table 1 Reference Line Parameters (a, c) & MEPC.354(78) dd vectors (d1..d4)
|
|
36
|
+
IMO_CII_SHIP_PARAMS: Dict[str, Dict[str, Any]] = {
|
|
37
|
+
"bulk_carrier": {"a": 4745.0, "c": 0.622, "dd": (0.86, 0.94, 1.06, 1.18)},
|
|
38
|
+
"tanker": {"a": 5247.0, "c": 0.610, "dd": (0.82, 0.93, 1.08, 1.28)},
|
|
39
|
+
"container": {"a": 1984.0, "c": 0.489, "dd": (0.83, 0.94, 1.07, 1.19)},
|
|
40
|
+
"general_cargo": {"a": 31948.0, "c": 0.792, "dd": (0.83, 0.94, 1.06, 1.19)},
|
|
41
|
+
"lng_carrier": {"a": 14479E7, "c": 2.673, "dd": (0.89, 0.98, 1.06, 1.13)},
|
|
42
|
+
"ro_ro_cargo": {"a": 5739.0, "c": 0.631, "dd": (0.86, 0.94, 1.06, 1.16)},
|
|
43
|
+
"cruise_passenger": {"a": 930.0, "c": 0.383, "dd": (0.87, 0.95, 1.06, 1.16)},
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
# IMO MEPC.338(76) Annual Reduction Factors Z (%) relative to 2019 reference line
|
|
47
|
+
IMO_CII_REDUCTION_Z: Dict[int, float] = {
|
|
48
|
+
2019: 0.0,
|
|
49
|
+
2020: 1.0,
|
|
50
|
+
2021: 2.0,
|
|
51
|
+
2022: 3.0,
|
|
52
|
+
2023: 5.0,
|
|
53
|
+
2024: 7.0,
|
|
54
|
+
2025: 9.0,
|
|
55
|
+
2026: 11.0,
|
|
56
|
+
2027: 13.5,
|
|
57
|
+
2028: 16.0,
|
|
58
|
+
2029: 18.5,
|
|
59
|
+
2030: 21.5,
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def calculate_imo_cii(
|
|
64
|
+
fuel_consumption_tonnes: Union[float, Dict[str, float]],
|
|
65
|
+
dwt_or_gt: float,
|
|
66
|
+
distance_nm: float,
|
|
67
|
+
ship_type: str = "bulk_carrier",
|
|
68
|
+
fuel_type: str = "vlsfo",
|
|
69
|
+
year: int = 2026,
|
|
70
|
+
) -> Dict[str, Any]:
|
|
71
|
+
"""
|
|
72
|
+
Compute the IMO Attained Annual Efficiency Ratio (AER), Required CII,
|
|
73
|
+
and A/B/C/D/E Operational Carbon Intensity Rating (MEPC.336(76)–MEPC.354(78)).
|
|
74
|
+
|
|
75
|
+
Parameters
|
|
76
|
+
----------
|
|
77
|
+
fuel_consumption_tonnes : float or dict[str, float]
|
|
78
|
+
Either total fuel mass in metric tonnes (using `fuel_type`) or a dictionary
|
|
79
|
+
mapping fuel names to metric tonnes, e.g. `{"vlsfo": 4200, "mdo": 350}`.
|
|
80
|
+
dwt_or_gt : float
|
|
81
|
+
Vessel Deadweight Tonnage (DWT) or Gross Tonnage (for cruise/ro-ro passenger).
|
|
82
|
+
distance_nm : float
|
|
83
|
+
Total distance sailed in nautical miles over the reporting period.
|
|
84
|
+
ship_type : str, default "bulk_carrier"
|
|
85
|
+
One of `'bulk_carrier'`, `'tanker'`, `'container'`, `'general_cargo'`,
|
|
86
|
+
`'lng_carrier'`, `'ro_ro_cargo'`, `'cruise_passenger'`.
|
|
87
|
+
fuel_type : str, default "vlsfo"
|
|
88
|
+
Fuel type when `fuel_consumption_tonnes` is a single float.
|
|
89
|
+
year : int, default 2026
|
|
90
|
+
Reporting calendar year (2019–2030).
|
|
91
|
+
"""
|
|
92
|
+
if isinstance(fuel_consumption_tonnes, dict):
|
|
93
|
+
co2_tonnes = 0.0
|
|
94
|
+
for f_name, f_mass in fuel_consumption_tonnes.items():
|
|
95
|
+
cf = IMO_CF_FACTORS.get(f_name.strip().lower(), 3.151)
|
|
96
|
+
co2_tonnes += float(f_mass) * cf
|
|
97
|
+
else:
|
|
98
|
+
cf = IMO_CF_FACTORS.get(fuel_type.strip().lower(), 3.151)
|
|
99
|
+
co2_tonnes = float(fuel_consumption_tonnes) * cf
|
|
100
|
+
|
|
101
|
+
capacity = max(float(dwt_or_gt), 1.0)
|
|
102
|
+
dist = max(float(distance_nm), 1.0)
|
|
103
|
+
|
|
104
|
+
# Attained AER in g CO2 / (dwt * nm)
|
|
105
|
+
attained_aer = (co2_tonnes * 1e6) / (capacity * dist)
|
|
106
|
+
|
|
107
|
+
st_key = ship_type.strip().lower()
|
|
108
|
+
params = IMO_CII_SHIP_PARAMS.get(st_key, IMO_CII_SHIP_PARAMS["bulk_carrier"])
|
|
109
|
+
# Cap DWT for bulk carrier >= 279,000 per MEPC.337(76)
|
|
110
|
+
ref_cap = min(capacity, 279000.0) if st_key == "bulk_carrier" else capacity
|
|
111
|
+
cii_ref_2019 = params["a"] * (ref_cap ** (-params["c"]))
|
|
112
|
+
|
|
113
|
+
z_pct = IMO_CII_REDUCTION_Z.get(int(year), 11.0 if year <= 2026 else 21.5)
|
|
114
|
+
required_cii = cii_ref_2019 * (1.0 - z_pct / 100.0)
|
|
115
|
+
|
|
116
|
+
d1, d2, d3, d4 = params["dd"]
|
|
117
|
+
ratio = attained_aer / required_cii
|
|
118
|
+
if ratio <= d1:
|
|
119
|
+
rating = "A"
|
|
120
|
+
elif ratio <= d2:
|
|
121
|
+
rating = "B"
|
|
122
|
+
elif ratio <= d3:
|
|
123
|
+
rating = "C"
|
|
124
|
+
elif ratio <= d4:
|
|
125
|
+
rating = "D"
|
|
126
|
+
else:
|
|
127
|
+
rating = "E"
|
|
128
|
+
|
|
129
|
+
boundaries = {
|
|
130
|
+
"A_max": round(required_cii * d1, 4),
|
|
131
|
+
"B_max": round(required_cii * d2, 4),
|
|
132
|
+
"C_max": round(required_cii * d3, 4),
|
|
133
|
+
"D_max": round(required_cii * d4, 4),
|
|
134
|
+
}
|
|
135
|
+
return {
|
|
136
|
+
"ship_type": st_key,
|
|
137
|
+
"year": int(year),
|
|
138
|
+
"total_co2_tonnes": round(co2_tonnes, 2),
|
|
139
|
+
"attained_aer_g_dwt_nm": round(attained_aer, 4),
|
|
140
|
+
"attained_aer_g_co2_dwt_nm": round(attained_aer, 4),
|
|
141
|
+
"cii_ref_2019_g_dwt_nm": round(cii_ref_2019, 4),
|
|
142
|
+
"reduction_factor_z_pct": z_pct,
|
|
143
|
+
"required_cii_g_dwt_nm": round(required_cii, 4),
|
|
144
|
+
"attained_to_required_ratio": round(ratio, 4),
|
|
145
|
+
"cii_rating": rating,
|
|
146
|
+
"boundaries_g_dwt_nm": boundaries,
|
|
147
|
+
"rating_boundaries": boundaries,
|
|
148
|
+
"regulatory_action_required": rating in ("D", "E"),
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def calculate_eu_ets_exposure(
|
|
153
|
+
co2_tonnes: float,
|
|
154
|
+
ch4_tonnes: float = 0.0,
|
|
155
|
+
n2o_tonnes: float = 0.0,
|
|
156
|
+
voyage_scope: str = "intra_eu",
|
|
157
|
+
year: int = 2026,
|
|
158
|
+
eua_price_eur: float = 75.0,
|
|
159
|
+
) -> Dict[str, Any]:
|
|
160
|
+
"""
|
|
161
|
+
Calculate maritime GHG emissions liability and EU Allowance (EUA) cost under
|
|
162
|
+
Directive (EU) 2023/959 (EU ETS extension to maritime transport).
|
|
163
|
+
|
|
164
|
+
Includes:
|
|
165
|
+
- IPCC AR5 GWP_100 conversion for Methane (CH4 = 28) and Nitrous Oxide (N2O = 265)
|
|
166
|
+
mandatory from 2026 onwards
|
|
167
|
+
- Geographical scope multiplier: 100% for intra-EU / EU port berth, 50% for extra-EU
|
|
168
|
+
- Phase-in surrender percentage: 40% (2024), 70% (2025), 100% (2026+)
|
|
169
|
+
"""
|
|
170
|
+
scope_mult = 0.50 if voyage_scope.strip().lower() in ("extra_eu", "eu_extra", "50%", "half") else 1.00
|
|
171
|
+
phase_in = 0.40 if year == 2024 else (0.70 if year == 2025 else 1.00)
|
|
172
|
+
|
|
173
|
+
co2e_total = float(co2_tonnes) + (28.0 * float(ch4_tonnes) + 265.0 * float(n2o_tonnes) if year >= 2026 else 0.0)
|
|
174
|
+
scoped_co2e = co2e_total * scope_mult
|
|
175
|
+
surrender_euas = scoped_co2e * phase_in
|
|
176
|
+
cost_eur = surrender_euas * float(eua_price_eur)
|
|
177
|
+
|
|
178
|
+
return {
|
|
179
|
+
"year": int(year),
|
|
180
|
+
"voyage_scope": voyage_scope,
|
|
181
|
+
"scope_multiplier": scope_mult,
|
|
182
|
+
"phase_in_rate": phase_in,
|
|
183
|
+
"total_co2e_tonnes": round(co2e_total, 2),
|
|
184
|
+
"scoped_co2e_tonnes": round(scoped_co2e, 2),
|
|
185
|
+
"surrender_euas_required": round(surrender_euas, 2),
|
|
186
|
+
"surrender_allowances_eua": round(surrender_euas, 2),
|
|
187
|
+
"eua_price_eur": round(float(eua_price_eur), 2),
|
|
188
|
+
"estimated_ets_cost_eur": round(cost_eur, 2),
|
|
189
|
+
"estimated_cost_eur": round(cost_eur, 2),
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
def poseidon_principles_alignment(
|
|
194
|
+
attained_aer: float,
|
|
195
|
+
required_trajectory_aer: float,
|
|
196
|
+
loan_outstanding_usd: float = 25_000_000.0,
|
|
197
|
+
base_margin_bps: float = 225.0,
|
|
198
|
+
) -> Dict[str, Any]:
|
|
199
|
+
"""
|
|
200
|
+
Evaluate vessel or portfolio climate alignment under the Poseidon Principles
|
|
201
|
+
Technical Guidance and simulate Sustainability-Linked Loan (SLL) margin ratchets.
|
|
202
|
+
|
|
203
|
+
Formula:
|
|
204
|
+
Delta_PP (%) = ((AER_attained - AER_trajectory) / AER_trajectory) * 100
|
|
205
|
+
"""
|
|
206
|
+
att = float(attained_aer)
|
|
207
|
+
traj = max(float(required_trajectory_aer), 1e-6)
|
|
208
|
+
delta_pct = ((att - traj) / traj) * 100.0
|
|
209
|
+
aligned = delta_pct <= 0.0
|
|
210
|
+
|
|
211
|
+
# Green Loan / SLL interest rate ratchet (-30 bps discount to +25 bps penalty)
|
|
212
|
+
if delta_pct <= -10.0:
|
|
213
|
+
bps_adj = -30.0
|
|
214
|
+
elif delta_pct <= 0.0:
|
|
215
|
+
bps_adj = -15.0
|
|
216
|
+
elif delta_pct <= 10.0:
|
|
217
|
+
bps_adj = 10.0
|
|
218
|
+
else:
|
|
219
|
+
bps_adj = 25.0
|
|
220
|
+
|
|
221
|
+
annual_interest_delta_usd = float(loan_outstanding_usd) * (bps_adj / 10000.0)
|
|
222
|
+
|
|
223
|
+
return {
|
|
224
|
+
"attained_aer": round(att, 4),
|
|
225
|
+
"trajectory_aer": round(traj, 4),
|
|
226
|
+
"alignment_delta_pct": round(delta_pct, 2),
|
|
227
|
+
"poseidon_aligned": aligned,
|
|
228
|
+
"sll_margin_adjustment_bps": bps_adj,
|
|
229
|
+
"effective_margin_bps": round(float(base_margin_bps) + bps_adj, 2),
|
|
230
|
+
"annual_interest_saving_or_penalty_usd": round(annual_interest_delta_usd, 2),
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
def psc_detention_risk_score(
|
|
235
|
+
ship_age_years: float,
|
|
236
|
+
flag_tier: str = "white",
|
|
237
|
+
ro_performance: str = "high",
|
|
238
|
+
deficiencies_36m: int = 0,
|
|
239
|
+
detentions_36m: int = 0,
|
|
240
|
+
ship_type: str = "bulk_carrier",
|
|
241
|
+
) -> Dict[str, Any]:
|
|
242
|
+
"""
|
|
243
|
+
Compute Paris MoU / Mediterranean MoU New Inspection Regime (NIR) Ship Risk
|
|
244
|
+
Profile points, classification (HRS / SRS / LRS), and logistic detention probability.
|
|
245
|
+
"""
|
|
246
|
+
points = 0
|
|
247
|
+
st = ship_type.strip().lower()
|
|
248
|
+
if st in ("bulk_carrier", "tanker", "chemical_tanker", "gas_carrier", "passenger", "cruise_passenger"):
|
|
249
|
+
points += 2
|
|
250
|
+
if float(ship_age_years) > 12.0:
|
|
251
|
+
points += 1
|
|
252
|
+
|
|
253
|
+
ft = flag_tier.strip().lower()
|
|
254
|
+
if ft == "black":
|
|
255
|
+
points += 2
|
|
256
|
+
elif ft == "grey":
|
|
257
|
+
points += 1
|
|
258
|
+
|
|
259
|
+
ro = ro_performance.strip().lower()
|
|
260
|
+
if ro in ("low", "very_low"):
|
|
261
|
+
points += 1
|
|
262
|
+
|
|
263
|
+
if int(detentions_36m) >= 2:
|
|
264
|
+
points += 2
|
|
265
|
+
elif int(detentions_36m) == 1:
|
|
266
|
+
points += 1
|
|
267
|
+
|
|
268
|
+
if int(deficiencies_36m) > 15:
|
|
269
|
+
points += 2
|
|
270
|
+
elif int(deficiencies_36m) > 5:
|
|
271
|
+
points += 1
|
|
272
|
+
|
|
273
|
+
if points >= 5:
|
|
274
|
+
profile = "HRS (High Risk Ship — Priority Inspection Window: 5-6 months)"
|
|
275
|
+
elif points == 0 and ft == "white" and ro == "high" and int(detentions_36m) == 0 and int(deficiencies_36m) <= 5:
|
|
276
|
+
profile = "LRS (Low Risk Ship — Extended Window: 24-36 months)"
|
|
277
|
+
else:
|
|
278
|
+
profile = "SRS (Standard Risk Ship — Window: 10-12 months)"
|
|
279
|
+
|
|
280
|
+
# Calibrated logistic detention probability
|
|
281
|
+
logit = -3.45 + 0.38 * points + 0.045 * max(float(ship_age_years) - 10.0, 0.0) + 0.12 * int(deficiencies_36m)
|
|
282
|
+
prob = 1.0 / (1.0 + math.exp(-logit))
|
|
283
|
+
|
|
284
|
+
return {
|
|
285
|
+
"nir_risk_points": points,
|
|
286
|
+
"ship_risk_profile": profile,
|
|
287
|
+
"estimated_detention_probability": round(prob, 4),
|
|
288
|
+
"estimated_detention_risk_pct": round(prob * 100.0, 2),
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
# Regulation (EU) 2023/1805 FuelEU Maritime default Well-to-Wake (WtW) GHG intensities (gCO2e/MJ)
|
|
293
|
+
# and Lower Calorific Values (LCV, MJ/g)
|
|
294
|
+
FUELEU_WTW_PROFILES: Dict[str, Dict[str, Any]] = {
|
|
295
|
+
"HFO": {"lcv_mj_per_g": 0.0405, "wtw_gco2e_mj": 91.74, "is_rfnbo": False},
|
|
296
|
+
"VLSFO": {"lcv_mj_per_g": 0.0410, "wtw_gco2e_mj": 91.16, "is_rfnbo": False},
|
|
297
|
+
"MGO": {"lcv_mj_per_g": 0.0427, "wtw_gco2e_mj": 90.77, "is_rfnbo": False},
|
|
298
|
+
"LNG_OTTO": {"lcv_mj_per_g": 0.0491, "wtw_gco2e_mj": 76.20, "is_rfnbo": False},
|
|
299
|
+
"LNG_DIESEL": {"lcv_mj_per_g": 0.0491, "wtw_gco2e_mj": 69.50, "is_rfnbo": False},
|
|
300
|
+
"BIO_LNG": {"lcv_mj_per_g": 0.0491, "wtw_gco2e_mj": 24.50, "is_rfnbo": False},
|
|
301
|
+
"BIO_METHANOL": {"lcv_mj_per_g": 0.0199, "wtw_gco2e_mj": 14.80, "is_rfnbo": False},
|
|
302
|
+
"E_METHANOL": {"lcv_mj_per_g": 0.0199, "wtw_gco2e_mj": 6.40, "is_rfnbo": True},
|
|
303
|
+
"E_AMMONIA": {"lcv_mj_per_g": 0.0186, "wtw_gco2e_mj": 3.20, "is_rfnbo": True},
|
|
304
|
+
"E_HYDROGEN": {"lcv_mj_per_g": 0.1200, "wtw_gco2e_mj": 2.10, "is_rfnbo": True},
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
|
|
308
|
+
def calculate_fueleu_maritime(
|
|
309
|
+
fuel_mix_tonnes: Dict[str, float],
|
|
310
|
+
year: int = 2025,
|
|
311
|
+
voyage_scope_share: float = 1.0,
|
|
312
|
+
ops_shore_power_mj: float = 0.0,
|
|
313
|
+
**kwargs: Any,
|
|
314
|
+
) -> Dict[str, Any]:
|
|
315
|
+
"""
|
|
316
|
+
Calculate Well-to-Wake (WtW) GHG Intensity (gCO2e/MJ), RFNBO 2x Multiplier Reward,
|
|
317
|
+
Compliance Balance (tCO2e), and Financial Penalty (EUR) under Regulation (EU) 2023/1805
|
|
318
|
+
(FuelEU Maritime, 2025-2050).
|
|
319
|
+
|
|
320
|
+
Post-2020 Regulatory & Scientific Reference:
|
|
321
|
+
- Regulation (EU) 2023/1805 of the European Parliament and of the Council of
|
|
322
|
+
13 September 2023 on the use of renewable and low-carbon fuels in maritime
|
|
323
|
+
transport (FuelEU Maritime).
|
|
324
|
+
- Baseline 2020 fleet WtW intensity: 91.16 gCO2e/MJ.
|
|
325
|
+
- Step-down reductions: 2025 (-2%), 2030 (-6%), 2035 (-14.5%), 2040 (-31%),
|
|
326
|
+
2045 (-62%), 2050 (-80%).
|
|
327
|
+
- Financial Penalty for deficit: EUR 2,400 per metric tonne of VLSFO-equivalent
|
|
328
|
+
energy (41,000 MJ/t).
|
|
329
|
+
"""
|
|
330
|
+
baseline_2020 = 91.16
|
|
331
|
+
yr = int(year)
|
|
332
|
+
if yr < 2025:
|
|
333
|
+
red_pct = 0.0
|
|
334
|
+
elif yr < 2030:
|
|
335
|
+
red_pct = 2.0
|
|
336
|
+
elif yr < 2035:
|
|
337
|
+
red_pct = 6.0
|
|
338
|
+
elif yr < 2040:
|
|
339
|
+
red_pct = 14.5
|
|
340
|
+
elif yr < 2045:
|
|
341
|
+
red_pct = 31.0
|
|
342
|
+
elif yr < 2050:
|
|
343
|
+
red_pct = 62.0
|
|
344
|
+
else:
|
|
345
|
+
red_pct = 80.0
|
|
346
|
+
|
|
347
|
+
target_wtw = baseline_2020 * (1.0 - red_pct / 100.0)
|
|
348
|
+
scope = min(max(float(voyage_scope_share), 0.0), 1.0)
|
|
349
|
+
|
|
350
|
+
total_energy_mj = max(float(ops_shore_power_mj), 0.0) * scope
|
|
351
|
+
weighted_energy_denom_mj = max(float(ops_shore_power_mj), 0.0) * scope
|
|
352
|
+
total_wtw_grams = 0.0
|
|
353
|
+
|
|
354
|
+
for f_name, tonnes in fuel_mix_tonnes.items():
|
|
355
|
+
key = f_name.strip().upper()
|
|
356
|
+
prof = FUELEU_WTW_PROFILES.get(key, FUELEU_WTW_PROFILES["VLSFO"])
|
|
357
|
+
mass_g = max(float(tonnes), 0.0) * 1e6 * scope
|
|
358
|
+
e_mj = mass_g * float(prof["lcv_mj_per_g"])
|
|
359
|
+
total_energy_mj += e_mj
|
|
360
|
+
# RFNBO 2x reward multiplier in denominator between 2025 and 2033 per Annex I
|
|
361
|
+
rfnbo_mult = 2.0 if (prof["is_rfnbo"] and 2025 <= yr <= 2033) else 1.0
|
|
362
|
+
weighted_energy_denom_mj += e_mj * rfnbo_mult
|
|
363
|
+
total_wtw_grams += e_mj * float(prof["wtw_gco2e_mj"])
|
|
364
|
+
|
|
365
|
+
if weighted_energy_denom_mj <= 0.0:
|
|
366
|
+
raise ValueError("Total energy in fuel_mix_tonnes must be positive.")
|
|
367
|
+
|
|
368
|
+
attained_wtw = total_wtw_grams / weighted_energy_denom_mj
|
|
369
|
+
# Compliance Balance in grams CO2e: (GHGIE_target - GHGIE_actual) * Energy_MJ
|
|
370
|
+
cb_grams = (target_wtw - attained_wtw) * total_energy_mj
|
|
371
|
+
cb_tco2e = cb_grams / 1e6
|
|
372
|
+
|
|
373
|
+
# If Compliance Balance < 0, Penalty = |CB_grams| / (attained_wtw * 41,000) * 2,400 EUR
|
|
374
|
+
penalty_eur = 0.0
|
|
375
|
+
vlsfo_eq_deficit_tonnes = 0.0
|
|
376
|
+
if cb_grams < 0.0 and attained_wtw > 0.0:
|
|
377
|
+
vlsfo_eq_deficit_tonnes = abs(cb_grams) / (attained_wtw * 41000.0)
|
|
378
|
+
penalty_eur = vlsfo_eq_deficit_tonnes * 2400.0
|
|
379
|
+
|
|
380
|
+
status_str = "SURPLUS (Bankable / Poolable)" if cb_tco2e >= 0.0 else "DEFICIT (Penalty / Pooling Required)"
|
|
381
|
+
return {
|
|
382
|
+
"year": yr,
|
|
383
|
+
"baseline_2020_gco2e_mj": baseline_2020,
|
|
384
|
+
"reduction_target_pct": red_pct,
|
|
385
|
+
"target_wtw_gco2e_mj": round(target_wtw, 4),
|
|
386
|
+
"attained_wtw_gco2e_mj": round(attained_wtw, 4),
|
|
387
|
+
"attained_wtw_ghg_intensity_gco2e_mj": round(attained_wtw, 4),
|
|
388
|
+
"total_covered_energy_gj": round(total_energy_mj / 1000.0, 2),
|
|
389
|
+
"compliance_balance_tco2e": round(cb_tco2e, 2),
|
|
390
|
+
"status": status_str,
|
|
391
|
+
"compliance_status": status_str,
|
|
392
|
+
"vlsfo_equivalent_deficit_tonnes": round(vlsfo_eq_deficit_tonnes, 2),
|
|
393
|
+
"fueleu_penalty_eur": round(penalty_eur, 2),
|
|
394
|
+
"reference": "Regulation (EU) 2023/1805 (FuelEU Maritime Annex I & IV)",
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
|
|
398
|
+
def imo_2023_strive_trajectory(
|
|
399
|
+
baseline_2008_aer: float,
|
|
400
|
+
current_aer: float,
|
|
401
|
+
annual_emissions_tco2e: float,
|
|
402
|
+
annual_efficiency_gain_pct: float = 1.8,
|
|
403
|
+
current_year: int = 2026,
|
|
404
|
+
shadow_carbon_levy_usd: float = 150.0,
|
|
405
|
+
) -> Dict[str, Any]:
|
|
406
|
+
"""
|
|
407
|
+
Evaluate Vessel Alignment against the IMO 2023 Revised GHG Strategy (Resolution
|
|
408
|
+
MEPC.377(80), July 2023) Indicative Checkpoints (2030 & 2040 Base vs. Striving),
|
|
409
|
+
identify the Stranded-Asset Year, and compute Maritime Carbon Value-at-Risk (CVaR).
|
|
410
|
+
|
|
411
|
+
Post-2020 Regulatory & Scientific Reference:
|
|
412
|
+
- IMO Resolution MEPC.377(80) (Adopted 7 July 2023): 2023 IMO Strategy on
|
|
413
|
+
Reduction of GHG Emissions from Ships.
|
|
414
|
+
- Checkpoints:
|
|
415
|
+
* 2030: >=40% carbon intensity reduction (vs 2008); 20% striving for 30% total GHG reduction.
|
|
416
|
+
* 2040: 70% striving for 80% total GHG reduction.
|
|
417
|
+
* 2050: Net-zero GHG emissions.
|
|
418
|
+
"""
|
|
419
|
+
b08 = max(float(baseline_2008_aer), 1e-4)
|
|
420
|
+
cur = max(float(current_aer), 1e-4)
|
|
421
|
+
g_rate = float(annual_efficiency_gain_pct) / 100.0
|
|
422
|
+
|
|
423
|
+
# IMO MEPC.377(80) trajectory checkpoints relative to 2008 baseline
|
|
424
|
+
checkpoints = {
|
|
425
|
+
2030: {"base_aer": round(b08 * 0.60, 4), "strive_aer": round(b08 * 0.50, 4)},
|
|
426
|
+
2040: {"base_aer": round(b08 * 0.30, 4), "strive_aer": round(b08 * 0.20, 4)},
|
|
427
|
+
2050: {"base_aer": 0.0, "strive_aer": 0.0},
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
# Find stranded asset year where vessel's projected AER exceeds linear striving corridor
|
|
431
|
+
stranded_year = None
|
|
432
|
+
for yr in range(int(current_year), 2051):
|
|
433
|
+
years_ahead = yr - int(current_year)
|
|
434
|
+
proj_vessel_aer = cur * ((1.0 - g_rate) ** years_ahead)
|
|
435
|
+
# Linear interpolation of striving trajectory between 2026 (0.65*b08), 2030 (0.50*b08), 2040 (0.20*b08), 2050 (0.0)
|
|
436
|
+
if yr <= 2030:
|
|
437
|
+
allowed = b08 * (0.65 - 0.15 * ((yr - 2025) / 5.0))
|
|
438
|
+
elif yr <= 2040:
|
|
439
|
+
allowed = b08 * (0.50 - 0.30 * ((yr - 2030) / 10.0))
|
|
440
|
+
else:
|
|
441
|
+
allowed = b08 * max(0.0, 0.20 - 0.20 * ((yr - 2040) / 10.0))
|
|
442
|
+
if proj_vessel_aer > allowed and stranded_year is None:
|
|
443
|
+
stranded_year = yr
|
|
444
|
+
|
|
445
|
+
# 10-year discounted Carbon Value-at-Risk under universal maritime GHG levy
|
|
446
|
+
em = max(float(annual_emissions_tco2e), 0.0)
|
|
447
|
+
levy = max(float(shadow_carbon_levy_usd), 0.0)
|
|
448
|
+
cvar_10yr_usd = sum(
|
|
449
|
+
(em * ((1.0 - g_rate) ** t) * levy) / ((1.04) ** t) for t in range(1, 11)
|
|
450
|
+
)
|
|
451
|
+
|
|
452
|
+
return {
|
|
453
|
+
"baseline_2008_aer": round(b08, 4),
|
|
454
|
+
"current_aer": round(cur, 4),
|
|
455
|
+
"reduction_vs_2008_pct": round((1.0 - cur / b08) * 100.0, 2),
|
|
456
|
+
"mepc80_checkpoints": checkpoints,
|
|
457
|
+
"stranded_asset_year_strive": stranded_year if stranded_year is not None else 2050,
|
|
458
|
+
"maritime_carbon_var_10yr_usd": round(cvar_10yr_usd, 2),
|
|
459
|
+
"reference": "IMO Resolution MEPC.377(80) (July 2023 Revised GHG Strategy)",
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
|
|
463
|
+
def ais_speed_power_emissions(
|
|
464
|
+
mcr_kw: float,
|
|
465
|
+
design_speed_kn: float,
|
|
466
|
+
observed_speed_kn: float,
|
|
467
|
+
design_draft_m: float = 12.0,
|
|
468
|
+
observed_draft_m: float = 11.0,
|
|
469
|
+
voyage_hours: float = 240.0,
|
|
470
|
+
aux_power_kw: float = 650.0,
|
|
471
|
+
weather_fouling_margin: float = 1.12,
|
|
472
|
+
sfoc_g_kwh: float = 175.0,
|
|
473
|
+
cf_tco2_per_tfuel: float = 3.151,
|
|
474
|
+
) -> Dict[str, Any]:
|
|
475
|
+
"""
|
|
476
|
+
Bottom-Up AIS Admiralty-Cube Propulsion Power, Fuel Consumption, Emissions &
|
|
477
|
+
Slow-Steaming Elasticity Model (IMO 4th GHG Study 2020 & STEAM3 Framework).
|
|
478
|
+
|
|
479
|
+
Formula:
|
|
480
|
+
P_main = 0.85 * MCR * (Draft_obs / Draft_des)^(2/3) * (V_obs / V_des)^3 * w_margin
|
|
481
|
+
"""
|
|
482
|
+
v_des = max(float(design_speed_kn), 1.0)
|
|
483
|
+
v_obs = max(float(observed_speed_kn), 0.5)
|
|
484
|
+
d_ratio = (max(float(observed_draft_m), 1.0) / max(float(design_draft_m), 1.0)) ** (2.0 / 3.0)
|
|
485
|
+
v_ratio = (v_obs / v_des) ** 3.0
|
|
486
|
+
|
|
487
|
+
p_main_kw = 0.85 * float(mcr_kw) * d_ratio * v_ratio * float(weather_fouling_margin)
|
|
488
|
+
p_total_kw = p_main_kw + float(aux_power_kw)
|
|
489
|
+
hrs = max(float(voyage_hours), 0.1)
|
|
490
|
+
dist_nm = v_obs * hrs
|
|
491
|
+
|
|
492
|
+
fuel_tonnes = (p_total_kw * hrs * float(sfoc_g_kwh)) / 1e6
|
|
493
|
+
co2_tonnes = fuel_tonnes * float(cf_tco2_per_tfuel)
|
|
494
|
+
|
|
495
|
+
# Slow-steaming counterfactual: 10% speed reduction over same distance (time increases by 1/0.9)
|
|
496
|
+
v_slow = v_obs * 0.90
|
|
497
|
+
hrs_slow = dist_nm / v_slow
|
|
498
|
+
p_main_slow = p_main_kw * (0.90 ** 3.0)
|
|
499
|
+
fuel_slow = ((p_main_slow + float(aux_power_kw)) * hrs_slow * float(sfoc_g_kwh)) / 1e6
|
|
500
|
+
co2_saved_tonnes = max(co2_tonnes - fuel_slow * float(cf_tco2_per_tfuel), 0.0)
|
|
501
|
+
abate_pct = (co2_saved_tonnes / co2_tonnes) * 100.0 if co2_tonnes > 0 else 0.0
|
|
502
|
+
|
|
503
|
+
return {
|
|
504
|
+
"voyage_distance_nm": round(dist_nm, 2),
|
|
505
|
+
"main_engine_power_kw": round(p_main_kw, 2),
|
|
506
|
+
"total_power_kw": round(p_total_kw, 2),
|
|
507
|
+
"fuel_consumption_tonnes": round(fuel_tonnes, 3),
|
|
508
|
+
"co2_emissions_tonnes": round(co2_tonnes, 3),
|
|
509
|
+
"slow_steaming_10pct_co2_saved_tonnes": round(co2_saved_tonnes, 3),
|
|
510
|
+
"slow_steaming_10pct_abatement_pct": round(abate_pct, 2),
|
|
511
|
+
"reference": "IMO Fourth GHG Study (2020) & STEAM3 AIS Bottom-Up Model",
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
|
|
515
|
+
def marine_insurance_actuarial_pricing(
|
|
516
|
+
hull_value_usd: float = 65_000_000.0,
|
|
517
|
+
gross_tonnage: float = 42_000.0,
|
|
518
|
+
cargo_value_usd: float = 28_000_000.0,
|
|
519
|
+
vessel_age_years: float = 8.0,
|
|
520
|
+
cii_rating: str = "B",
|
|
521
|
+
observed_aer: float = 4.65,
|
|
522
|
+
required_ppmi_aer: float = 5.10,
|
|
523
|
+
psc_detentions_3yr: int = 0,
|
|
524
|
+
war_risk_chokepoint_zone: bool = False,
|
|
525
|
+
green_dual_fuel: bool = False,
|
|
526
|
+
chokepoint_transits_yr: Optional[int] = None,
|
|
527
|
+
**kwargs: Any,
|
|
528
|
+
) -> Dict[str, Any]:
|
|
529
|
+
"""
|
|
530
|
+
Actuarial Marine & Shipping Insurance Underwriting Model: Hull & Machinery (H&M),
|
|
531
|
+
International Group of P&I Clubs Mutual Call, Cargo CIF-FOB Risk, War-Risk Surcharge,
|
|
532
|
+
and Poseidon Principles for Marine Insurance (PPMI) Climate Alignment Pricing.
|
|
533
|
+
|
|
534
|
+
Post-2020 Scientific & Industry References:
|
|
535
|
+
- International Union of Marine Insurance (IUMI, 2024/2025). IUMI Global Marine
|
|
536
|
+
Insurance Report: Hull, Cargo, Offshore Energy & Protection and Indemnity.
|
|
537
|
+
- Poseidon Principles for Marine Insurance (PPMI, 2021/2024). A Global Framework
|
|
538
|
+
for Assessing and Disclosing the Climate Alignment of Marine Insurance Portfolios.
|
|
539
|
+
- International Group of P&I Clubs (IG P&I, 2024). Pooling Agreement & Reinsurance
|
|
540
|
+
General Excess of Loss (GXL) Actuarial Structure.
|
|
541
|
+
"""
|
|
542
|
+
hv = max(float(hull_value_usd), 100_000.0)
|
|
543
|
+
gt = max(float(gross_tonnage), 500.0)
|
|
544
|
+
cv = max(float(cargo_value_usd), 0.0)
|
|
545
|
+
age = max(int(vessel_age_years), 0)
|
|
546
|
+
rating = str(cii_rating).strip().upper() or "C"
|
|
547
|
+
if chokepoint_transits_yr is not None and int(chokepoint_transits_yr) > 0:
|
|
548
|
+
war_risk_chokepoint_zone = True
|
|
549
|
+
|
|
550
|
+
# 1. Base Hull & Machinery (H&M) rate (~0.42% of insured hull value, IUMI global benchmark)
|
|
551
|
+
base_hm_rate = 0.0042
|
|
552
|
+
age_mult = 1.0 + max(age - 5, 0) * 0.032 + max(age - 15, 0) * 0.045
|
|
553
|
+
cii_mult_map = {"A": 0.90, "B": 0.95, "C": 1.00, "D": 1.12, "E": 1.28}
|
|
554
|
+
cii_mult = cii_mult_map.get(rating, 1.00)
|
|
555
|
+
psc_mult = 1.0 + min(max(int(psc_detentions_3yr), 0), 5) * 0.18
|
|
556
|
+
|
|
557
|
+
# Poseidon Principles for Marine Insurance (PPMI) climate alignment delta (%)
|
|
558
|
+
req_aer = max(float(required_ppmi_aer), 0.1)
|
|
559
|
+
obs_aer = max(float(observed_aer), 0.0)
|
|
560
|
+
ppmi_delta_pct = ((obs_aer - req_aer) / req_aer) * 100.0
|
|
561
|
+
|
|
562
|
+
# Green underwriting discount / surcharge (up to -12.5% discount for PPMI-aligned dual-fuel vessels)
|
|
563
|
+
ppmi_mult = 1.0 + max(min(ppmi_delta_pct / 100.0 * 0.25, 0.20), -0.10)
|
|
564
|
+
if green_dual_fuel:
|
|
565
|
+
ppmi_mult *= 0.94
|
|
566
|
+
|
|
567
|
+
hm_premium_usd = hv * base_hm_rate * age_mult * cii_mult * psc_mult * ppmi_mult
|
|
568
|
+
|
|
569
|
+
# 2. International Group of P&I Clubs mutual call + GXL reinsurance ($3.85/GT baseline)
|
|
570
|
+
base_pi_per_gt = 3.85
|
|
571
|
+
pi_call_usd = gt * base_pi_per_gt * (0.95 if rating in ("A", "B") else (1.18 if rating == "E" else 1.0)) * psc_mult
|
|
572
|
+
|
|
573
|
+
# 3. Marine Cargo insurance (CIF-FOB merchandise protection, ~0.16% per voyage/annualized)
|
|
574
|
+
cargo_premium_usd = cv * 0.0016 * (0.94 if rating in ("A", "B") else 1.05)
|
|
575
|
+
|
|
576
|
+
# 4. War Risk / High-Risk Chokepoint (Red Sea / Bab el-Mandeb / Black Sea) Additional Premium (AP)
|
|
577
|
+
war_risk_usd = (hv * 0.0035 + cv * 0.0012) if war_risk_chokepoint_zone else 0.0
|
|
578
|
+
|
|
579
|
+
total_premium_usd = hm_premium_usd + pi_call_usd + cargo_premium_usd + war_risk_usd
|
|
580
|
+
expected_loss_ratio_pct = min(max(58.5 * cii_mult * (1.0 + 0.08 * int(psc_detentions_3yr)), 38.0), 96.0)
|
|
581
|
+
combined_ratio_pct = expected_loss_ratio_pct + 24.5 # 24.5% acquisition & administrative expense ratio
|
|
582
|
+
solvency_ii_scr_var995_usd = (hv * 0.045 + gt * 18.0) * math.sqrt(psc_mult * cii_mult)
|
|
583
|
+
ppmi_adj_pct = (ppmi_mult - 1.0) * 100.0
|
|
584
|
+
|
|
585
|
+
return {
|
|
586
|
+
"hull_machinery_premium_usd": round(hm_premium_usd, 2),
|
|
587
|
+
"pi_club_mutual_call_usd": round(pi_call_usd, 2),
|
|
588
|
+
"marine_cargo_premium_usd": round(cargo_premium_usd, 2),
|
|
589
|
+
"war_risk_chokepoint_surcharge_usd": round(war_risk_usd, 2),
|
|
590
|
+
"total_annual_marine_insurance_usd": round(total_premium_usd, 2),
|
|
591
|
+
"total_annual_premium_usd": round(total_premium_usd, 2),
|
|
592
|
+
"ppmi_climate_alignment_delta_pct": round(ppmi_delta_pct, 2),
|
|
593
|
+
"ppmi_climate_alignment_adjustment_pct": round(ppmi_adj_pct, 2),
|
|
594
|
+
"ppmi_status": "ALIGNED (Green Underwriting Tier)" if ppmi_delta_pct <= 0.0 else "MISALIGNED (Transition Risk Surcharge)",
|
|
595
|
+
"expected_loss_ratio_pct": round(expected_loss_ratio_pct, 2),
|
|
596
|
+
"combined_ratio_pct": round(combined_ratio_pct, 2),
|
|
597
|
+
"solvency_ii_var995_capital_usd": round(solvency_ii_scr_var995_usd, 2),
|
|
598
|
+
"reference": "IUMI Global Marine Insurance Report (2025); Poseidon Principles for Marine Insurance (PPMI, 2024); IG P&I (2024)",
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
|
|
602
|
+
def global_chokepoint_disruption_index(
|
|
603
|
+
baseline_transits_yr: float,
|
|
604
|
+
observed_transits_yr: float,
|
|
605
|
+
detour_distance_nm: float = 3500.0,
|
|
606
|
+
vessel_speed_knots: float = 14.5,
|
|
607
|
+
daily_fuel_tonnes: float = 42.0,
|
|
608
|
+
fuel_price_usd_t: float = 640.0,
|
|
609
|
+
eua_carbon_price_usd_t: float = 75.0,
|
|
610
|
+
) -> Dict[str, Any]:
|
|
611
|
+
"""
|
|
612
|
+
Compute the Global Maritime Chokepoint Supply-Chain Disruption & Rerouting Cost
|
|
613
|
+
Index (Verschuur et al., Nature 2023; IMF PortWatch 2024; UNCTAD 2024) for major
|
|
614
|
+
chokepoints (Suez, Bab el-Mandeb, Panama, Malacca, Hormuz, Bosporus, Dardanelles).
|
|
615
|
+
"""
|
|
616
|
+
base_n = max(float(baseline_transits_yr), 1.0)
|
|
617
|
+
obs_n = max(float(observed_transits_yr), 0.0)
|
|
618
|
+
transit_drop_pct = max(0.0, ((base_n - obs_n) / base_n) * 100.0)
|
|
619
|
+
rerouted_vessels = max(0.0, base_n - obs_n)
|
|
620
|
+
extra_days_per_voyage = float(detour_distance_nm) / (max(float(vessel_speed_knots), 1.0) * 24.0)
|
|
621
|
+
extra_fuel_t_per_voyage = extra_days_per_voyage * float(daily_fuel_tonnes)
|
|
622
|
+
extra_co2_t_per_voyage = extra_fuel_t_per_voyage * 3.151
|
|
623
|
+
extra_cost_per_voyage_usd = (
|
|
624
|
+
extra_fuel_t_per_voyage * float(fuel_price_usd_t)
|
|
625
|
+
+ extra_co2_t_per_voyage * float(eua_carbon_price_usd_t)
|
|
626
|
+
)
|
|
627
|
+
disruption_score = min(100.0, round(transit_drop_pct * 1.35 + extra_days_per_voyage * 2.2, 2))
|
|
628
|
+
return {
|
|
629
|
+
"transit_drop_pct": round(transit_drop_pct, 2),
|
|
630
|
+
"rerouted_vessels_count": round(rerouted_vessels, 1),
|
|
631
|
+
"extra_sea_days_per_voyage": round(extra_days_per_voyage, 2),
|
|
632
|
+
"extra_fuel_tonnes_per_voyage": round(extra_fuel_t_per_voyage, 2),
|
|
633
|
+
"extra_co2_tonnes_per_voyage": round(extra_co2_t_per_voyage, 2),
|
|
634
|
+
"extra_voyage_cost_usd": round(extra_cost_per_voyage_usd, 2),
|
|
635
|
+
"aggregate_rerouting_co2_mt": round((rerouted_vessels * extra_co2_t_per_voyage) / 1e6, 4),
|
|
636
|
+
"disruption_severity_index_0_100": disruption_score,
|
|
637
|
+
"reference": "Verschuur et al. (2023, Nature); IMF PortWatch (2024); UNCTAD Review of Maritime Transport (2024)",
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
|
|
641
|
+
def kunming_montreal_30x30_gap(
|
|
642
|
+
current_mpa_pct: float,
|
|
643
|
+
eez_area_km2: float,
|
|
644
|
+
current_year: int = 2025,
|
|
645
|
+
target_year: int = 2030,
|
|
646
|
+
target_pct: float = 30.0,
|
|
647
|
+
) -> Dict[str, Any]:
|
|
648
|
+
"""
|
|
649
|
+
Evaluate national or global ocean basin progress toward the CBD Kunming-Montreal
|
|
650
|
+
Global Biodiversity Framework (GBF) Target 3 ('30x30' by 2030) and UN BBNJ High-Seas
|
|
651
|
+
Treaty (Sala et al., Nature 2021; UNEP-WCMC Protected Planet Report 2024).
|
|
652
|
+
"""
|
|
653
|
+
cur_pct = max(0.0, min(100.0, float(current_mpa_pct)))
|
|
654
|
+
area = max(0.0, float(eez_area_km2))
|
|
655
|
+
tgt = max(0.1, min(100.0, float(target_pct)))
|
|
656
|
+
yrs_left = max(1, int(target_year) - int(current_year))
|
|
657
|
+
protected_km2 = area * (cur_pct / 100.0)
|
|
658
|
+
target_km2 = area * (tgt / 100.0)
|
|
659
|
+
gap_pct = max(0.0, tgt - cur_pct)
|
|
660
|
+
gap_km2 = max(0.0, target_km2 - protected_km2)
|
|
661
|
+
req_cagr_pct = (((tgt / max(cur_pct, 0.05)) ** (1.0 / yrs_left)) - 1.0) * 100.0 if cur_pct < tgt else 0.0
|
|
662
|
+
return {
|
|
663
|
+
"current_mpa_pct": round(cur_pct, 2),
|
|
664
|
+
"target_30x30_pct": round(tgt, 2),
|
|
665
|
+
"mpa_gap_percentage_points": round(gap_pct, 2),
|
|
666
|
+
"protected_marine_area_km2": round(protected_km2, 1),
|
|
667
|
+
"additional_area_needed_km2": round(gap_km2, 1),
|
|
668
|
+
"required_annual_expansion_km2_yr": round(gap_km2 / yrs_left, 1),
|
|
669
|
+
"required_cagr_pct": round(req_cagr_pct, 2),
|
|
670
|
+
"status": "ACHIEVED (>=30% Protected)" if cur_pct >= tgt else "EXPANSION_REQUIRED",
|
|
671
|
+
"reference": "CBD Kunming-Montreal GBF Target 3 (2022); Sala et al. (2021, Nature 592); UNEP-WCMC WDPA (2024)",
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
|
|
675
|
+
def arctic_route_navigability_index(
|
|
676
|
+
corridor: str = "CORR_NSR",
|
|
677
|
+
september_ice_extent_mkm2: float = 0.62,
|
|
678
|
+
baseline_2005_ice_extent_mkm2: float = 1.45,
|
|
679
|
+
polar_distance_nm: float = 7200.0,
|
|
680
|
+
suez_distance_nm: float = 11200.0,
|
|
681
|
+
vessel_ice_class: str = "PC6",
|
|
682
|
+
daily_fuel_tonnes: float = 45.0,
|
|
683
|
+
speed_suez_knots: float = 15.0,
|
|
684
|
+
speed_polar_knots: float = 12.5,
|
|
685
|
+
) -> Dict[str, Any]:
|
|
686
|
+
"""
|
|
687
|
+
Compute the Arctic Polar Maritime Corridor Navigability, Distance/Fuel Saving &
|
|
688
|
+
IMO Polar Code Risk Index (Melia et al., Geophysical Research Letters 2016;
|
|
689
|
+
Stephenson et al., Nature Climate Change; Arctic Council PAME ASTD 2024; NSIDC).
|
|
690
|
+
"""
|
|
691
|
+
ice_cur = max(0.01, float(september_ice_extent_mkm2))
|
|
692
|
+
ice_base = max(0.1, float(baseline_2005_ice_extent_mkm2))
|
|
693
|
+
d_pol = max(100.0, float(polar_distance_nm))
|
|
694
|
+
d_suez = max(100.0, float(suez_distance_nm))
|
|
695
|
+
v_suez = max(5.0, float(speed_suez_knots))
|
|
696
|
+
v_pol = max(4.0, float(speed_polar_knots))
|
|
697
|
+
f_day = max(1.0, float(daily_fuel_tonnes))
|
|
698
|
+
|
|
699
|
+
ice_retreat_fraction = max(0.0, min(1.0, 1.0 - (ice_cur / ice_base)))
|
|
700
|
+
distance_saving_pct = max(0.0, ((d_suez - d_pol) / d_suez) * 100.0)
|
|
701
|
+
days_suez = d_suez / (v_suez * 24.0)
|
|
702
|
+
days_polar = d_pol / (v_pol * 24.0)
|
|
703
|
+
days_saved = days_suez - days_polar
|
|
704
|
+
# Ice-resistance fuel penalty factor (+12% per day in polar waters)
|
|
705
|
+
fuel_suez_t = days_suez * f_day
|
|
706
|
+
fuel_polar_t = days_polar * f_day * 1.12
|
|
707
|
+
co2_saved_t = (fuel_suez_t - fuel_polar_t) * 3.151
|
|
708
|
+
|
|
709
|
+
ic = vessel_ice_class.strip().upper()
|
|
710
|
+
class_bonus = 1.0 if ic in ("PC1", "PC2", "PC3", "PC4", "PC5", "ARC7") else (0.85 if ic in ("PC6", "PC7", "ARC4", "1A") else 0.55)
|
|
711
|
+
navigability_score = min(100.0, (0.55 * ice_retreat_fraction * 100.0 + 0.45 * distance_saving_pct) * class_bonus)
|
|
712
|
+
|
|
713
|
+
return {
|
|
714
|
+
"corridor": str(corridor).upper(),
|
|
715
|
+
"vessel_ice_class": ic,
|
|
716
|
+
"september_ice_extent_mkm2": round(ice_cur, 3),
|
|
717
|
+
"ice_retreat_fraction_vs_2005": round(ice_retreat_fraction, 4),
|
|
718
|
+
"distance_saving_pct_vs_suez": round(distance_saving_pct, 2),
|
|
719
|
+
"transit_days_suez": round(days_suez, 2),
|
|
720
|
+
"transit_days_polar": round(days_polar, 2),
|
|
721
|
+
"sea_days_saved": round(days_saved, 2),
|
|
722
|
+
"net_co2_saved_tonnes": round(co2_saved_t, 2),
|
|
723
|
+
"polar_navigability_index_0_100": round(navigability_score, 2),
|
|
724
|
+
"reference": "Arctic Council PAME ASTD (2024); NSIDC Sea Ice Index v3; IMO Polar Code (MEPC 68/21)",
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
|
|
728
|
+
def seaborne_trade_climate_coupling_index(
|
|
729
|
+
merchandise_trade_gdp_pct: float = 54.0,
|
|
730
|
+
export_volume_index_2015_100: float = 132.0,
|
|
731
|
+
transport_service_share_pct: float = 24.5,
|
|
732
|
+
hub_wind_max_kmh: float = 52.0,
|
|
733
|
+
renewable_electricity_share_pct: float = 42.0,
|
|
734
|
+
) -> Dict[str, Any]:
|
|
735
|
+
"""
|
|
736
|
+
Compute the Global Seaborne Merchandise Trade Openness, ERA5 Port/Chokepoint
|
|
737
|
+
Climate Disruption & Green Transition Coupling Index (Verschuur et al., Nature
|
|
738
|
+
Communications 2023; Hersbach et al., Q. J. R. Meteorol. Soc. 2020; UNCTAD 2024;
|
|
739
|
+
WTO World Trade Report 2024).
|
|
740
|
+
"""
|
|
741
|
+
trd_gdp = max(0.0, float(merchandise_trade_gdp_pct))
|
|
742
|
+
exp_vol = max(1.0, float(export_volume_index_2015_100))
|
|
743
|
+
trn_shr = max(0.0, min(100.0, float(transport_service_share_pct)))
|
|
744
|
+
wind_max = max(0.0, float(hub_wind_max_kmh))
|
|
745
|
+
ren_elc = max(0.0, min(100.0, float(renewable_electricity_share_pct)))
|
|
746
|
+
|
|
747
|
+
openness_score = min(100.0, (trd_gdp / 120.0) * 50.0 + (exp_vol / 200.0) * 30.0 + (trn_shr / 45.0) * 20.0)
|
|
748
|
+
# Beaufort >= 8 gale threshold (~62 km/h) increases port downtime & chokepoint queue risk
|
|
749
|
+
weather_disruption_pct = min(100.0, max(0.0, ((wind_max - 35.0) / 55.0) * 100.0))
|
|
750
|
+
green_port_readiness = min(100.0, ren_elc * 0.85 + 15.0)
|
|
751
|
+
net_resilience_index = max(0.0, min(100.0, 0.45 * openness_score + 0.35 * green_port_readiness - 0.20 * weather_disruption_pct))
|
|
752
|
+
|
|
753
|
+
return {
|
|
754
|
+
"merchandise_trade_gdp_pct": round(trd_gdp, 2),
|
|
755
|
+
"export_volume_index_2015_100": round(exp_vol, 2),
|
|
756
|
+
"transport_service_share_pct": round(trn_shr, 2),
|
|
757
|
+
"era5_hub_wind_max_kmh": round(wind_max, 2),
|
|
758
|
+
"renewable_electricity_share_pct": round(ren_elc, 2),
|
|
759
|
+
"seaborne_openness_score_0_100": round(openness_score, 2),
|
|
760
|
+
"era5_port_weather_disruption_risk_0_100": round(weather_disruption_pct, 2),
|
|
761
|
+
"green_shore_power_readiness_0_100": round(green_port_readiness, 2),
|
|
762
|
+
"trade_climate_resilience_index_0_100": round(net_resilience_index, 2),
|
|
763
|
+
"reference": "Verschuur et al. (2023, Nat. Commun. 14:1341); Hersbach et al. (2020, ERA5); UNCTAD (2024)",
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
|
|
767
|
+
def fueleu_cii_ets_compliance_cost(
|
|
768
|
+
annual_fuel_tonnes: float = 11500.0,
|
|
769
|
+
wtw_ghg_intensity_gco2e_mj: float = 89.34,
|
|
770
|
+
target_wtw_gco2e_mj: float = 89.34,
|
|
771
|
+
eu_voyage_share_pct: float = 65.0,
|
|
772
|
+
eua_carbon_price_eur_t: float = 78.5,
|
|
773
|
+
attained_aer_gco2_dwt_nm: float = 4.65,
|
|
774
|
+
required_aer_gco2_dwt_nm: float = 4.50,
|
|
775
|
+
) -> Dict[str, Any]:
|
|
776
|
+
"""
|
|
777
|
+
Compute integrated Maritime Regulatory Carbon Exposure combining Regulation (EU) 2023/1805
|
|
778
|
+
(FuelEU Maritime WtW GHG Intensity Deficit Penalty at 2,400 EUR/t VLSFOe = 58.5 EUR/GJ),
|
|
779
|
+
Directive (EU) 2023/959 (100% EU ETS Maritime Allowance Liability from 2026), and
|
|
780
|
+
IMO MARPOL Annex VI CII (AER Rating A–E) across the 15 global fleet segments.
|
|
781
|
+
"""
|
|
782
|
+
fuel_t = max(1.0, float(annual_fuel_tonnes))
|
|
783
|
+
wtw_act = max(0.0, float(wtw_ghg_intensity_gco2e_mj))
|
|
784
|
+
wtw_tgt = max(1.0, float(target_wtw_gco2e_mj))
|
|
785
|
+
eu_share = max(0.0, min(100.0, float(eu_voyage_share_pct))) / 100.0
|
|
786
|
+
eua_eur = max(0.0, float(eua_carbon_price_eur_t))
|
|
787
|
+
aer_act = max(0.1, float(attained_aer_gco2_dwt_nm))
|
|
788
|
+
aer_req = max(0.1, float(required_aer_gco2_dwt_nm))
|
|
789
|
+
|
|
790
|
+
# Energy in MJ (assuming 41,000 MJ/t VLSFO equivalent)
|
|
791
|
+
total_energy_mj = fuel_t * 41000.0 * eu_share
|
|
792
|
+
ghg_balance_tco2e = ((wtw_tgt - wtw_act) * total_energy_mj) / 1e6
|
|
793
|
+
if ghg_balance_tco2e < 0.0 and wtw_act > 0.0:
|
|
794
|
+
fueleu_penalty_eur = (abs(ghg_balance_tco2e) * 1e6 / (wtw_act * 41000.0)) * 2400.0
|
|
795
|
+
else:
|
|
796
|
+
fueleu_penalty_eur = 0.0
|
|
797
|
+
|
|
798
|
+
# EU ETS CO2e liability (3.114 tCO2/t fuel * eu_share)
|
|
799
|
+
ets_verified_tco2e = fuel_t * 3.114 * eu_share
|
|
800
|
+
ets_cost_eur = ets_verified_tco2e * eua_eur
|
|
801
|
+
|
|
802
|
+
aer_ratio = aer_act / aer_req
|
|
803
|
+
if aer_ratio <= 0.86:
|
|
804
|
+
cii_grade = "A"
|
|
805
|
+
elif aer_ratio <= 0.94:
|
|
806
|
+
cii_grade = "B"
|
|
807
|
+
elif aer_ratio <= 1.06:
|
|
808
|
+
cii_grade = "C"
|
|
809
|
+
elif aer_ratio <= 1.18:
|
|
810
|
+
cii_grade = "D"
|
|
811
|
+
else:
|
|
812
|
+
cii_grade = "E"
|
|
813
|
+
|
|
814
|
+
return {
|
|
815
|
+
"annual_fuel_tonnes": round(fuel_t, 2),
|
|
816
|
+
"eu_scope_energy_gj": round(total_energy_mj / 1000.0, 2),
|
|
817
|
+
"fueleu_ghg_balance_tco2e": round(ghg_balance_tco2e, 2),
|
|
818
|
+
"fueleu_penalty_eur": round(fueleu_penalty_eur, 2),
|
|
819
|
+
"eu_ets_verified_tco2e": round(ets_verified_tco2e, 2),
|
|
820
|
+
"eu_ets_allowance_cost_eur": round(ets_cost_eur, 2),
|
|
821
|
+
"total_regulatory_carbon_cost_eur": round(fueleu_penalty_eur + ets_cost_eur, 2),
|
|
822
|
+
"imo_cii_aer_ratio": round(aer_ratio, 4),
|
|
823
|
+
"imo_cii_rating": cii_grade,
|
|
824
|
+
"reference": "Regulation (EU) 2023/1805 (FuelEU Maritime); Directive (EU) 2023/959 (EU ETS); IMO MEPC.377(80)",
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
|
|
828
|
+
def subsea_corridor_vulnerability_index(
|
|
829
|
+
daily_vessel_transits: float = 58.0,
|
|
830
|
+
rerouting_detour_nm: float = 3450.0,
|
|
831
|
+
subsea_cable_count: int = 16,
|
|
832
|
+
cable_fault_rate_per_yr: float = 2.4,
|
|
833
|
+
geopolitical_hazard_score_0_100: float = 68.0,
|
|
834
|
+
) -> Dict[str, Any]:
|
|
835
|
+
"""
|
|
836
|
+
Evaluate Global Maritime Chokepoint, Trade Corridor & Subsea Telecommunication/HVDC
|
|
837
|
+
Cable Infrastructure Vulnerability Index (0–100) following Verschuur et al. (Nature
|
|
838
|
+
Communications 2023), IMF PortWatch (2024–2026), Carter et al. (UNEP-WCMC/ICPC 2009),
|
|
839
|
+
and UN BBNJ High-Seas Governance metrics.
|
|
840
|
+
"""
|
|
841
|
+
transits = max(0.0, float(daily_vessel_transits))
|
|
842
|
+
detour = max(0.0, float(rerouting_detour_nm))
|
|
843
|
+
cables = max(0, int(subsea_cable_count))
|
|
844
|
+
faults = max(0.0, float(cable_fault_rate_per_yr))
|
|
845
|
+
geo_haz = max(0.0, min(100.0, float(geopolitical_hazard_score_0_100)))
|
|
846
|
+
|
|
847
|
+
# Additional transit days at 14.5 knots service speed
|
|
848
|
+
added_days = detour / (14.5 * 24.0)
|
|
849
|
+
freight_surcharge_multiplier = 1.0 + 0.085 * added_days + 0.004 * geo_haz
|
|
850
|
+
|
|
851
|
+
s_detour = min(100.0, (detour / 4500.0) * 100.0)
|
|
852
|
+
s_cable = min(100.0, (cables / 22.0) * 55.0 + (faults / 5.0) * 45.0)
|
|
853
|
+
composite_vuln = 0.40 * geo_haz + 0.35 * s_detour + 0.25 * s_cable
|
|
854
|
+
|
|
855
|
+
return {
|
|
856
|
+
"daily_vessel_transits": round(transits, 1),
|
|
857
|
+
"rerouting_detour_nm": round(detour, 1),
|
|
858
|
+
"estimated_detour_days_at_14_5_kn": round(added_days, 2),
|
|
859
|
+
"subsea_cable_corridor_count": cables,
|
|
860
|
+
"annual_anchor_trawl_fault_rate": round(faults, 2),
|
|
861
|
+
"freight_war_risk_multiplier": round(freight_surcharge_multiplier, 3),
|
|
862
|
+
"corridor_infrastructure_vulnerability_0_100": round(composite_vuln, 2),
|
|
863
|
+
"reference": "Verschuur et al. (2023, Nat. Commun.); IMF PortWatch (2026); ICPC / TeleGeography (2026)",
|
|
864
|
+
}
|
|
865
|
+
|
|
866
|
+
|
|
867
|
+
def rfmo_stock_iuu_sustainability(
|
|
868
|
+
ssb_bmsy_ratio: float = 1.04,
|
|
869
|
+
f_fmsy_ratio: float = 0.96,
|
|
870
|
+
iuu_risk_index_0_100: float = 34.0,
|
|
871
|
+
bycatch_compliance_pct: float = 68.0,
|
|
872
|
+
annual_tac_kt: float = 825.0,
|
|
873
|
+
) -> Dict[str, Any]:
|
|
874
|
+
"""
|
|
875
|
+
Evaluate Regional Fisheries Management Organization (RFMO) Kobe Plot Quadrant Status
|
|
876
|
+
(SSB/BMSY vs. F/FMSY), Illegal, Unreported and Unregulated (IUU) Depletion Risk, and
|
|
877
|
+
Sustainable Harvest Governance Index across all 18 Global RFMOs (Hilborn et al., PNAS 2020;
|
|
878
|
+
FAO SOFIA 2024; Agnew et al., PLOS ONE 2009).
|
|
879
|
+
"""
|
|
880
|
+
b_ratio = max(0.05, float(ssb_bmsy_ratio))
|
|
881
|
+
f_ratio = max(0.05, float(f_fmsy_ratio))
|
|
882
|
+
iuu = max(0.0, min(100.0, float(iuu_risk_index_0_100)))
|
|
883
|
+
bycatch = max(0.0, min(100.0, float(bycatch_compliance_pct)))
|
|
884
|
+
tac = max(0.0, float(annual_tac_kt))
|
|
885
|
+
|
|
886
|
+
if b_ratio >= 1.0 and f_ratio <= 1.0:
|
|
887
|
+
kobe_quadrant = "GREEN (Sustainable: Not Overfished, No Overfishing)"
|
|
888
|
+
elif b_ratio < 1.0 and f_ratio > 1.0:
|
|
889
|
+
kobe_quadrant = "RED (Depleted & Overfishing Active)"
|
|
890
|
+
elif b_ratio < 1.0 and f_ratio <= 1.0:
|
|
891
|
+
kobe_quadrant = "YELLOW_REBUILDING (Overfished, Rebuilding Under Low F)"
|
|
892
|
+
else:
|
|
893
|
+
kobe_quadrant = "ORANGE_WARNING (Biomass Above BMSY but Overfishing Occurring)"
|
|
894
|
+
|
|
895
|
+
bio_health = min(100.0, max(0.0, (b_ratio / 1.35) * 55.0 + max(0.0, (2.0 - f_ratio) / 1.5) * 45.0))
|
|
896
|
+
governance_score = 0.45 * bio_health + 0.30 * (100.0 - iuu) + 0.25 * bycatch
|
|
897
|
+
|
|
898
|
+
return {
|
|
899
|
+
"ssb_bmsy_ratio": round(b_ratio, 3),
|
|
900
|
+
"f_fmsy_ratio": round(f_ratio, 3),
|
|
901
|
+
"kobe_plot_quadrant": kobe_quadrant,
|
|
902
|
+
"iuu_risk_index_0_100": round(iuu, 2),
|
|
903
|
+
"bycatch_compliance_pct": round(bycatch, 2),
|
|
904
|
+
"annual_tac_kt": round(tac, 1),
|
|
905
|
+
"rfmo_sustainability_index_0_100": round(governance_score, 2),
|
|
906
|
+
"reference": "Hilborn et al. (2020, PNAS); FAO SOFIA (2024); Cullis-Suzuki & Pauly (2010, Mar. Policy)",
|
|
907
|
+
}
|
|
908
|
+
|
|
909
|
+
|
|
910
|
+
def desalination_brine_green_h2_nexus(
|
|
911
|
+
desal_capacity_m3_day: float = 850000.0,
|
|
912
|
+
specific_energy_kwh_m3: float = 3.25,
|
|
913
|
+
brine_salinity_anomaly_psu: float = 2.4,
|
|
914
|
+
green_h2_nh3_output_kt_yr: float = 210.0,
|
|
915
|
+
marine_renewable_mw: float = 380.0,
|
|
916
|
+
) -> Dict[str, Any]:
|
|
917
|
+
"""
|
|
918
|
+
Model the Coastal Seawater Desalination (SWRO), Hypersaline Brine Plume Dilution Demand,
|
|
919
|
+
and Green Maritime Hydrogen / e-Ammonia Electrolysis Nexus across 25 Global Hubs
|
|
920
|
+
(Jones et al., Sci. Total Environ. 2019; IRENA / IEA Green Hydrogen & Maritime e-Fuels 2024).
|
|
921
|
+
"""
|
|
922
|
+
cap_m3d = max(100.0, float(desal_capacity_m3_day))
|
|
923
|
+
sec = max(1.8, float(specific_energy_kwh_m3))
|
|
924
|
+
psu_anom = max(0.1, float(brine_salinity_anomaly_psu))
|
|
925
|
+
h2_kt = max(0.0, float(green_h2_nh3_output_kt_yr))
|
|
926
|
+
ren_mw = max(0.0, float(marine_renewable_mw))
|
|
927
|
+
|
|
928
|
+
# Brine discharge volume assuming 45% SWRO recovery -> 1.22 m3 brine per m3 permeate
|
|
929
|
+
brine_discharge_m3_day = cap_m3d * 1.22
|
|
930
|
+
annual_power_gwh = (cap_m3d * sec * 365.0) / 1e6
|
|
931
|
+
# Stoichiometric water for electrolysis (~9 m3 ultrapure water per tonne H2) as % of annual desal output
|
|
932
|
+
electrolysis_water_share_pct = min(100.0, ((h2_kt * 1000.0 * 9.0) / (cap_m3d * 365.0)) * 100.0)
|
|
933
|
+
nexus_efficiency_score = min(100.0, max(0.0, (4.8 - sec) / 3.0 * 40.0 + max(0.0, (4.5 - psu_anom) / 4.5) * 30.0 + min(30.0, (ren_mw / 500.0) * 30.0)))
|
|
934
|
+
|
|
935
|
+
return {
|
|
936
|
+
"desal_capacity_m3_day": round(cap_m3d, 1),
|
|
937
|
+
"estimated_brine_discharge_m3_day": round(brine_discharge_m3_day, 1),
|
|
938
|
+
"specific_energy_kwh_m3": round(sec, 3),
|
|
939
|
+
"annual_desal_electricity_demand_gwh": round(annual_power_gwh, 2),
|
|
940
|
+
"brine_salinity_anomaly_psu": round(psu_anom, 2),
|
|
941
|
+
"green_h2_nh3_output_kt_yr": round(h2_kt, 2),
|
|
942
|
+
"electrolysis_feedwater_share_pct": round(electrolysis_water_share_pct, 3),
|
|
943
|
+
"coastal_water_energy_nexus_score_0_100": round(nexus_efficiency_score, 2),
|
|
944
|
+
"reference": "Jones et al. (2019, Sci. Total Environ.); IRENA & IMO Green Shipping Corridors (2024)",
|
|
945
|
+
}
|
|
946
|
+
|
|
947
|
+
|
|
948
|
+
|
|
949
|
+
|
|
950
|
+
|
|
951
|
+
|
|
952
|
+
|