"""QA-only fixture API mirroring src/countryatlas/api/schemas.py shapes. Serves deterministic SYNTHETIC values (seeded per country×indicator) from the registries so the web app can be rendered and screenshotted before the real snapshot exists. Every provenance object says "Fixture (mock data)". NEVER point production at this. Run: cd && .venv/bin/python apps/web/qa/mock_api.py (port 8299) """ from __future__ import annotations import math import random from datetime import datetime, timezone from pathlib import Path from typing import Any import yaml from fastapi import FastAPI, Query from fastapi.responses import JSONResponse ROOT = Path(__file__).resolve().parents[3] REG = ROOT / "registry" countries = [c for c in yaml.safe_load((REG / "countries.yaml").read_text())["countries"] if c.get("kind", "country") != "aggregate"] indicators = yaml.safe_load((REG / "indicators.yaml").read_text())["indicators"] topics = yaml.safe_load((REG / "topics.yaml").read_text()) groups = yaml.safe_load((REG / "groups.yaml").read_text())["groups"] BY_ID = {c["id"]: c for c in countries} BY_SLUG = {c["slug"]: c for c in countries} IND = {i["slug"]: i for i in indicators} BUILT = "2026-09-11T03:20:11Z" META = {"built_at": BUILT, "run_id": "fixture-20260911", "generated_at": datetime.now(timezone.utc).isoformat(timespec="seconds").replace("+00:00", "Z")} app = FastAPI(title="CountryAtlas fixture API") BASELINE = { # rough scale per format so numbers look plausible "currency": (2e9, 2e13), "percent": (1, 90), "number": (1e5, 5e7), "years": (52, 84), "index": (60, 140), "per_1000": (2, 40), "per_100k": (0.5, 40), "per_million": (50, 8000), "ratio": (0.5, 12), "celsius": (0.001, 0.2), "tonnes": (0.1, 25), "kwh": (200, 15000), "ha": (0.05, 3), "km": (500, 250000), } SIZE = {"HIC": 1.0, "UMC": 0.55, "LMC": 0.3, "LIC": 0.15} def rng(*keys: Any) -> random.Random: return random.Random("|".join(map(str, keys))) def has_data(c: dict, ind: dict) -> bool: return rng(c["id"], ind["slug"], "has").random() < (0.92 if c.get("status") == "country" else 0.55) def series(c: dict, ind: dict) -> list[dict]: r = rng(c["id"], ind["slug"]) lo, hi = BASELINE.get(ind.get("format", "number"), (1, 100)) size = SIZE.get(c.get("income_group") or "LMC", 0.3) fmt = ind.get("format") if fmt == "currency" and "per-capita" not in ind["slug"]: base = lo * (1 + 400 * size * r.random()) * (c.get("area_km2") or 1e5) ** 0.25 / 20 elif fmt == "currency": base = 1500 + 90000 * size * r.random() elif fmt == "number": base = lo * (1 + 200 * r.random()) * (c.get("area_km2") or 1e5) ** 0.3 / 30 else: base = lo + (hi - lo) * (0.2 + 0.7 * r.random()) * (0.6 + 0.6 * size if fmt in ("years", "kwh") else 1) start = 1990 if r.random() < 0.7 else 2000 end = 2024 if r.random() < 0.85 else 2023 trend = r.uniform(-0.01, 0.035) if fmt not in ("percent", "years") else r.uniform(-0.2, 0.3) out = [] v = base for y in range(start, end + 1): noise = r.gauss(0, 0.03 if fmt != "percent" else 0.5) v = v * (1 + trend + noise) if fmt not in ("percent", "years", "celsius") else v + trend + noise v = max(0.001, v) if fmt != "percent" or "balance" not in ind["slug"] else v out.append({"period": f"{y}-01-01", "year": y, "frequency": "A", "value": round(v, 3), "is_forecast": False, "is_estimate": y >= 2023, "status": "verified", "source_id": "worldbank", "provenance": prov(ind, c)}) if ind["slug"] in ("gdp", "gdp-per-capita", "gdp-growth", "inflation", "unemployment-rate", "general-government-gross-debt-pct-gdp"): for y in range(end + 1, end + 4): v = v * (1 + trend) if fmt not in ("percent",) else v + trend out.append({"period": f"{y}-01-01", "year": y, "frequency": "A", "value": round(v, 3), "is_forecast": True, "is_estimate": False, "status": "imported", "source_id": "imf", "provenance": prov(ind, c, "imf")}) return out def prov(ind: dict, c: dict | None = None, src: str = "worldbank") -> dict: s = next((x for x in ind.get("sources", []) if x.get("connector") == src), (ind.get("sources") or [{}])[0]) return {"source": src, "source_name": "Fixture (mock data)", "dataset": s.get("dataset", "WDI"), "series_code": s.get("code", ind["slug"].upper()), "retrieved_at": BUILT, "source_updated_at": "2026-07-01", "url": f"https://data.worldbank.org/indicator/{s.get('code', '')}" + (f"?locations={c['iso2']}" if c else ""), "transform": s.get("transform"), "licence": "CC BY 4.0"} def fmt_value(v: float | None, ind: dict) -> str: if v is None: return "—" f = ind.get("format", "number") if f == "currency": for d, s in ((1e12, "T"), (1e9, "B"), (1e6, "M"), (1e3, "k")): if abs(v) >= d: return f"US${v / d:.1f}{s}" return f"US${v:,.0f}" if f == "percent": return f"{v:.1f} %" if f == "years": return f"{v:.1f} yrs" if f == "number": for d, s in ((1e9, "B"), (1e6, "M")): if abs(v) >= d: return f"{v / d:.2f}{s}" return f"{v:,.0f}" return f"{v:,.{ind.get('precision', 1)}f}" def card(c: dict) -> dict: return {"id": c["id"], "iso2": c.get("iso2"), "slug": c["slug"], "name": c["short_name"], "flag": c.get("flag_emoji"), "region": c.get("region_wb"), "region_name": c.get("region_wb_name"), "income": c.get("income_group"), "income_name": c.get("income_group_name"), "kind": c.get("kind", "country")} def icard(ind: dict) -> dict: return {"id": ind["slug"], "slug": ind["slug"], "name": ind["name"], "short_name": ind.get("short_name", ind["name"]), "topic": ind.get("topic"), "subtopic": ind.get("subtopic"), "unit": ind.get("unit"), "unit_short": ind.get("unit_short"), "format": ind.get("format", "number"), "precision": ind.get("precision", 1), "frequency": ind.get("frequency", "A"), "aggregation": ind.get("aggregation"), "higher_is_better": ind.get("higher_is_better"), "ranking_eligible": ind.get("ranking_eligible", True), "featured": ind.get("featured", False)} _latest_cache: dict[str, dict[str, dict]] = {} def latest_all(ind_slug: str) -> dict[str, dict]: """country id → last actual value dict for an indicator (with ranks).""" if ind_slug in _latest_cache: return _latest_cache[ind_slug] ind = IND[ind_slug] rows = {} for c in countries: if not has_data(c, ind): continue s = [p for p in series(c, ind) if not p["is_forecast"]] if len(s) < 2: continue rows[c["id"]] = {"last": s[-1], "prev": s[-2], "ten": s[-11] if len(s) > 11 else None, "spark": [[p["year"], p["value"]] for p in s[-30:]]} ranked = sorted(rows.items(), key=lambda kv: -(kv[1]["last"]["value"])) if ind.get("higher_is_better") is False: ranked.reverse() for i, (cid, r) in enumerate(ranked): r["rank_world"] = i + 1 r["n_world"] = len(ranked) region = BY_ID[cid].get("region_wb") peers = [x for x, _ in ranked if BY_ID[x].get("region_wb") == region] r["rank_region"] = peers.index(cid) + 1 r["n_region"] = len(peers) _latest_cache[ind_slug] = rows return rows def metric(c: dict, ind: dict) -> dict: row = latest_all(ind["slug"]).get(c["id"]) if not row: return {"indicator": ind["slug"], "indicator_name": ind.get("short_name", ind["name"]), "has_data": False, "value": None, "formatted": "—", "unit": ind.get("unit"), "unit_short": ind.get("unit_short"), "format": ind.get("format", "number"), "higher_is_better": ind.get("higher_is_better"), "sparkline": [], "provenance": None, "period": None, "year": None, "frequency": None, "is_estimate": False, "is_forecast": False, "status": None, "prev": None, "change": None, "change_10y": None, "rank_world": None, "n_world": None, "rank_region": None, "n_region": None, "rank_income": None, "n_income": None, "rank_year": None, "rank_is_stale": False} last, prev = row["last"], row["prev"] ch_abs = last["value"] - prev["value"] ch_pct = ch_abs / prev["value"] * 100 if prev["value"] else None pts = ind.get("format") in ("percent", "index", "ratio", "years") return { "indicator": ind["slug"], "indicator_name": ind.get("short_name", ind["name"]), "has_data": True, "value": last["value"], "formatted": fmt_value(last["value"], ind), "period": last["period"], "year": last["year"], "frequency": "A", "unit": ind.get("unit"), "unit_short": ind.get("unit_short"), "format": ind.get("format", "number"), "is_estimate": last["is_estimate"], "is_forecast": False, "status": "verified", "prev": {"period": prev["period"], "value": prev["value"]}, "change": {"abs": round(ch_abs, 3), "pct": round(ch_pct, 2) if ch_pct is not None else None, "formatted": (f"{ch_abs:+.1f} pts" if pts else f"{ch_pct:+.1f} %") if ch_pct is not None else None}, "change_10y": None, "rank_world": row["rank_world"], "n_world": row["n_world"], "rank_region": row["rank_region"], "n_region": row["n_region"], "rank_income": None, "n_income": None, "rank_year": last["year"], "rank_is_stale": last["year"] < 2022, "higher_is_better": ind.get("higher_is_better"), "sparkline": row["spark"], "provenance": prov(ind, c), } def problem(status: int, title: str, detail: str) -> JSONResponse: return JSONResponse({"type": "about:blank", "title": title, "status": status, "detail": detail}, status_code=status, media_type="application/problem+json") def resolve(ident: str) -> dict | None: return BY_ID.get(ident.upper()) or BY_SLUG.get(ident.lower()) @app.get("/api/v1/health") def health(): return {"status": "ok", "run_id": META["run_id"], "built_at": BUILT, "observations": 3_412_338, "countries": len(countries), "indicators": len(indicators), "db_path": "fixture", "version": "0.0-fixture", "cache": {}} @app.get("/api/v1/countries") def list_countries(region: str | None = None, income: str | None = None, q: str | None = None, sort: str = "name", limit: int = 1000, offset: int = 0): items = [] for c in countries: if region and (c.get("region_wb") or "").lower() != region.lower() and c.get("region_wb", "").lower() != region.lower(): continue if income and (c.get("income_group") or "").upper() != income.upper(): continue if q and q.lower() not in c["short_name"].lower(): continue pop = latest_all("population").get(c["id"]) gdp = latest_all("gdp").get(c["id"]) gpc = latest_all("gdp-per-capita").get(c["id"]) item = card(c) item.update({"capital": c.get("capital"), "continent": c.get("continent"), "subregion": c.get("subregion"), "population_latest": pop["last"]["value"] if pop else None, "population_year": pop["last"]["year"] if pop else None, "gdp_latest": gdp["last"]["value"] if gdp else None, "gdp_year": gdp["last"]["year"] if gdp else None, "gdp_per_capita_latest": gpc["last"]["value"] if gpc else None, "gdp_per_capita_year": gpc["last"]["year"] if gpc else None, "coverage_pct": round(100 * sum(1 for i in indicators if has_data(c, i)) / len(indicators), 1), "n_indicators": sum(1 for i in indicators if has_data(c, i)), "iso_numeric": c.get("iso_numeric")}) items.append(item) return {"meta": META, "n": len(items), "filters": {"region": region, "income": income, "q": q, "sort": sort}, "items": items[offset: offset + limit]} @app.get("/api/v1/countries/{ident}") def get_country(ident: str): c = resolve(ident) if not c: return problem(404, "Country not found", f"Unknown country '{ident}'.") full = card(c) for k in ("official_name", "iso3", "iso_numeric", "capital", "continent", "subregion", "currency_code", "currency_name", "area_km2", "latitude", "longitude", "un_member", "independent", "landlocked", "borders", "languages", "demonym", "status"): full[k] = c.get(k) with_data = {i["slug"] for i in indicators if has_data(c, i)} tps = [{"id": tp["id"], "name": tp["name"], "short": tp.get("short"), "order": tp.get("order"), "blurb": tp.get("blurb"), "n_indicators": len(tp["indicators"]), "n_with_data": sum(1 for s in tp["indicators"] if s in with_data)} for tp in sorted(topics["topics"], key=lambda x: x["order"])] return {"meta": META, "country": full, "groups": [{"id": g["id"], "slug": g["slug"], "name": g["name"], "kind": g["kind"], "wb_code": g.get("wb_code"), "n_members": None} for g in groups[:4]], "coverage": {"n_indicators": len(with_data), "n_observations": 34 * len(with_data), "latest_year": 2024, "coverage_pct": round(100 * len(with_data) / len(indicators), 1), "updated_at": BUILT}, "freshness": {"source_updated_at": "2026-07-01", "retrieved_at": BUILT, "built_at": BUILT}, "headline": [metric(c, IND[s]) for s in topics["headline"] if s in IND], "topics": tps, "neighbours": [card(BY_ID[b]) for b in (c.get("borders") or []) if b in BY_ID]} @app.get("/api/v1/countries/{ident}/topics/{topic}") def get_topic(ident: str, topic: str): c = resolve(ident) tp = next((x for x in topics["topics"] if x["id"] == topic), None) if not c: return problem(404, "Country not found", f"Unknown country '{ident}'.") if not tp: return problem(404, "Topic not found", f"Unknown topic '{topic}'.") blocks: dict[str, list] = {} n_with = 0 for s in tp["indicators"]: ind = IND.get(s) if not ind: continue m = metric(c, ind) n_with += m["has_data"] blocks.setdefault(ind.get("subtopic") or "Other", []).append(m) return {"meta": META, "country": card(c), "topic": {k: tp.get(k) for k in ("id", "name", "short", "order", "blurb")}, "n_with_data": n_with, "n_indicators": sum(len(v) for v in blocks.values()), "subtopics": [{"subtopic": k, "indicators": v} for k, v in blocks.items()]} @app.get("/api/v1/countries/{ident}/series/{slug}") def get_series(ident: str, slug: str): c = resolve(ident) ind = IND.get(slug) if not c or not ind: return problem(404, "Not found", "Unknown country or indicator.") vals = series(c, ind) if has_data(c, ind) else [] act = [v for v in vals if not v["is_forecast"]] stats = {"min": None, "max": None, "first": None, "last": None, "cagr": None, "n": len(act)} if act: mn = min(act, key=lambda v: v["value"]); mx = max(act, key=lambda v: v["value"]) stats.update({"min": {"year": mn["year"], "value": mn["value"]}, "max": {"year": mx["year"], "value": mx["value"]}, "first": {"year": act[0]["year"], "value": act[0]["value"]}, "last": {"year": act[-1]["year"], "value": act[-1]["value"]}}) return {"meta": META, "indicator": icard(ind), "country": card(c), "unit": ind.get("unit"), "frequency": "A", "values": vals, "alternatives": None, "provenance": prov(ind, c) if vals else None, "sources": [dict(prov(ind, c), n_values=len(act))] if vals else [], "stats": stats} KINDS = ["yoy_jump", "yoy_drop", "record_high", "record_low", "n_year_high", "sign_flip", "accelerating"] def change_items(c: dict, n: int, whole: bool = False) -> list[dict]: r = rng(c["id"], "changes", whole) items = [] slugs = [s for s in IND if has_data(c, IND[s])] r.shuffle(slugs) for s in slugs[:n]: ind = IND[s] row = latest_all(s).get(c["id"]) if not row: continue kind = r.choice(KINDS) year = row["last"]["year"] if not whole else r.randint(1995, 2024) v = row["last"]["value"]; ref = row["prev"]["value"] d = v - ref sev = round(r.uniform(0.2, 1.0), 2) head = {"yoy_jump": f"{ind.get('short_name', ind['name'])} jumped to {fmt_value(v, ind)} in {year}", "yoy_drop": f"{ind.get('short_name', ind['name'])} fell to {fmt_value(v, ind)} in {year}", "record_high": f"{ind.get('short_name', ind['name'])} reached a record high of {fmt_value(v, ind)} in {year}", "record_low": f"{ind.get('short_name', ind['name'])} hit a record low of {fmt_value(v, ind)} in {year}", "n_year_high": f"{ind.get('short_name', ind['name'])} at a 10-year high ({fmt_value(v, ind)}) in {year}", "sign_flip": f"{ind.get('short_name', ind['name'])} turned {'positive' if v > 0 else 'negative'} in {year}", "accelerating": f"{ind.get('short_name', ind['name'])} accelerating for three years running ({year})"}[kind] items.append({"id": f"{c['id']}-{s}-{year}", "country": card(c), "indicator": icard(ind), "kind": kind, "period": f"{year}-01-01", "year": year, "value": v, "ref_value": ref, "delta": round(d, 3), "delta_pct": round(d / ref * 100, 2) if ref else None, "window_years": 10 if kind.startswith("n_year") else None, "severity": sev, "headline": head, "detail": None, "detected_at": BUILT, "formatted": fmt_value(v, ind), "provenance": prov(ind, c)}) items.sort(key=lambda x: (-x["severity"], x["year"]) if not whole else (-x["year"], -x["severity"])) return items @app.get("/api/v1/countries/{ident}/changes") def get_changes(ident: str, limit: int = 50): c = resolve(ident) if not c: return problem(404, "Country not found", ident) items = change_items(c, min(limit, 10)) return {"meta": META, "n": len(items), "items": items} @app.get("/api/v1/countries/{ident}/events") def get_events(ident: str, limit: int = 100): c = resolve(ident) if not c: return problem(404, "Country not found", ident) items = change_items(c, min(limit, 24), whole=True) return {"meta": META, "n": len(items), "items": items} @app.get("/api/v1/countries/{ident}/similar") def get_similar(ident: str, mode: str = "overall", limit: int = 12): c = resolve(ident) if not c: return problem(404, "Country not found", ident) r = rng(c["id"], "similar", mode) pool = [x for x in countries if x["id"] != c["id"] and x.get("income_group") == c.get("income_group")] or countries r.shuffle(pool) feats = ["gdp-per-capita-ppp", "median-age", "urban-population-share", "trade-pct-gdp", "energy-use-per-capita", "co2-per-capita", "life-expectancy", "unemployment-rate"] peers = [] for i, p in enumerate(pool[:limit]): contrib = {f: {"z_a": round(r.gauss(0, 1), 2), "z_b": round(r.gauss(0, 1), 2), "weight": 1.0, "contribution": round(r.uniform(0, 0.4), 3)} for f in feats} peers.append({"country": card(p), "score": round(92 - i * 4.3 - r.random() * 2, 1), "rank": i + 1, "contributions": contrib}) return {"meta": META, "country": card(c), "mode": mode, "modes": ["overall", "economic", "demographic", "energy", "social"], "peers": peers} @app.get("/api/v1/countries/{ident}/insights") def get_insights(ident: str): c = resolve(ident) if not c: return problem(404, "Country not found", ident) items = [] for s, tmpl in (("population", "{n}'s population grew {pct} % since 1990."), ("gdp-per-capita", "GDP per capita in {n} is {v}, ranking {rank} of {nw}."), ("life-expectancy", "Life expectancy in {n} reached {v} in {y}."), ("renewable-electricity-share", "{v} of {n}'s electricity comes from renewables."), ("co2-per-capita", "{n} emits {v} of CO₂ per person, {cmp} the world median.")): row = latest_all(s).get(c["id"]) if not row: continue ind = IND[s] v = fmt_value(row["last"]["value"], ind) items.append({"id": f"{c['id']}-{s}", "template_id": s, "text": tmpl.format(n=c["short_name"], v=v, y=row["last"]["year"], pct=f"{rng(c['id'], s).uniform(3, 60):.0f}", rank=row["rank_world"], nw=row["n_world"], cmp=rng(c['id'], s, 'c').choice(["above", "below"])), "values": {"value": row["last"]["value"]}, "indicators": [s], "computed_at": BUILT, "provenance": [prov(ind, c)]}) return {"meta": META, "country": card(c), "items": items} @app.get("/api/v1/countries/{ident}/dna") def get_dna(ident: str): c = resolve(ident) if not c: return problem(404, "Country not found", ident) r = rng(c["id"], "dna") keys = ["income", "demographics", "urbanization", "trade", "energy", "emissions", "innovation", "education", "public_spending"] dims = {k: (round(r.uniform(5, 98), 1) if r.random() > 0.08 else None) for k in keys} return {"meta": META, "country": card(c), "dims": dims, "year_ref": 2024, "dimensions": [{"id": k, "label": k.title(), "indicator": None, "value": v} for k, v in dims.items()]} @app.get("/api/v1/indicators/{slug}/map") def get_map(slug: str, year: int | None = None, nearest: bool = False): ind = IND.get(slug) if not ind: return problem(404, "Indicator not found", slug) rows = latest_all(slug) values = {cid: r["last"]["value"] for cid, r in rows.items() if BY_ID[cid].get("status") == "country"} vals = sorted(values.values()) k = 6 breaks = [vals[int(i / k * (len(vals) - 1))] for i in range(1, k)] if len(vals) > 10 else [] return {"meta": META, "indicator": icard(ind), "year": year, "year_used": 2024, "nearest": nearest, "values": values, "years": None, "formatted": {c: fmt_value(v, ind) for c, v in values.items()}, "legend": {"min": vals[0] if vals else None, "max": vals[-1] if vals else None, "breaks": breaks, "n_classes": len(breaks) + 1}, "n": len(values), "provenance": prov(ind), "sources": [prov(ind)]} def ranking_rows(slug: str, n: int, order: str = "desc", min_pop: float | None = None) -> list[dict]: ind = IND[slug] rows = [(cid, r) for cid, r in latest_all(slug).items() if BY_ID[cid].get("status") == "country" and (not min_pop or (latest_all("population").get(cid, {}).get("last", {}).get("value", 0) >= min_pop))] rows.sort(key=lambda kv: kv[1]["last"]["value"], reverse=(order == "desc")) out = [] for i, (cid, r) in enumerate(rows[:n]): v = r["last"]["value"]; pv = r["prev"]["value"] out.append({"rank": i + 1, "rank_world": r["rank_world"], "n_world": r["n_world"], "pct_rank": None, "country": card(BY_ID[cid]), "value": v, "formatted": fmt_value(v, ind), "year": r["last"]["year"], "change_pct": round((v - pv) / pv * 100, 2) if pv else None, "change_abs": round(v - pv, 3), "change_1y": {"abs": round(v - pv, 3), "pct": round((v - pv) / pv * 100, 2) if pv else None, "formatted": None}, "change_10y": None, "sparkline": r["spark"], "provenance": prov(ind, BY_ID[cid])}) return out @app.get("/api/v1/rankings/{slug}") def get_ranking(slug: str, limit: int = 50, sort: str = "desc", offset: int = 0): ind = IND.get(slug) if not ind: return problem(404, "Indicator not found", slug) rows = ranking_rows(slug, limit + offset, sort)[offset:] return {"meta": META, "indicator": icard(ind), "group": {"id": "world", "slug": "world", "name": "World", "kind": "world", "wb_code": "WLD", "n_members": len(countries)}, "year": None, "year_used": 2024, "years_available": list(range(1990, 2025)), "sort": sort, "n": len(latest_all(slug)), "limit": limit, "offset": offset, "rows": rows} def ind_summary(ind: dict) -> dict: n = sum(1 for c in countries if has_data(c, ind)) out = icard(ind) out.update({"description": ind.get("description"), "n_countries": n, "n_observations": n * 34, "first_year": 1990, "last_year": 2024, "latest_source_updated_at": "2026-07-01", "primary_source_id": "worldbank", "coverage_pct": round(100 * n / len(countries), 1), "tags": []}) return out @app.get("/api/v1/home") def home(): lists = {} for key, title, slug, order, min_pop, desc in (("largest_economies", "Largest economies", "gdp", "desc", None, "GDP, current US$"), ("fastest_population_growth", "Fastest population growth", "population-growth", "desc", 1e6, "Countries above 1 M inhabitants"), ("highest_life_expectancy", "Highest life expectancy", "life-expectancy", "desc", None, "Years at birth"), ("energy_transition_leaders", "Energy transition leaders", "renewable-electricity-share", "desc", None, "Share of electricity from renewables")): lists[key] = {"title": title, "description": desc, "indicator": icard(IND[slug]), "sort": order, "rows": ranking_rows(slug, 8, order, min_pop)} pop = sum(r["last"]["value"] for cid, r in latest_all("population").items() if BY_ID[cid].get("status") == "country") gdp = sum(r["last"]["value"] for cid, r in latest_all("gdp").items() if BY_ID[cid].get("status") == "country") les = sorted(r["last"]["value"] for r in latest_all("life-expectancy").values()) changes = [] for c in rng("home").sample(countries, 12): changes += change_items(c, 1) changes.sort(key=lambda x: -x["severity"]) featured = [ind_summary(i) for i in indicators if i.get("featured")] return {"meta": META, "snapshot": {"world_population": pop, "world_population_formatted": fmt_value(pop, {"format": "number"}), "world_population_year": 2024, "world_gdp": gdp, "world_gdp_formatted": fmt_value(gdp, {"format": "currency"}), "world_gdp_year": 2024, "median_life_expectancy": les[len(les) // 2] if les else None, "median_life_expectancy_year": 2024, "n_countries": sum(1 for c in countries if c.get("status") == "country"), "n_territories": sum(1 for c in countries if c.get("status") != "country"), "n_indicators": len(indicators), "n_indicators_with_data": len(indicators) - 3, "n_observations": 3_412_338, "n_sources": 9, "built_at": BUILT, "run_id": META["run_id"], "note": "Fixture data."}, "lists": lists, "recent_changes": changes[:12], "recently_updated": [ind_summary(i) for i in indicators[:12]], "featured_indicators": featured, "trending": featured[:12]} @app.get("/api/v1/search") def search(q: str = Query(""), limit: int = 12): ql = q.lower().strip() hits = [] for c in countries: if ql and (ql in c["short_name"].lower() or ql == c["id"].lower()): hits.append({"type": "country", "id": c["id"], "slug": c["slug"], "name": c["short_name"], "hint": f"Country · {c.get('region_wb_name')}", "score": 1.0 if c["short_name"].lower().startswith(ql) else 0.8, "url": f"/countries/{c['slug']}", "country": card(c), "topic": None, "indicator": None}) for i in indicators: if ql and ql in i["name"].lower(): hits.append({"type": "indicator", "id": i["slug"], "slug": i["slug"], "name": i["name"], "hint": f"Indicator · {i['topic'].capitalize()} · {i.get('unit')}", "score": 0.7, "url": f"/indicators/{i['slug']}", "country": None, "topic": None, "indicator": None}) for tp in topics["topics"]: if ql and ql in tp["name"].lower(): hits.append({"type": "topic", "id": tp["id"], "slug": tp["id"], "name": tp["name"], "hint": "Topic", "score": 0.6, "url": f"/indicators?topic={tp['id']}", "country": None, "topic": tp["id"], "indicator": None}) hits.sort(key=lambda h: -h["score"]) return {"meta": META, "q": q, "n": len(hits), "hits": hits[:limit]} @app.get("/api/v1/countries/{ident}/download.csv") def download(ident: str): from fastapi.responses import PlainTextResponse return PlainTextResponse("country_id,indicator_id,period,value\n", media_type="text/csv") if __name__ == "__main__": import uvicorn uvicorn.run(app, host="127.0.0.1", port=8299, log_level="warning")