Captures backend work already running on godstrument.pro but never committed: - auth.py: invite-only accounts + per-user presets (SQLite, scrypt, signed stateless sessions), mounted by hub.py at /api/* - hub.py: /api static+API handler, readonly public mode, full route spec in hello - socio-economic / market / fire / debt / crypto workers + normalize/transform - .gitignore: never commit godstrument_users.db* or auth_secret - test_fixes.py: framework-free self-check for the review fixes Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
87 lines
3.2 KiB
Python
87 lines
3.2 KiB
Python
"""
|
|
world_bank.py — the human condition, slow and heavy.
|
|
|
|
Pulls World Bank development indicators (no key) for the whole world: extreme
|
|
poverty, undernourishment (food insecurity), fertility, life expectancy. These
|
|
are annual figures — they barely move — so they're near-DC ballast, a moral
|
|
weight under the set rather than a rhythm. (The API is annual and occasionally
|
|
throttles rapid requests, so this polls slowly and holds the last value.)
|
|
|
|
Emits:
|
|
/gs/world/poverty extreme poverty headcount, % of population
|
|
/gs/world/hunger prevalence of undernourishment, %
|
|
/gs/world/fertility births per woman
|
|
/gs/world/lifeexp life expectancy at birth, years
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
import argparse
|
|
import json
|
|
import time
|
|
import urllib.request
|
|
|
|
from pythonosc.udp_client import SimpleUDPClient
|
|
|
|
BASE = "https://api.worldbank.org/v2/country/WLD/indicator/{ind}?format=json&mrnev=1"
|
|
UA = "Godstrument/1.0 (live instrument)"
|
|
|
|
INDICATORS = {
|
|
"SI.POV.DDAY": ("world/poverty", "extreme poverty %"),
|
|
"SN.ITK.DEFC.ZS": ("world/hunger", "undernourishment %"),
|
|
"SP.DYN.TFRT.IN": ("world/fertility", "fertility"),
|
|
"SP.DYN.LE00.IN": ("world/lifeexp", "life expectancy"),
|
|
}
|
|
|
|
|
|
def fetch(ind: str):
|
|
req = urllib.request.Request(BASE.format(ind=ind), headers={"User-Agent": UA})
|
|
with urllib.request.urlopen(req, timeout=30) as r:
|
|
raw = r.read().decode("utf-8-sig").strip() # World Bank prepends a BOM
|
|
d = json.loads(raw)
|
|
if isinstance(d, list) and len(d) > 1 and d[1]:
|
|
row = d[1][0]
|
|
if row.get("value") is not None:
|
|
return float(row["value"]), row["date"]
|
|
return None, None
|
|
|
|
|
|
def main():
|
|
ap = argparse.ArgumentParser()
|
|
ap.add_argument("--host", default="127.0.0.1")
|
|
ap.add_argument("--port", type=int, default=9000)
|
|
ap.add_argument("--poll", type=float, default=21600.0, help="re-query every Ns")
|
|
args = ap.parse_args()
|
|
|
|
client = SimpleUDPClient(args.host, args.port)
|
|
print("[worldbank] emitting /gs/world/poverty|hunger|fertility|lifeexp "
|
|
f"(re-query every {args.poll:g}s)")
|
|
|
|
values: dict[str, float] = {}
|
|
next_poll, retry = 0.0, 60.0
|
|
while True:
|
|
if time.time() >= next_poll:
|
|
got = False
|
|
for ind, (addr, name) in INDICATORS.items():
|
|
try:
|
|
v, year = fetch(ind)
|
|
if v is not None:
|
|
values[addr] = v
|
|
got = True
|
|
print(f" 🌍 {name}: {v} ({year})")
|
|
except Exception as e: # noqa
|
|
print(f"[worldbank] {ind} warn: {e}")
|
|
time.sleep(1.0) # be gentle with the API
|
|
if got:
|
|
next_poll, retry = time.time() + args.poll, 60.0
|
|
else: # nothing came back — back off
|
|
print(f"[worldbank] no values; retry in {retry:.0f}s")
|
|
next_poll = time.time() + retry
|
|
retry = min(retry * 2, args.poll)
|
|
for addr, v in values.items():
|
|
client.send_message("/gs/" + addr, v)
|
|
time.sleep(10.0)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|