maritime-choke 0.1.0__tar.gz
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.
- maritime_choke-0.1.0/LICENSE +25 -0
- maritime_choke-0.1.0/PKG-INFO +95 -0
- maritime_choke-0.1.0/README.md +63 -0
- maritime_choke-0.1.0/pyproject.toml +52 -0
- maritime_choke-0.1.0/setup.cfg +4 -0
- maritime_choke-0.1.0/src/maritime_choke/__init__.py +21 -0
- maritime_choke-0.1.0/src/maritime_choke/app.py +187 -0
- maritime_choke-0.1.0/src/maritime_choke/chokepoint_names.py +43 -0
- maritime_choke-0.1.0/src/maritime_choke/cli.py +101 -0
- maritime_choke-0.1.0/src/maritime_choke/data.py +174 -0
- maritime_choke-0.1.0/src/maritime_choke/io_utils.py +46 -0
- maritime_choke-0.1.0/src/maritime_choke/model.py +725 -0
- maritime_choke-0.1.0/src/maritime_choke/model_ras.py +217 -0
- maritime_choke-0.1.0/src/maritime_choke/solver_backend.py +484 -0
- maritime_choke-0.1.0/src/maritime_choke/static/loss_anchors.json +637 -0
- maritime_choke-0.1.0/src/maritime_choke/static/world_iso.json +1 -0
- maritime_choke-0.1.0/src/maritime_choke/templates/index.html +671 -0
- maritime_choke-0.1.0/src/maritime_choke.egg-info/PKG-INFO +95 -0
- maritime_choke-0.1.0/src/maritime_choke.egg-info/SOURCES.txt +21 -0
- maritime_choke-0.1.0/src/maritime_choke.egg-info/dependency_links.txt +1 -0
- maritime_choke-0.1.0/src/maritime_choke.egg-info/entry_points.txt +2 -0
- maritime_choke-0.1.0/src/maritime_choke.egg-info/requires.txt +12 -0
- maritime_choke-0.1.0/src/maritime_choke.egg-info/top_level.txt +1 -0
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Fathimath S. Vemmarath and Vipin P. Veetil
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
22
|
+
|
|
23
|
+
Note: This licence covers the source code only. The underlying economic data
|
|
24
|
+
(GTAP, OECD ICIO) are governed by their own licences and are not distributed
|
|
25
|
+
with this software.
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: maritime-choke
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Interactive model of macroeconomic risk from maritime-chokepoint trade disruptions
|
|
5
|
+
Author: Fathimath S. Vemmarath
|
|
6
|
+
Author-email: "Vipin P. Veetil" <vipin.veetil@gmail.com>
|
|
7
|
+
License: MIT
|
|
8
|
+
Project-URL: Homepage, https://bitbucket.org/VipinVeetil/chokepoints
|
|
9
|
+
Project-URL: Source, https://bitbucket.org/VipinVeetil/chokepoints
|
|
10
|
+
Keywords: production networks,input-output,maritime,chokepoints,trade disruption,systemic risk,economics,supply chains
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
13
|
+
Classifier: Operating System :: OS Independent
|
|
14
|
+
Classifier: Intended Audience :: Science/Research
|
|
15
|
+
Classifier: Topic :: Scientific/Engineering
|
|
16
|
+
Classifier: Framework :: Flask
|
|
17
|
+
Requires-Python: >=3.9
|
|
18
|
+
Description-Content-Type: text/markdown
|
|
19
|
+
License-File: LICENSE
|
|
20
|
+
Requires-Dist: numpy>=1.23
|
|
21
|
+
Requires-Dist: scipy>=1.10
|
|
22
|
+
Requires-Dist: matplotlib>=3.6
|
|
23
|
+
Requires-Dist: flask>=2.2
|
|
24
|
+
Requires-Dist: platformdirs>=2.6
|
|
25
|
+
Requires-Dist: certifi>=2022.0.0
|
|
26
|
+
Provides-Extra: dev
|
|
27
|
+
Requires-Dist: pyshp>=2.3; extra == "dev"
|
|
28
|
+
Requires-Dist: pandas>=1.5; extra == "dev"
|
|
29
|
+
Requires-Dist: build; extra == "dev"
|
|
30
|
+
Requires-Dist: twine; extra == "dev"
|
|
31
|
+
Dynamic: license-file
|
|
32
|
+
|
|
33
|
+
# maritime-choke
|
|
34
|
+
|
|
35
|
+
An interactive model of the macroeconomic risk from **maritime-chokepoint trade
|
|
36
|
+
disruptions**. Close any set of the world's 12 major shipping chokepoints, pick a
|
|
37
|
+
`(τ, ρ, δ)` calibration, solve a short-run production-network model forward, and
|
|
38
|
+
explore where the value-added loss falls — on an interactive world heat map, by
|
|
39
|
+
country, and by sector — all in a local browser GUI.
|
|
40
|
+
|
|
41
|
+
Based on *Macroeconomic Risks from Maritime Trade Disruptions* (Vemmarath &
|
|
42
|
+
Veetil). Source & data: https://bitbucket.org/VipinVeetil/chokepoints
|
|
43
|
+
|
|
44
|
+
## Install
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
pip install maritime-choke
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Get the data (one-time)
|
|
51
|
+
|
|
52
|
+
The solver needs a model cache (~180 MB) that isn't shipped in the wheel:
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
maritime-choke fetch-data # downloads and installs it
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
(You can also point at a local copy: `maritime-choke fetch-data --from-local /path/to/model_cache_2019`.)
|
|
59
|
+
|
|
60
|
+
## Run
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
maritime-choke run # opens the GUI at http://127.0.0.1:5057
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Pick chokepoints, set the calibration, hit **Run scenario**. Results are written
|
|
67
|
+
to a per-user folder (see `maritime-choke where`) and shown in the page:
|
|
68
|
+
world value-added loss, a relatable "scale of the loss" comparison, the world
|
|
69
|
+
heat map (recolour by any of 50 sectors), and per-country / per-sector drill-downs.
|
|
70
|
+
|
|
71
|
+
## Commands
|
|
72
|
+
|
|
73
|
+
| command | what it does |
|
|
74
|
+
|---|---|
|
|
75
|
+
| `maritime-choke run` | launch the GUI (`--port`, `--no-browser`) |
|
|
76
|
+
| `maritime-choke fetch-data` | download/install the model cache (`--url`, `--from-local`, `--force`) |
|
|
77
|
+
| `maritime-choke pack-data <dir>` | pack a local cache into a `.tar.xz` for hosting |
|
|
78
|
+
| `maritime-choke where` | show data / cache / run-output locations |
|
|
79
|
+
| `maritime-choke build-data` | how to rebuild the cache from GTAP + OECD ICIO |
|
|
80
|
+
|
|
81
|
+
Data locations can be overridden with `MARITIME_CHOKE_DATA` (cache root) and
|
|
82
|
+
`MARITIME_CHOKE_RUNS` (run outputs). The download URL can be set with
|
|
83
|
+
`MARITIME_CHOKE_DATA_URL`.
|
|
84
|
+
|
|
85
|
+
## Data & licensing
|
|
86
|
+
|
|
87
|
+
The model cache is derived from OECD ICIO (freely downloadable) and the licensed
|
|
88
|
+
**GTAP 10** data base. The raw GTAP data is *not* redistributed; if you need to
|
|
89
|
+
rebuild the cache from scratch you must supply your own GTAP licence — see
|
|
90
|
+
`maritime-choke build-data`. The shipped map/comparison assets are built from
|
|
91
|
+
public Natural Earth and World Bank data.
|
|
92
|
+
|
|
93
|
+
## License
|
|
94
|
+
|
|
95
|
+
MIT (code). Underlying GTAP and OECD ICIO data are governed by their own terms.
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# maritime-choke
|
|
2
|
+
|
|
3
|
+
An interactive model of the macroeconomic risk from **maritime-chokepoint trade
|
|
4
|
+
disruptions**. Close any set of the world's 12 major shipping chokepoints, pick a
|
|
5
|
+
`(τ, ρ, δ)` calibration, solve a short-run production-network model forward, and
|
|
6
|
+
explore where the value-added loss falls — on an interactive world heat map, by
|
|
7
|
+
country, and by sector — all in a local browser GUI.
|
|
8
|
+
|
|
9
|
+
Based on *Macroeconomic Risks from Maritime Trade Disruptions* (Vemmarath &
|
|
10
|
+
Veetil). Source & data: https://bitbucket.org/VipinVeetil/chokepoints
|
|
11
|
+
|
|
12
|
+
## Install
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
pip install maritime-choke
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Get the data (one-time)
|
|
19
|
+
|
|
20
|
+
The solver needs a model cache (~180 MB) that isn't shipped in the wheel:
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
maritime-choke fetch-data # downloads and installs it
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
(You can also point at a local copy: `maritime-choke fetch-data --from-local /path/to/model_cache_2019`.)
|
|
27
|
+
|
|
28
|
+
## Run
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
maritime-choke run # opens the GUI at http://127.0.0.1:5057
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Pick chokepoints, set the calibration, hit **Run scenario**. Results are written
|
|
35
|
+
to a per-user folder (see `maritime-choke where`) and shown in the page:
|
|
36
|
+
world value-added loss, a relatable "scale of the loss" comparison, the world
|
|
37
|
+
heat map (recolour by any of 50 sectors), and per-country / per-sector drill-downs.
|
|
38
|
+
|
|
39
|
+
## Commands
|
|
40
|
+
|
|
41
|
+
| command | what it does |
|
|
42
|
+
|---|---|
|
|
43
|
+
| `maritime-choke run` | launch the GUI (`--port`, `--no-browser`) |
|
|
44
|
+
| `maritime-choke fetch-data` | download/install the model cache (`--url`, `--from-local`, `--force`) |
|
|
45
|
+
| `maritime-choke pack-data <dir>` | pack a local cache into a `.tar.xz` for hosting |
|
|
46
|
+
| `maritime-choke where` | show data / cache / run-output locations |
|
|
47
|
+
| `maritime-choke build-data` | how to rebuild the cache from GTAP + OECD ICIO |
|
|
48
|
+
|
|
49
|
+
Data locations can be overridden with `MARITIME_CHOKE_DATA` (cache root) and
|
|
50
|
+
`MARITIME_CHOKE_RUNS` (run outputs). The download URL can be set with
|
|
51
|
+
`MARITIME_CHOKE_DATA_URL`.
|
|
52
|
+
|
|
53
|
+
## Data & licensing
|
|
54
|
+
|
|
55
|
+
The model cache is derived from OECD ICIO (freely downloadable) and the licensed
|
|
56
|
+
**GTAP 10** data base. The raw GTAP data is *not* redistributed; if you need to
|
|
57
|
+
rebuild the cache from scratch you must supply your own GTAP licence — see
|
|
58
|
+
`maritime-choke build-data`. The shipped map/comparison assets are built from
|
|
59
|
+
public Natural Earth and World Bank data.
|
|
60
|
+
|
|
61
|
+
## License
|
|
62
|
+
|
|
63
|
+
MIT (code). Underlying GTAP and OECD ICIO data are governed by their own terms.
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=64", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "maritime-choke"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Interactive model of macroeconomic risk from maritime-chokepoint trade disruptions"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.9"
|
|
11
|
+
license = { text = "MIT" }
|
|
12
|
+
authors = [
|
|
13
|
+
{ name = "Fathimath S. Vemmarath" },
|
|
14
|
+
{ name = "Vipin P. Veetil", email = "vipin.veetil@gmail.com" },
|
|
15
|
+
]
|
|
16
|
+
keywords = [
|
|
17
|
+
"production networks", "input-output", "maritime", "chokepoints",
|
|
18
|
+
"trade disruption", "systemic risk", "economics", "supply chains",
|
|
19
|
+
]
|
|
20
|
+
classifiers = [
|
|
21
|
+
"Programming Language :: Python :: 3",
|
|
22
|
+
"License :: OSI Approved :: MIT License",
|
|
23
|
+
"Operating System :: OS Independent",
|
|
24
|
+
"Intended Audience :: Science/Research",
|
|
25
|
+
"Topic :: Scientific/Engineering",
|
|
26
|
+
"Framework :: Flask",
|
|
27
|
+
]
|
|
28
|
+
dependencies = [
|
|
29
|
+
"numpy>=1.23",
|
|
30
|
+
"scipy>=1.10",
|
|
31
|
+
"matplotlib>=3.6",
|
|
32
|
+
"flask>=2.2",
|
|
33
|
+
"platformdirs>=2.6",
|
|
34
|
+
"certifi>=2022.0.0",
|
|
35
|
+
]
|
|
36
|
+
|
|
37
|
+
[project.optional-dependencies]
|
|
38
|
+
# tools to regenerate the shipped static assets / build & publish the package
|
|
39
|
+
dev = ["pyshp>=2.3", "pandas>=1.5", "build", "twine"]
|
|
40
|
+
|
|
41
|
+
[project.scripts]
|
|
42
|
+
maritime-choke = "maritime_choke.cli:main"
|
|
43
|
+
|
|
44
|
+
[project.urls]
|
|
45
|
+
Homepage = "https://bitbucket.org/VipinVeetil/chokepoints"
|
|
46
|
+
Source = "https://bitbucket.org/VipinVeetil/chokepoints"
|
|
47
|
+
|
|
48
|
+
[tool.setuptools.packages.find]
|
|
49
|
+
where = ["src"]
|
|
50
|
+
|
|
51
|
+
[tool.setuptools.package-data]
|
|
52
|
+
maritime_choke = ["templates/*.html", "static/*.json"]
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"""
|
|
2
|
+
maritime-choke
|
|
3
|
+
==============
|
|
4
|
+
A short-run production-network model of maritime-chokepoint trade disruption,
|
|
5
|
+
with an interactive local web GUI. Close any set of the world's major shipping
|
|
6
|
+
chokepoints, pick a (tau, rho, delta) calibration, solve the model forward, and
|
|
7
|
+
explore where the value-added loss falls on a world heat map and by sector.
|
|
8
|
+
|
|
9
|
+
Quickstart:
|
|
10
|
+
maritime-choke fetch-data # one-time: install the model cache (~180 MB)
|
|
11
|
+
maritime-choke run # launch the GUI in your browser
|
|
12
|
+
"""
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
__version__ = "0.1.0"
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def run(port: int | None = None, open_browser: bool = True) -> None:
|
|
19
|
+
"""Launch the GUI (same as `maritime-choke run`)."""
|
|
20
|
+
from .app import serve
|
|
21
|
+
serve(port=port, open_browser=open_browser)
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
"""
|
|
2
|
+
app.py -- local web GUI for the maritime-chokepoint disruption model.
|
|
3
|
+
|
|
4
|
+
Run:
|
|
5
|
+
cd 02_scripts/gui
|
|
6
|
+
../../.venv_gui/bin/python app.py # then open http://127.0.0.1:5000
|
|
7
|
+
|
|
8
|
+
Pick which chokepoints to close and the (tau, rho, delta) calibration, press
|
|
9
|
+
Run, and the model solves forward. Each run writes a self-contained folder
|
|
10
|
+
under 04_outputs/gui_runs/<run_id>/ (config, summary, per-country CSV, two
|
|
11
|
+
PNG plots) and the headline numbers are shown in the page.
|
|
12
|
+
|
|
13
|
+
Solves run in a background thread; the page polls /api/progress for the live
|
|
14
|
+
outer-iteration count and h-residual, then renders the results.
|
|
15
|
+
"""
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
import threading
|
|
19
|
+
import time
|
|
20
|
+
import traceback
|
|
21
|
+
from pathlib import Path
|
|
22
|
+
|
|
23
|
+
from flask import (Flask, jsonify, render_template, request,
|
|
24
|
+
send_from_directory, abort)
|
|
25
|
+
|
|
26
|
+
from . import solver_backend as sb
|
|
27
|
+
|
|
28
|
+
app = Flask(__name__)
|
|
29
|
+
|
|
30
|
+
# In-memory registry of runs started this session: run_id -> state dict.
|
|
31
|
+
RUNS: dict[str, dict] = {}
|
|
32
|
+
_LOCK = threading.Lock()
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def _worker(run_id: str, cfg: dict) -> None:
|
|
36
|
+
state = RUNS[run_id]
|
|
37
|
+
try:
|
|
38
|
+
summary = sb.run_scenario(cfg, progress=state["progress"])
|
|
39
|
+
state["status"] = "done"
|
|
40
|
+
state["summary"] = summary
|
|
41
|
+
except Exception as e: # noqa: BLE001
|
|
42
|
+
state["status"] = "error"
|
|
43
|
+
state["error"] = f"{e}"
|
|
44
|
+
state["traceback"] = traceback.format_exc()
|
|
45
|
+
state["progress"]["phase"] = "error"
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
@app.route("/")
|
|
49
|
+
def index():
|
|
50
|
+
return render_template(
|
|
51
|
+
"index.html",
|
|
52
|
+
chokepoints=sb.chokepoint_labels(),
|
|
53
|
+
caches=sb.list_caches(),
|
|
54
|
+
presets=sb.PRESETS,
|
|
55
|
+
mapmeta=sb.map_metadata(),
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
@app.route("/api/run", methods=["POST"])
|
|
60
|
+
def api_run():
|
|
61
|
+
body = request.get_json(force=True)
|
|
62
|
+
Q = [int(k) for k in body.get("Q", [])]
|
|
63
|
+
if not Q:
|
|
64
|
+
return jsonify(error="Select at least one chokepoint to close."), 400
|
|
65
|
+
|
|
66
|
+
energy_specific = bool(body.get("energy_specific", True))
|
|
67
|
+
base_delta = float(body.get("base_delta", 0.10))
|
|
68
|
+
energy_delta = (float(body.get("energy_delta", 0.45))
|
|
69
|
+
if energy_specific else None)
|
|
70
|
+
|
|
71
|
+
run_id = body.get("run_id") or f"run_{int(time.time())}"
|
|
72
|
+
# keep run_id filesystem-safe
|
|
73
|
+
run_id = "".join(ch for ch in run_id if ch.isalnum() or ch in "-_")[:60]
|
|
74
|
+
|
|
75
|
+
cfg = dict(
|
|
76
|
+
Q=Q,
|
|
77
|
+
tau=float(body.get("tau", 0.3)),
|
|
78
|
+
rho=float(body.get("rho", -0.5)),
|
|
79
|
+
base_delta=base_delta,
|
|
80
|
+
energy_delta=energy_delta,
|
|
81
|
+
cache=body.get("cache") or "model_cache_2019",
|
|
82
|
+
max_outer=int(body.get("max_outer", 600)),
|
|
83
|
+
run_id=run_id,
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
with _LOCK:
|
|
87
|
+
# one heavy solve at a time keeps the CPU sane on a laptop
|
|
88
|
+
busy = any(s["status"] == "running" for s in RUNS.values())
|
|
89
|
+
if busy:
|
|
90
|
+
return jsonify(error="A solve is already running; wait for it "
|
|
91
|
+
"to finish."), 409
|
|
92
|
+
RUNS[run_id] = {
|
|
93
|
+
"status": "running",
|
|
94
|
+
"cfg": cfg,
|
|
95
|
+
"progress": {"phase": "queued", "iter": 0,
|
|
96
|
+
"max_iter": cfg["max_outer"], "residual": None,
|
|
97
|
+
"elapsed": 0.0},
|
|
98
|
+
"summary": None,
|
|
99
|
+
"error": None,
|
|
100
|
+
}
|
|
101
|
+
t = threading.Thread(target=_worker, args=(run_id, cfg), daemon=True)
|
|
102
|
+
t.start()
|
|
103
|
+
return jsonify(run_id=run_id)
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
@app.route("/api/progress/<run_id>")
|
|
107
|
+
def api_progress(run_id: str):
|
|
108
|
+
state = RUNS.get(run_id)
|
|
109
|
+
if state is None:
|
|
110
|
+
return jsonify(error="unknown run_id"), 404
|
|
111
|
+
return jsonify(
|
|
112
|
+
status=state["status"],
|
|
113
|
+
progress=state["progress"],
|
|
114
|
+
summary=state["summary"],
|
|
115
|
+
error=state["error"],
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
@app.route("/api/runs")
|
|
120
|
+
def api_runs():
|
|
121
|
+
"""List past runs on disk (most recent first)."""
|
|
122
|
+
import json
|
|
123
|
+
out = []
|
|
124
|
+
if sb._runs_dir().exists():
|
|
125
|
+
for d in sb._runs_dir().iterdir():
|
|
126
|
+
sfile = d / "summary.json"
|
|
127
|
+
if sfile.exists():
|
|
128
|
+
try:
|
|
129
|
+
s = json.loads(sfile.read_text())
|
|
130
|
+
out.append({
|
|
131
|
+
"run_id": s.get("run_id", d.name),
|
|
132
|
+
"closed": s.get("closed_chokepoints", []),
|
|
133
|
+
"world_lambda_pct": s.get("world_lambda_pct"),
|
|
134
|
+
"params": s.get("params", {}),
|
|
135
|
+
"mtime": sfile.stat().st_mtime,
|
|
136
|
+
})
|
|
137
|
+
except Exception: # noqa: BLE001
|
|
138
|
+
pass
|
|
139
|
+
out.sort(key=lambda r: r["mtime"], reverse=True)
|
|
140
|
+
return jsonify(runs=out)
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
@app.route("/runs/<run_id>/<path:filename>")
|
|
144
|
+
def serve_run_file(run_id: str, filename: str):
|
|
145
|
+
run_id = "".join(ch for ch in run_id if ch.isalnum() or ch in "-_")
|
|
146
|
+
d = sb._runs_dir() / run_id
|
|
147
|
+
if not d.exists():
|
|
148
|
+
abort(404)
|
|
149
|
+
return send_from_directory(d, filename)
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def _free_port(preferred: int) -> int:
|
|
153
|
+
"""Return `preferred` if free, else an OS-assigned free port."""
|
|
154
|
+
import socket
|
|
155
|
+
for cand in (preferred, 0):
|
|
156
|
+
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
|
157
|
+
try:
|
|
158
|
+
s.bind(("127.0.0.1", cand))
|
|
159
|
+
return s.getsockname()[1]
|
|
160
|
+
except OSError:
|
|
161
|
+
continue
|
|
162
|
+
finally:
|
|
163
|
+
s.close()
|
|
164
|
+
return preferred
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def serve(port: int | None = None, open_browser: bool = True) -> None:
|
|
168
|
+
"""Launch the local GUI server (used by the `maritime-choke run` command)."""
|
|
169
|
+
import os
|
|
170
|
+
if port is None:
|
|
171
|
+
port = int(os.environ.get("PORT", "5057"))
|
|
172
|
+
port = _free_port(port)
|
|
173
|
+
url = f"http://127.0.0.1:{port}"
|
|
174
|
+
print(f"maritime-choke GUI -> {url}")
|
|
175
|
+
print("Run outputs are written to:", sb._runs_dir())
|
|
176
|
+
if not sb.list_caches():
|
|
177
|
+
print("\n[!] No model cache found. Run `maritime-choke fetch-data` first "
|
|
178
|
+
"(or set MARITIME_CHOKE_DATA to a folder that holds model_cache_2019/).\n")
|
|
179
|
+
if open_browser:
|
|
180
|
+
import threading
|
|
181
|
+
import webbrowser
|
|
182
|
+
threading.Timer(1.0, lambda: webbrowser.open(url)).start()
|
|
183
|
+
app.run(host="127.0.0.1", port=port, debug=False, threaded=True)
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
if __name__ == "__main__":
|
|
187
|
+
serve()
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"""
|
|
2
|
+
chokepoint_names.py
|
|
3
|
+
===================
|
|
4
|
+
Single source of truth for the three chokepoint name forms used across the
|
|
5
|
+
pipeline, in the canonical meta.json order (k = 0..11):
|
|
6
|
+
|
|
7
|
+
FULL : meta["chokepoints"] -- table row labels, captions
|
|
8
|
+
CSV : short keys used as the master-matrix CSV columns and by
|
|
9
|
+
verification/verify_ras_prose.py (e.g. "Hormuz", "Bab-el-Mandeb")
|
|
10
|
+
NICE : compact display labels for figure axes / joint constituents
|
|
11
|
+
(e.g. "BAM/Aden", "Malacca-Sing.")
|
|
12
|
+
|
|
13
|
+
Before this module the committed outputs used CSV columns while the committed
|
|
14
|
+
05_figures.py used FULL columns + a FULL->NICE map, so the script could not
|
|
15
|
+
regenerate the committed CSV/figures. All scripts now import these maps so the
|
|
16
|
+
naming is consistent end to end.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
FULL = [
|
|
20
|
+
"Strait of Hormuz", "Turkish Straits", "Danish Straits", "Suez Canal",
|
|
21
|
+
"Bab-el-Mandeb", "Strait of Gibraltar", "Strait of Malacca", "Panama Canal",
|
|
22
|
+
"Taiwan-Luzon Corridor", "Strait of Dover", "Korea Strait", "Kerch Strait",
|
|
23
|
+
]
|
|
24
|
+
CSV = [
|
|
25
|
+
"Hormuz", "Turkish", "Danish", "Suez", "Bab-el-Mandeb", "Gibraltar",
|
|
26
|
+
"Malacca", "Panama", "Taiwan-Luzon", "Dover", "Korea", "Kerch",
|
|
27
|
+
]
|
|
28
|
+
NICE = [
|
|
29
|
+
"Hormuz", "Turkish", "Danish", "Suez", "BAM/Aden", "Gibraltar",
|
|
30
|
+
"Malacca-Sing.", "Panama", "Taiwan-Luzon", "Dover", "Korea", "Kerch",
|
|
31
|
+
]
|
|
32
|
+
|
|
33
|
+
assert len(FULL) == len(CSV) == len(NICE) == 12
|
|
34
|
+
|
|
35
|
+
full_to_csv = dict(zip(FULL, CSV))
|
|
36
|
+
full_to_nice = dict(zip(FULL, NICE))
|
|
37
|
+
csv_to_full = dict(zip(CSV, FULL))
|
|
38
|
+
csv_to_nice = dict(zip(CSV, NICE))
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def to_csv_columns(full_names):
|
|
42
|
+
"""Map an ordered list of FULL chokepoint names to CSV-short columns."""
|
|
43
|
+
return [full_to_csv[c] for c in full_names]
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
"""
|
|
2
|
+
cli.py — the `maritime-choke` command.
|
|
3
|
+
|
|
4
|
+
maritime-choke run launch the interactive GUI (opens a browser)
|
|
5
|
+
maritime-choke fetch-data download/install the model cache
|
|
6
|
+
maritime-choke pack-data <dir> pack a local cache into an uploadable archive
|
|
7
|
+
maritime-choke where show data / cache / run-output locations
|
|
8
|
+
maritime-choke build-data how to rebuild the cache from GTAP + OECD ICIO
|
|
9
|
+
"""
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import argparse
|
|
13
|
+
|
|
14
|
+
from . import __version__, data
|
|
15
|
+
|
|
16
|
+
BUILD_HELP = """\
|
|
17
|
+
Rebuild the model cache from raw inputs (for users with their own GTAP licence)
|
|
18
|
+
------------------------------------------------------------------------------
|
|
19
|
+
The cache is derived from OECD ICIO (freely downloadable) and the licensed
|
|
20
|
+
GTAP 10 data base. To rebuild it, use the source repository's pipeline:
|
|
21
|
+
|
|
22
|
+
git clone https://bitbucket.org/VipinVeetil/chokepoints
|
|
23
|
+
# place your GTAP .har files and the OECD ICIO tables under 01_raw_data/
|
|
24
|
+
cd chokepoints/02_scripts
|
|
25
|
+
python3 pipeline/00_extract_gtap_har.py
|
|
26
|
+
python3 pipeline/01_build_extended_table_fast.py --year 2019
|
|
27
|
+
python3 pipeline/02_build_inputs_k12.py
|
|
28
|
+
|
|
29
|
+
That writes 03_intermediate/model_cache_2019/. Then install it into
|
|
30
|
+
maritime-choke's data dir:
|
|
31
|
+
|
|
32
|
+
maritime-choke fetch-data --from-local <path>/03_intermediate/model_cache_2019
|
|
33
|
+
|
|
34
|
+
See DATA_AVAILABILITY.md in that repo for the licence notes.
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def main(argv=None) -> None:
|
|
39
|
+
p = argparse.ArgumentParser(
|
|
40
|
+
prog="maritime-choke",
|
|
41
|
+
description="Interactive model of macroeconomic risk from "
|
|
42
|
+
"maritime-chokepoint trade disruptions.")
|
|
43
|
+
p.add_argument("--version", action="version",
|
|
44
|
+
version=f"maritime-choke {__version__}")
|
|
45
|
+
sub = p.add_subparsers(dest="cmd")
|
|
46
|
+
|
|
47
|
+
pr = sub.add_parser("run", help="launch the interactive GUI")
|
|
48
|
+
pr.add_argument("--port", type=int, default=None, help="port (default 5057)")
|
|
49
|
+
pr.add_argument("--no-browser", action="store_true",
|
|
50
|
+
help="do not open a browser window")
|
|
51
|
+
|
|
52
|
+
pf = sub.add_parser("fetch-data", help="download/install the model cache")
|
|
53
|
+
pf.add_argument("--url", default=None, help="explicit archive URL")
|
|
54
|
+
pf.add_argument("--from-local", default=None,
|
|
55
|
+
help="a local cache directory or .tar.xz to install instead")
|
|
56
|
+
pf.add_argument("--name", default=data.DEFAULT_CACHE, help="cache name")
|
|
57
|
+
pf.add_argument("--force", action="store_true", help="overwrite if present")
|
|
58
|
+
|
|
59
|
+
pp = sub.add_parser("pack-data",
|
|
60
|
+
help="pack a local cache dir into an uploadable .tar.xz")
|
|
61
|
+
pp.add_argument("cache_dir", help="path to a model_cache_* directory")
|
|
62
|
+
pp.add_argument("-o", "--out", default=None, help="output archive path")
|
|
63
|
+
|
|
64
|
+
sub.add_parser("where", help="show data / cache / run-output locations")
|
|
65
|
+
sub.add_parser("build-data", help="how to rebuild the cache from GTAP + ICIO")
|
|
66
|
+
|
|
67
|
+
args = p.parse_args(argv)
|
|
68
|
+
|
|
69
|
+
if args.cmd == "run":
|
|
70
|
+
from . import app
|
|
71
|
+
app.serve(port=args.port, open_browser=not args.no_browser)
|
|
72
|
+
|
|
73
|
+
elif args.cmd == "fetch-data":
|
|
74
|
+
path = data.fetch(url=args.url, name=args.name,
|
|
75
|
+
from_local=args.from_local, force=args.force)
|
|
76
|
+
print("cache ready at:", path)
|
|
77
|
+
|
|
78
|
+
elif args.cmd == "pack-data":
|
|
79
|
+
out = data.pack(args.cache_dir, args.out)
|
|
80
|
+
mb = out.stat().st_size / (1 << 20)
|
|
81
|
+
print(f"wrote {out} ({mb:.0f} MB)")
|
|
82
|
+
print("Upload this to Zenodo/Release, then set MARITIME_CHOKE_DATA_URL "
|
|
83
|
+
"(or DATA_URL in data.py) to its download URL.")
|
|
84
|
+
|
|
85
|
+
elif args.cmd == "where":
|
|
86
|
+
print("data dir :", data.data_dir())
|
|
87
|
+
print("caches :", data.caches_dir())
|
|
88
|
+
print("run outputs:", data.runs_dir())
|
|
89
|
+
caches = [p.name for p in data.caches_dir().glob("model_cache*")
|
|
90
|
+
if data.is_valid_cache(p)] if data.caches_dir().exists() else []
|
|
91
|
+
print("installed caches:", caches or "(none — run `maritime-choke fetch-data`)")
|
|
92
|
+
|
|
93
|
+
elif args.cmd == "build-data":
|
|
94
|
+
print(BUILD_HELP)
|
|
95
|
+
|
|
96
|
+
else:
|
|
97
|
+
p.print_help()
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
if __name__ == "__main__":
|
|
101
|
+
main()
|