debt-atlas 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.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Jaudat Faisal Al-Husein and Global Debt & Fiscal Atlas contributors
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.
@@ -0,0 +1,102 @@
1
+ Metadata-Version: 2.4
2
+ Name: debt-atlas
3
+ Version: 0.1.0
4
+ Summary: Sovereign debt, deficits, government spending and who-owes-whom data for every country — World Bank and IMF, with provenance.
5
+ Author-email: Jaudat Faisal Al-Husein <jaudatalhusein@gmail.com>
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://jaudat150.github.io/global-debt-explorer/
8
+ Project-URL: Source, https://github.com/Jaudat150/global-debt-explorer
9
+ Project-URL: Documentation, https://github.com/Jaudat150/global-debt-explorer/blob/main/docs/LIBRARY.md
10
+ Project-URL: Issues, https://github.com/Jaudat150/global-debt-explorer/issues
11
+ Project-URL: Changelog, https://github.com/Jaudat150/global-debt-explorer/blob/main/CHANGELOG.md
12
+ Keywords: sovereign debt,public debt,government spending,military spending,world bank,imf,open data,public finance,economics,fiscal,bilateral debt,china lending
13
+ Classifier: Development Status :: 4 - Beta
14
+ Classifier: Intended Audience :: Science/Research
15
+ Classifier: Intended Audience :: Education
16
+ Classifier: Intended Audience :: Developers
17
+ Classifier: Programming Language :: Python :: 3
18
+ Classifier: Topic :: Scientific/Engineering :: Information Analysis
19
+ Classifier: Topic :: Office/Business :: Financial
20
+ Requires-Python: >=3.9
21
+ Description-Content-Type: text/markdown
22
+ License-File: LICENSE
23
+ Requires-Dist: pandas>=1.5
24
+ Dynamic: license-file
25
+
26
+ # debt-atlas
27
+
28
+ **Sovereign debt, deficits, government spending and who-owes-whom data for every country — in one line of Python.**
29
+
30
+ `debt-atlas` is the Python library of the open-source [Global Debt & Fiscal Atlas](https://github.com/Jaudat150/global-debt-explorer).
31
+ It gives you 37 indicators from the World Bank and IMF, government spending in US dollars, national debt profiles and
32
+ bilateral lending between countries as tidy pandas DataFrames — every value with its source, series code and year.
33
+
34
+ ```bash
35
+ pip install debt-atlas
36
+ ```
37
+
38
+ ## Quick start
39
+
40
+ ```python
41
+ import debt_atlas as da
42
+
43
+ da.latest("gov_debt_gdp", countries=["Japan", "Italy", "USA"]) # government debt, % of GDP
44
+ da.rank("military_usd", n=10) # biggest military spenders, US$
45
+ da.spending("United States") # where the money goes, in US$
46
+ da.who_owes("Kenya") # Kenya's lenders and how much
47
+ da.owed_by("China") # who owes China
48
+ da.series("inflation", "Argentina", start=2010) # history, one row per source
49
+ da.compare(["Egypt", "Pakistan", "Kenya"], ["gov_debt_gdp", "interest_gdp", "health_gov_gdp"])
50
+ da.profile("Syria") # 3-tier national debt profile
51
+ ```
52
+
53
+ Countries can be given as names (`"Kenya"`), ISO2 (`"KE"`) or ISO3 (`"KEN"`).
54
+
55
+ ## What is inside
56
+
57
+ | Function | Returns |
58
+ |---|---|
59
+ | `indicators(category=None)` | Every indicator: id, name, unit, description, upstream series |
60
+ | `latest(indicator, countries=None)` | Latest value per country, with year, estimate flag and source |
61
+ | `rank(indicator, n=10, lowest=False)` | Top or bottom countries |
62
+ | `series(indicator, countries, start, end, provider)` | Full history, one row per upstream series |
63
+ | `wide(indicator, countries)` | Year × country table |
64
+ | `compare(countries, indicators)` | Several indicators for several countries side by side |
65
+ | `spending(country=None, item=None)` | Military, health, education, social transfers, wages, interest, R&D, total — US$, per person, % of GDP |
66
+ | `who_owes(country)` / `owed_by(country)` | Bilateral lenders / borrowers with amounts and shares (World Bank IDS) |
67
+ | `bilateral(debtor, creditor, year)` | The full who-owes-whom table |
68
+ | `profile(country)` | National debt profile with source tier |
69
+ | `sources(topic=None)` | Where to find newer or more detailed data |
70
+ | `countries()` | ISO codes, names, coordinates |
71
+ | `live(series_code)` | Any series straight from the World Bank or IMF API |
72
+ | `download_all(folder)` | Every file plus a Frictionless `datapackage.json` |
73
+
74
+ Categories: `debt`, `spending`, `revenue`, `economy`, `flows`, `people`.
75
+
76
+ ## Command line
77
+
78
+ ```bash
79
+ debt-atlas rank military_usd -n 10
80
+ debt-atlas latest gov_debt_gdp --countries Japan Italy
81
+ debt-atlas who-owes Pakistan
82
+ debt-atlas spending "United States" --csv > usa.csv
83
+ debt-atlas download ./atlas-data
84
+ ```
85
+
86
+ ## Where the data comes from
87
+
88
+ The project rebuilds its data every week from the World Bank (World Development Indicators, International Debt
89
+ Statistics) and IMF (World Economic Outlook, Global Debt Database, Public Finances in Modern History) APIs, validates it
90
+ against published reference figures, and publishes it as CSV. The library downloads those files (cached for 24 hours
91
+ in `~/.cache/debt_atlas`) — fast, no API keys, and identical to what the web app shows.
92
+
93
+ - IMF values for the current year are staff estimates and are flagged `is_estimate`; projections are never included.
94
+ - Missing values are absent, never zero.
95
+ - Use another source with `da.Atlas("path/or/url")`, or set `DEBT_ATLAS_SOURCE`.
96
+
97
+ ## Citing
98
+
99
+ Please cite the original data providers named in each row's `source` column, and this project
100
+ (see [CITATION.cff](https://github.com/Jaudat150/global-debt-explorer/blob/main/CITATION.cff)).
101
+
102
+ Code: MIT licence. Data: each provider's terms (World Bank CC BY 4.0, IMF, SIPRI, WHO, UNESCO).
@@ -0,0 +1,354 @@
1
+ # 🌐 Global Debt & Fiscal Atlas
2
+
3
+ [![Tests](https://github.com/Jaudat150/global-debt-explorer/actions/workflows/tests.yml/badge.svg)](https://github.com/Jaudat150/global-debt-explorer/actions/workflows/tests.yml)
4
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
5
+ ![Python 3.11–3.13](https://img.shields.io/badge/python-3.11%E2%80%933.13-blue)
6
+ ![Streamlit](https://img.shields.io/badge/built%20with-Streamlit-ff4b4b)
7
+ [![Cite](https://img.shields.io/badge/cite-CITATION.cff-informational)](CITATION.cff)
8
+
9
+ **Who owes what, who spends what — for every country, with the source and year of every number.**
10
+
11
+ An open-source atlas of sovereign debt, deficits, government spending, revenue, the economy and development, built
12
+ for students, researchers, journalists and anyone curious about public money. Data comes **live** from the World Bank
13
+ and IMF APIs; when a number is missing or out of date, the app tells you exactly where to find a better one.
14
+
15
+ <p align="center">
16
+ <b>🗺️ Interactive app</b> · <a href="https://jaudat150.github.io/global-debt-explorer/"><b>🌐 Website: every country</b></a> ·
17
+ <a href="docs/LIBRARY.md"><b>🐍 <code>pip install debt-atlas</code></b></a> · <a href="https://jaudat150.github.io/global-debt-explorer/data/"><b>⬇️ Data</b></a>
18
+ </p>
19
+
20
+ ## Why this project
21
+
22
+ Public-finance data is scattered across a dozen institutions, each with its own codes, units, definitions and
23
+ release calendars. This atlas puts 37 indicators on one map while keeping what researchers need:
24
+
25
+ - **Provenance on every value** — source, series code, year, and whether it is an IMF estimate.
26
+ - **Freshness first** — when two series share a definition, the most recent valid value per country wins.
27
+ Projections are never shown as data.
28
+ - **Honest gaps** — missing is never zero. Grey countries link to 31 curated sources for newer or
29
+ more detailed data.
30
+ - **Works offline** — 24-hour caching, a weekly snapshot refreshed by GitHub Actions, and graceful fallback when an
31
+ API is down.
32
+
33
+ ## What you can do
34
+
35
+ | View | What it shows |
36
+ |---|---|
37
+ | 🗺️ **World atlas** | Choropleth map of any indicator; click a country to draw **who-owes-whom arrows** (lender → borrower, with amounts); top/bottom rankings; full table and CSV export with provenance; "find newer data" links. |
38
+ | 📊 **Country profile** | Headline numbers, **where the money goes** (defence, health, education, interest, R&D as % of GDP), a 25-year trend for any indicator, and links to the IMF, World Bank, budget documents and national statistics office. |
39
+ | 💸 **Spending** | What each government spends on defence, health, education, social transfers, wages, interest and R&D — in US$, per person and % of GDP, with the exact series behind every number — plus a world ranking for each line. |
40
+ | 🔗 **Who owes whom** | Bilateral lending from World Bank International Debt Statistics: arrows from every lender to the selected borrower **and** from the selected lender to everyone who owes it, with amounts, shares and each country's national debt profile. |
41
+ | 📚 **Data & sources** | Live freshness check of every indicator, one-click download of the full dataset, and the full source directory. |
42
+ | 🧑‍💻 **Developers** | One-click ZIP of all data with a Frictionless `datapackage.json`, every upstream API with live links and code examples (curl, Python, R), the project's Python API and CLI, and a full data dictionary. |
43
+
44
+ ### Share any view
45
+
46
+ Every view has its own link, and the **🔗 Share** button copies it:
47
+
48
+ ```text
49
+ ?view=atlas&indicator=military_gdp&country=KEN military spending map, Kenya selected
50
+ ?view=spending&country=USA&item=health where the US spends, health ranking
51
+ ?view=debt&country=CHN who owes China, and whom China owes
52
+ ```
53
+
54
+ ### Indicators
55
+
56
+ | Category | # | Indicators |
57
+ |---|---|---|
58
+ | 🏦 **Debt & deficits** | 7 | Gov. debt, Fiscal balance, External debt, External debt / GNI, Debt service / exports, Interest / revenue, Private debt |
59
+ | 🧾 **Government spending** | 11 | Total spending, Military, Military / budget, Health, Health / budget, Education, Education / budget, Interest, R&D, Public wages, Subsidies & transfers |
60
+ | 💰 **Revenue & taxes** | 2 | Revenue, Tax revenue |
61
+ | 📈 **Economy** | 6 | GDP, GDP per capita, GDP growth, Inflation, Unemployment, Current account |
62
+ | 🌍 **External finance** | 4 | FDI inflows, Remittances, Aid received, Reserves cover |
63
+ | 👥 **People & development** | 4 | Population, Life expectancy, Extreme poverty, Inequality (Gini) |
64
+
65
+ Full definitions, series codes and links: **[docs/DATA_SOURCES.md](docs/DATA_SOURCES.md)**.
66
+
67
+ ## Python library — `pip install debt-atlas`
68
+
69
+ ```python
70
+ import debt_atlas as da
71
+
72
+ da.rank("military_usd", n=10) # biggest military spenders, US$
73
+ da.latest("gov_debt_gdp", countries=["Japan", "Italy", "USA"])
74
+ da.spending("United States") # where the money goes, in US$
75
+ da.who_owes("Kenya") # Kenya's lenders and how much
76
+ da.owed_by("China") # who owes China
77
+ da.series("inflation", "Argentina", start=2010) # full history, per source
78
+ ```
79
+
80
+ Lightweight (pandas only), cached, no API keys; accepts country names, ISO2 or ISO3. Also a command line:
81
+ `debt-atlas who-owes Pakistan`. Full reference: **[docs/LIBRARY.md](docs/LIBRARY.md)**.
82
+
83
+ ## Website — a page for every country
84
+
85
+ [jaudat150.github.io/global-debt-explorer](https://jaudat150.github.io/global-debt-explorer/) has a fast, readable page for
86
+ every country (debt, spending, who it owes, who owes it, FAQ) and every indicator (full ranking with sources), rebuilt
87
+ weekly from the same validated data (`python -m src.website build`).
88
+
89
+ ## Quick start
90
+
91
+ Python 3.11–3.13.
92
+
93
+ ```bash
94
+ git clone https://github.com/Jaudat150/global-debt-explorer.git
95
+ cd global-debt-explorer
96
+ python -m venv .venv
97
+ source .venv/bin/activate # Windows: .venv\Scripts\activate
98
+ pip install -r requirements.txt
99
+ streamlit run app.py
100
+ ```
101
+
102
+ Open <http://localhost:8501>. The atlas, country profiles and sources work immediately. The **Who owes whom** view
103
+ needs the bilateral cache (`data/bilateral_debt_cache.json`, included; refresh with `python -m src.fetcher`).
104
+ Windows users: see [START_HERE_WINDOWS.txt](START_HERE_WINDOWS.txt).
105
+
106
+ ### Deploy your own copy for free
107
+
108
+ 1. Fork this repository.
109
+ 2. On [Streamlit Community Cloud](https://streamlit.io/cloud), choose **New app**, select your fork and `app.py`.
110
+ 3. Enable GitHub Actions on your fork so the weekly data snapshot keeps refreshing.
111
+
112
+ ## How fresh is the data?
113
+
114
+ | Layer | Source | Refresh |
115
+ |---|---|---|
116
+ | Atlas indicators | World Bank WDI/IDS API, IMF DataMapper API | Live, cached 24 h; weekly snapshot via `refresh_indicators.yml` |
117
+ | National debt profile | World Bank → IMF → curated estimates → last-known snapshot | Live, cached 24 h |
118
+ | Bilateral debt | World Bank IDS (source 6) | Monthly via `update_data.yml` |
119
+
120
+ The IMF publishes the World Economic Outlook in April and October; values for the current year are IMF staff
121
+ estimates and are flagged (toggle **Reported data only** to hide them). The World Bank publishes WDI several times a
122
+ year and International Debt Statistics each December. When the live sources lag, the in-app **Find newer data**
123
+ panel and [docs/DATA_SOURCES.md](docs/DATA_SOURCES.md) list where newer figures appear first — usually national
124
+ statistics offices, ministries of finance, SIPRI, WHO and the IMF Fiscal Monitor.
125
+
126
+ ## Open data for everyone
127
+
128
+ Every dataset is published as plain CSV with a [Frictionless Data Package](https://specs.frictionlessdata.io/data-package/)
129
+ descriptor, rebuilt weekly by GitHub Actions into [`data/exports/`](data/exports/README.md):
130
+
131
+ ```python
132
+ import pandas as pd
133
+ base = "https://raw.githubusercontent.com/Jaudat150/global-debt-explorer/main/data/exports"
134
+ latest = pd.read_csv(f"{base}/latest_values.csv") # every indicator, every country, with provenance
135
+ spending = pd.read_csv(f"{base}/spending_usd.csv") # where the money goes, in US$
136
+ history = pd.read_csv(f"{base}/timeseries.csv.gz") # full per-source time series
137
+ owes = pd.read_csv(f"{base}/bilateral_debt.csv.gz") # who owes whom
138
+ ```
139
+
140
+ Or build everything locally: `python -m src.exports build`, or use **Developers → Build the full data bundle** in the app.
141
+
142
+ ## Using the data in research and teaching
143
+
144
+ - **CSV exports** from the atlas and the Data & sources view include ISO3, value, unit, year, estimate flag, source,
145
+ series code, series URL and retrieval timestamp.
146
+ - **Command line:**
147
+
148
+ ```bash
149
+ python -m src.indicator_data show gov_debt_gdp KEN GHA ZMB # one indicator, any countries
150
+ python -m src.indicator_data refresh # download everything, update the snapshot
151
+ python -m src.processor profiles SYR PSE CAN SAU # national debt profiles with tier trail
152
+ python -m src.catalog # list every indicator and series code
153
+ python -m src.exports build # every dataset as CSV + datapackage.json
154
+ python -m src.validate # check coverage, reference figures, consistency
155
+ ```
156
+
157
+ - **Cite** this project via [CITATION.cff](CITATION.cff) (GitHub shows a "Cite this repository" button) **and** the
158
+ original data providers shown next to each value.
159
+
160
+ ## Architecture
161
+
162
+ ```mermaid
163
+ flowchart LR
164
+ subgraph Live["Live APIs (24 h cache)"]
165
+ WB[World Bank V2<br/>WDI · IDS]
166
+ IMF[IMF DataMapper<br/>WEO · GDD · FPP]
167
+ end
168
+ CAT[src/catalog.py<br/>indicators + source directory] --> DATA
169
+ WB --> DATA[src/indicator_data.py<br/>freshest-wins resolver]
170
+ IMF --> DATA
171
+ SNAP[(data/indicator_snapshot.json<br/>weekly via Actions)] -. source down .-> DATA
172
+ DATA --> ATLAS[src/atlas_map.py<br/>choropleth]
173
+ DATA --> VIEWS[src/atlas_view.py<br/>atlas · country · sources]
174
+ WB --> PROF[src/processor.py<br/>3-tier national debt profile]
175
+ IMF --> PROF
176
+ IDS[(data/bilateral_debt_cache.json<br/>monthly)] --> REL[src/relationship_map.py]
177
+ PROF --> REL
178
+ ATLAS --> APP[app.py · Streamlit]
179
+ VIEWS --> APP
180
+ REL --> APP
181
+ ```
182
+
183
+ ## Repository layout
184
+
185
+ ```text
186
+ global-debt-explorer/
187
+ ├── app.py # Streamlit entry point and view switcher
188
+ ├── debt_atlas/ # Python library (pip install debt-atlas)
189
+ ├── pyproject.toml # library packaging
190
+ ├── src/
191
+ │ ├── catalog.py # indicator catalogue + external source directory (pure data)
192
+ │ ├── indicator_data.py # live download, 24 h cache, freshest-wins resolution, snapshot, CLI
193
+ │ ├── atlas_map.py # choropleth + legend
194
+ │ ├── atlas_view.py # World atlas, Country profile, Data & sources views
195
+ │ ├── debt_flows.py # who-owes-whom: lender → borrower arrows and tables
196
+ │ ├── spending.py / spending_view.py # where the money goes, in US$
197
+ │ ├── exports.py / developer_view.py # open-data exports, datapackage.json, Developers tab
198
+ │ ├── website.py # static SEO website (country + indicator pages, sitemap)
199
+ │ ├── validate.py · share.py # data validation · shareable links
200
+ │ ├── fetcher.py # World Bank IDS bilateral pipeline + national-profile API clients
201
+ │ ├── processor.py # IDS processing + 3-tier national debt profiles
202
+ │ ├── relationship_map.py # Who-owes-whom map
203
+ │ ├── map_geometry.py # Natural Earth boundaries
204
+ │ └── ui_state.py
205
+ ├── data/ # caches, snapshots, label points (see data/README.md)
206
+ ├── docs/DATA_SOURCES.md # generated from src/catalog.py
207
+ ├── tests/ # network-free unit tests
208
+ ├── .github/ # CI, weekly/monthly data refresh, issue & PR templates
209
+ ├── CITATION.cff · CONTRIBUTING.md · CODE_OF_CONDUCT.md · ROADMAP.md · CHANGELOG.md · LICENSE
210
+ ```
211
+
212
+ ## Methodology details
213
+
214
+ ### National debt profile: 3-tier fallback
215
+
216
+ The national-profile layer is independent of the bilateral IDS cache. For **every** ISO-3 code, `src/processor.py` resolves a profile in this order and never raises because of a network failure:
217
+
218
+ | Tier | Source | Indicator | Returned metric |
219
+ |---|---|---|---|
220
+ | 1 — Primary | World Bank V2 API | `DT.DOD.DLXF.CD` | Long-term external debt stocks, current US$ |
221
+ | 2 — Secondary | IMF DataMapper API | `GGXWDG_NGDP` (World Economic Outlook), then `GG_DEBT_GDP` (Global Debt Database) | General government gross debt, % of GDP |
222
+ | 3 — Safety net | a) `LOCAL_FALLBACK_DB` · b) last-known-good snapshot | Curated, sourced estimates · last value retrieved from a live source | The metric stated by the record |
223
+ | — | none of the above | — | Explicit `No verified data` profile (never zero), still plotted |
224
+
225
+ A tier is skipped — and the next one tried — when its source is **unreachable**, has **no non-null value**, returns **zero/negative/non-numeric** data, or its latest observation is **older than 10 years** (`GDE_MAX_OBSERVATION_AGE_YEARS`). Each profile carries a `resolution_trail`, e.g. `Tier 1 World Bank: no non-null observation → Tier 2 IMF: latest non-null observation used`, which the dashboard shows under the country panel.
226
+
227
+ #### Tier 1 — World Bank
228
+
229
+ ```text
230
+ https://api.worldbank.org/v2/country/{ISO3}/indicator/DT.DOD.DLXF.CD?format=json
231
+ ```
232
+
233
+ The parser reads the observation array at index `[1]` and selects the latest year with a non-null value. For the map, one paginated `country/all` request replaces ~250 single-country calls; if that bulk request fails, the exact per-country route above is queried concurrently behind a circuit breaker (it stops after 5 consecutive failures so an outage costs seconds, not minutes).
234
+
235
+ **Definition:** `DT.DOD.DLXF.CD` is long-term external debt — public, publicly guaranteed **and** private nonguaranteed — not public debt alone. High-income creditor countries such as Canada and Saudi Arabia do not report to IDS, so they normally resolve at Tier 2. See the [World Bank debt-statistics FAQ](https://www.worldbank.org/en/programs/debt-statistics/faq).
236
+
237
+ #### Tier 2 — IMF DataMapper
238
+
239
+ ```text
240
+ https://www.imf.org/external/datamapper/api/v1/GG_DEBT_GDP
241
+ ```
242
+
243
+ The full payload is downloaded once and read at `data["values"]["GG_DEBT_GDP"][code]`.
244
+
245
+ - **Projections excluded.** DataMapper series include World Economic Outlook forecasts up to five years ahead. Years after the current year are ignored, so a 2030 forecast is never shown as the latest value. Current-year values are flagged as IMF staff estimates.
246
+ - **Code aliases.** The IMF uses `WBG` for West Bank and Gaza and `UVK` for Kosovo; these are translated automatically.
247
+ - This is a **ratio**, displayed as `% of GDP`, and is never ranked against US$ stocks. See the [IMF DataMapper API](https://www.imf.org/external/datamapper/api/).
248
+
249
+ #### Tier 3 — local safety net
250
+
251
+ **a) Curated estimates — `LOCAL_FALLBACK_DB`.** Used when both live tiers come up empty:
252
+
253
+ - **Syria (`SYR`):** US$20–23B estimated total external debt (range attributed to the post-2024 government), reported by [PIIE](https://www.piie.com/blogs/realtime-economics/2025/syria-needs-debt-restructuring-will-be-difficult) and the [EUAA](https://www.euaa.europa.eu/coi/syria/2025/country-focus/3-socio-economic-situation-focus-damascus-city/31-economic-situation-overview). PIIE describes the World Bank's figure (~US$4.1B, 2023) as very incomplete.
254
+ - **West Bank and Gaza (`PSE`):** US$4.8B Palestinian Authority public debt at end-2025, [World Bank Palestinian Economic Monitor](https://thedocs.worldbank.org/en/doc/caf69bb9db6d729c6b35254c5815df9a-0280012026/original/Palestinian-Economic-Monitor-5-19-26web.pdf) (May 19, 2026). Excludes large arrears.
255
+
256
+ Each record keeps its value or range, metric type, observation year, source URL, source publication date, review date (`reviewed_at`) and a perimeter caveat. When a live tier *does* return a value for one of these countries, the curated figure is not discarded: it is attached as `alternative_estimate` and shown as "Other estimate". Set `"prefer_over_live": True` on an entry only if you have a documented reason to rank it above the live series.
257
+
258
+ To add a country, append a record with the same fields. Add one only when a credible source, value or range, period and source date can be recorded.
259
+
260
+ **b) Last-known-good snapshot.** Every successful live response is merged into `data/country_profiles_snapshot.json`. If the World Bank or IMF is **unreachable** (not merely "no data"), the last known value is used, labelled `Last retrieved YYYY-MM-DD · live source unreachable`. The monthly GitHub workflow refreshes this file; you can also run `python -m src.processor profiles`.
261
+
262
+ #### Stable country-profile contract
263
+
264
+ Every valid ISO-3 code returns a profile with at least:
265
+
266
+ - `iso3`, `country_name`
267
+ - `value`, `value_low`, `value_high`, `display_value`
268
+ - `metric_type`, `metric_label`, `metric_short`, `unit`, `observation_year`
269
+ - `source_label`, `data_source`, `source_url`, `source_timestamp`, `source_date_label`
270
+ - `fallback_tier`, `tier_label`, `is_estimate`, `is_stale_snapshot`, `data_available`
271
+ - `coordinates` (`{"latitude", "longitude"}`), `map_available`, `definition_note`
272
+ - `resolution_trail`, `alternative_estimate`
273
+
274
+ Coordinates come from `data/country_label_points.json` (Natural Earth 1:10m label points, 241 economies, including small states absent from the 1:110m outlines). The map prefers a polygon's representative point where one exists. Natural Earth's own codes for Palestine (`PSX`), South Sudan (`SDS`), Kosovo (`KOS`) and Western Sahara (`SAH`) are translated to ISO codes so those countries match their data.
275
+
276
+ #### Caching, outages and refresh
277
+
278
+ - Live API responses are cached with `@st.cache_data(ttl=86400)` — 24 hours ([docs](https://docs.streamlit.io/develop/api-reference/caching-and-state/st.cache_data)).
279
+ - **Failures are never cached:** a failed call raises inside the cached function, so Streamlit stores nothing. A 5-minute in-process back-off then stops the dead source from adding timeout latency to every rerun.
280
+ - If both live APIs are down, the dashboard shows a banner and runs on Tier 3.
281
+ - **Refresh country data** clears the 24-hour cache and the back-off.
282
+ - Request timeouts: 5 s connect / 20 s read, up to 3 attempts with exponential back-off honouring `Retry-After`.
283
+
284
+ #### Debugging a country from the command line
285
+
286
+ ```cmd
287
+ python -m src.processor profiles CAN SAU SYR PSE
288
+ python -m src.processor profiles SYR --json
289
+ ```
290
+
291
+ Prints each country's tier, value and source; `--json` includes the full resolution trail.
292
+
293
+ ### Bilateral IDS data (Who owes whom)
294
+
295
+ The bilateral relationship map continues to use World Bank International Debt Statistics (IDS), source ID `6`, indicator `DT.DOD.BLAT.CD` — *PPG, bilateral (DOD, current US$)*.
296
+
297
+ | Field | Definition |
298
+ |---|---|
299
+ | Debtor | Reporting economy in the IDS debtor dimension |
300
+ | Creditor | IDS `counterpart-area` identifier and label |
301
+ | Amount | Public and publicly guaranteed bilateral debt outstanding and disbursed, current US dollars |
302
+ | Time | Annual observation year |
303
+
304
+ `src/fetcher.py` retrieves counterpart-area metadata, sends semicolon-delimited creditor ID batches, follows pagination, retries transient errors, and recursively splits batches rejected by the API. It requests `time/all` then filters the requested year range locally. The default start year is 2000; use `--start-year 1960` for a longer history.
305
+
306
+ The monthly GitHub Actions workflow refreshes this bilateral cache and the country-profile snapshot. National country profiles are requested by the dashboard and cached for 24 hours at runtime.
307
+
308
+ ### Map tiles
309
+
310
+ The default basemap uses OpenStreetMap Standard tiles and keeps the required attribution visible. Follow the [OpenStreetMap tile usage policy](https://operations.osmfoundation.org/policies/tiles/); the public tile service is best-effort and is not intended for heavy production traffic. For public deployments with more traffic, use a dedicated tile provider or host your own tiles.
311
+
312
+ Optional CARTO Positron support is available with a user-provided API key. Never commit keys:
313
+
314
+ ```cmd
315
+ set GDE_MAP_BASEMAP=carto
316
+ set CARTO_BASEMAP_API_KEY=YOUR_PERSONAL_KEY
317
+ python -m streamlit run app.py
318
+ ```
319
+
320
+ Without a key, the app automatically uses OpenStreetMap.
321
+
322
+ ## Tests
323
+
324
+ ```bash
325
+ python -m unittest discover -s tests -v
326
+ ```
327
+
328
+ The suite is network-free (all HTTP is mocked) and covers API parsing, projection and estimate handling,
329
+ freshest-wins resolution, outage back-off, snapshots, colour classification, maps, the 3-tier national profile and
330
+ the bilateral pipeline. CI runs it on Python 3.11, 3.12 and 3.13 and checks that `docs/DATA_SOURCES.md` matches the
331
+ catalogue.
332
+
333
+ ## Team
334
+
335
+ Created and led by **Jaudat Faisal Al-Husein** ([@Jaudat150](https://github.com/Jaudat150)). Maintainers, roles and how
336
+ the team works: [docs/TEAM_PLAYBOOK.md](docs/TEAM_PLAYBOOK.md).
337
+
338
+ ## Contributing
339
+
340
+ Contributions of every size are welcome — reporting a stale number is as valuable as code. Adding an indicator is
341
+ usually about ten lines in `src/catalog.py`. See [CONTRIBUTING.md](CONTRIBUTING.md) and [ROADMAP.md](ROADMAP.md).
342
+
343
+ ## Licence and data attribution
344
+
345
+ Code: [MIT](LICENSE). Data remain subject to their providers' terms:
346
+ [World Bank](https://www.worldbank.org/en/about/legal/terms-of-use-for-datasets) (CC BY 4.0),
347
+ [IMF](https://www.imf.org/external/terms.htm), SIPRI, WHO, UNESCO and the other sources listed with each value.
348
+ Boundaries: [Natural Earth](https://www.naturalearthdata.com/) (public domain). Map tiles:
349
+ [© OpenStreetMap contributors](https://www.openstreetmap.org/copyright).
350
+
351
+ ## Author
352
+
353
+ Created and maintained by **Eng. Jaudat Faisal Al-Husein** · [github.com/Jaudat150](https://github.com/Jaudat150) ·
354
+ jaudatalhusein@gmail.com
@@ -0,0 +1,69 @@
1
+ """debt-atlas — sovereign debt, deficits, government spending and who-owes-whom data for every country.
2
+
3
+ Quick start::
4
+
5
+ import debt_atlas as da
6
+
7
+ da.indicators() # what is available
8
+ da.latest("gov_debt_gdp", countries=["Japan", "Italy"])
9
+ da.rank("military_usd", n=10) # biggest military spenders
10
+ da.series("inflation", "Argentina", start=2010) # history, per source
11
+ da.spending("United States") # where the money goes, in US$
12
+ da.who_owes("Kenya") # Kenya's lenders
13
+ da.owed_by("China") # who owes China
14
+ da.profile("Syria") # 3-tier national debt profile
15
+ da.download_all("atlas-data") # every file + datapackage.json
16
+
17
+ Every value carries its source, series code and year. Cite the original
18
+ providers and this project (see CITATION.cff).
19
+ """
20
+
21
+ from __future__ import annotations
22
+
23
+ from typing import Any
24
+
25
+ from debt_atlas._meta import REPO_URL, SITE_URL, __version__
26
+ from debt_atlas.client import Atlas, AtlasError
27
+
28
+ _default: Atlas | None = None
29
+
30
+
31
+ def default() -> Atlas:
32
+ """The shared client used by the module-level functions."""
33
+ global _default
34
+ if _default is None:
35
+ _default = Atlas()
36
+ return _default
37
+
38
+
39
+ def _delegate(name: str):
40
+ def call(*args: Any, **kwargs: Any) -> Any:
41
+ return getattr(default(), name)(*args, **kwargs)
42
+
43
+ call.__name__ = name
44
+ call.__doc__ = getattr(Atlas, name).__doc__
45
+ return call
46
+
47
+
48
+ indicators = _delegate("indicators")
49
+ sources = _delegate("sources")
50
+ countries = _delegate("countries")
51
+ latest = _delegate("latest")
52
+ rank = _delegate("rank")
53
+ series = _delegate("series")
54
+ wide = _delegate("wide")
55
+ compare = _delegate("compare")
56
+ spending = _delegate("spending")
57
+ profile = _delegate("profile")
58
+ bilateral = _delegate("bilateral")
59
+ who_owes = _delegate("who_owes")
60
+ owed_by = _delegate("owed_by")
61
+ download_all = _delegate("download_all")
62
+ live = _delegate("live")
63
+ resolve_country = _delegate("resolve_country")
64
+
65
+ __all__ = [
66
+ "Atlas", "AtlasError", "__version__", "REPO_URL", "SITE_URL", "default",
67
+ "indicators", "sources", "countries", "latest", "rank", "series", "wide", "compare", "spending",
68
+ "profile", "bilateral", "who_owes", "owed_by", "download_all", "live", "resolve_country",
69
+ ]
@@ -0,0 +1,9 @@
1
+ """Project-wide constants. Change REPO_SLUG once if the GitHub repository is renamed."""
2
+
3
+ __version__ = "0.1.0"
4
+
5
+ REPO_SLUG = "Jaudat150/global-debt-explorer"
6
+ BRANCH = "main"
7
+ RAW_BASE = f"https://raw.githubusercontent.com/{REPO_SLUG}/{BRANCH}/data/exports"
8
+ REPO_URL = f"https://github.com/{REPO_SLUG}"
9
+ SITE_URL = f"https://{REPO_SLUG.split('/')[0].lower()}.github.io/{REPO_SLUG.split('/')[1]}/"
@@ -0,0 +1,63 @@
1
+ """Command line: ``debt-atlas latest gov_debt_gdp --countries Japan USA``."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import argparse
6
+ import sys
7
+
8
+ import pandas as pd
9
+
10
+ from debt_atlas import Atlas, AtlasError, __version__
11
+
12
+
13
+ def main(argv: list[str] | None = None) -> int:
14
+ parser = argparse.ArgumentParser(prog="debt-atlas", description="Global Debt & Fiscal Atlas data from the command line.")
15
+ parser.add_argument("--version", action="version", version=f"debt-atlas {__version__}")
16
+ parser.add_argument("--source", help="Base URL or folder of the export files")
17
+ parser.add_argument("--csv", action="store_true", help="Print CSV instead of a table")
18
+ sub = parser.add_subparsers(dest="command", required=True)
19
+ sub.add_parser("indicators", help="List indicators")
20
+ p = sub.add_parser("latest", help="Latest value per country"); p.add_argument("indicator"); p.add_argument("--countries", nargs="*")
21
+ p = sub.add_parser("rank", help="Top countries"); p.add_argument("indicator"); p.add_argument("-n", type=int, default=10); p.add_argument("--lowest", action="store_true")
22
+ p = sub.add_parser("series", help="History"); p.add_argument("indicator"); p.add_argument("countries", nargs="+"); p.add_argument("--start", type=int)
23
+ p = sub.add_parser("spending", help="Where the money goes (US$)"); p.add_argument("country")
24
+ p = sub.add_parser("who-owes", help="A country's lenders"); p.add_argument("country")
25
+ p = sub.add_parser("owed-by", help="Who owes a country"); p.add_argument("country")
26
+ p = sub.add_parser("profile", help="National debt profile"); p.add_argument("country")
27
+ p = sub.add_parser("download", help="Save every file"); p.add_argument("folder")
28
+ args = parser.parse_args(argv)
29
+
30
+ atlas = Atlas(args.source) if args.source else Atlas()
31
+ try:
32
+ if args.command == "indicators":
33
+ result = atlas.indicators()[["id", "category", "unit", "name"]]
34
+ elif args.command == "latest":
35
+ result = atlas.latest(args.indicator, args.countries)[["iso3", "country", "value", "year", "is_estimate", "source"]]
36
+ elif args.command == "rank":
37
+ result = atlas.rank(args.indicator, args.n, lowest=args.lowest)[["iso3", "country", "value", "year", "source"]]
38
+ elif args.command == "series":
39
+ result = atlas.series(args.indicator, args.countries, start=args.start)[["iso3", "year", "value", "series_code"]]
40
+ elif args.command == "spending":
41
+ result = atlas.spending(args.country)[["item", "usd", "pct_gdp", "usd_per_person", "year", "scope"]]
42
+ elif args.command == "who-owes":
43
+ result = atlas.who_owes(args.country)
44
+ elif args.command == "owed-by":
45
+ result = atlas.owed_by(args.country)
46
+ elif args.command == "profile":
47
+ result = pd.Series(atlas.profile(args.country)).to_frame("value")
48
+ else:
49
+ print(f"Saved to {atlas.download_all(args.folder)}")
50
+ return 0
51
+ except AtlasError as exc:
52
+ print(f"error: {exc}", file=sys.stderr)
53
+ return 2
54
+ if args.csv:
55
+ result.to_csv(sys.stdout, index=False)
56
+ else:
57
+ with pd.option_context("display.max_rows", 300, "display.width", 160, "display.max_colwidth", 60):
58
+ print(result.to_string(index=args.command == "profile"))
59
+ return 0
60
+
61
+
62
+ if __name__ == "__main__":
63
+ raise SystemExit(main())