ASCEND / refresh_data.py
mmrech's picture
v2.3.0: UN Data Commons cross-check extended to every indicator (spending, OOP share, life expectancy, population, derived SAO; four documented gaps), report in docs/
6055ba8 verified
Raw History Blame Contribute Delete
29.8 kB
#!/usr/bin/env python3
"""Reproducible public-data snapshot for ASCEND (all World Bank economies); no synthetic fallback.
Usage:
python3 refresh_data.py # rebuild data.json from live public APIs
python3 refresh_data.py --validate # re-validate the committed data.json
Optional: if geo/access_summary.json exists (produced by access_pipeline.py), its
modelled geographic-access indicators are merged as a separate, clearly labelled group.
"""
from pathlib import Path
import argparse, concurrent.futures, datetime, gzip, html, json, math, re, time
import urllib.request, urllib.parse
BASE = Path(__file__).resolve().parent
DEST = BASE / 'data.json'
RAW = BASE / 'data_raw'
ACCESS = BASE / 'geo' / 'access_summary.json'
YEARS = (2000, 2024)
VERSION = '2.3.0'
WB_NOTE = 'World Bank open data (CC BY 4.0); indicator attribution retained. Check dataset terms before redistribution.'
WHO_NOTE = 'WHO Global Health Observatory open data (CC BY-NC-SA 3.0 IGO); attribution retained.'
EPMC_NOTE = 'Europe PMC bibliographic service. Article and abstract rights remain with their respective holders; linked records retain identifiers and attribution.'
CT_NOTE = 'ClinicalTrials.gov public registry data; sponsor submissions are not independent verification of efficacy. See ClinicalTrials.gov terms and conditions.'
GOV_NOTE = 'Official Brazilian Ministry of Health public API. Connector proof only; schema field names retained, no individual patient records distributed.'
# key -> definition. `source` is 'worldbank' or 'who'. `scale` guides chart axes. `target` is the
# Lancet Commission on Global Surgery 2030 target where one exists (Meara et al., Lancet 2015).
INDICATORS = {
'spending': {'id':'SH.XPD.CHEX.PC.CD','source':'worldbank','label':'Current health expenditure per capita','short':'Health spending / person','unit':'current US$','decimals':0,'scale':'log','better':'higher'},
'spending_ppp': {'id':'SH.XPD.CHEX.PP.CD','source':'worldbank','label':'Current health expenditure per capita, PPP','short':'Health spending / person (PPP)','unit':'international $','decimals':0,'scale':'log','better':'higher'},
'physicians': {'id':'SH.MED.PHYS.ZS','source':'worldbank','label':'Physicians','short':'Physicians','unit':'per 1,000 people','decimals':2,'scale':'linear','better':'higher'},
'life': {'id':'SP.DYN.LE00.IN','source':'worldbank','label':'Life expectancy at birth','short':'Life expectancy','unit':'years','decimals':1,'scale':'linear','better':'higher'},
'oop': {'id':'SH.XPD.OOPC.CH.ZS','source':'worldbank','label':'Out-of-pocket expenditure','short':'Out-of-pocket share','unit':'% of current health expenditure','decimals':1,'scale':'linear','better':'lower'},
'population': {'id':'SP.POP.TOTL','source':'worldbank','label':'Population, total','short':'Population','unit':'people','decimals':0,'scale':'log','better':None},
'sao': {'id':'SH.MED.SAOP.P5','source':'worldbank','label':'Specialist surgical workforce','short':'Surgical workforce (SAO)','unit':'per 100,000 population','decimals':1,'scale':'linear','better':'higher','target':20,'targetLabel':'LCoGS 2030 target: at least 20 per 100,000'},
'volume': {'id':'SH.SGR.PROC.P5','source':'worldbank','label':'Number of surgical procedures','short':'Surgical volume','unit':'per 100,000 population','decimals':0,'scale':'linear','better':'higher','target':5000,'targetLabel':'LCoGS 2030 target: at least 5,000 per 100,000'},
'catastrophic': {'id':'SH.SGR.CRSK.ZS','source':'worldbank','label':'Risk of catastrophic expenditure for surgical care','short':'Catastrophic cost risk (surgery)','unit':'% of people at risk','decimals':1,'scale':'linear','better':'lower','target':0,'targetLabel':'LCoGS 2030 target: 100% protection (0% at risk)'},
'impoverishing':{'id':'SH.SGR.IRSK.ZS','source':'worldbank','label':'Risk of impoverishing expenditure for surgical care','short':'Impoverishing cost risk (surgery)','unit':'% of people at risk','decimals':1,'scale':'linear','better':'lower','target':0,'targetLabel':'LCoGS 2030 target: 100% protection (0% at risk)'},
'uhc': {'id':'UHC_INDEX_REPORTED','source':'who','label':'UHC service coverage index (SDG 3.8.1)','short':'UHC service coverage index','unit':'index 0–100','decimals':0,'scale':'linear','better':'higher'},
'cata10': {'id':'FINPROTECTION_CATA_TOT_10_POP','source':'who','dim1':'RESIDENCEAREATYPE_TOTL','label':'Population with household health spending above 10% of total consumption or income (SDG 3.8.2)','short':'Catastrophic health spending (>10%)','unit':'% of population','decimals':1,'scale':'linear','better':'lower'},
}
GROUPS = [
{'id':'context','label':'System context','indicators':['spending','spending_ppp','physicians','life','oop','population'],'note':'National averages; contextual observations, not measures of surgical access.'},
{'id':'surgical','label':'Surgical system (LCoGS indicators)','indicators':['sao','volume','catastrophic','impoverishing'],'note':'Four of the six Lancet Commission on Global Surgery indicators as compiled in World Development Indicators. Sparse and irregular reporting years; the latest value can be a decade old.'},
{'id':'coverage','label':'Coverage & financial protection','indicators':['uhc','cata10'],'note':'SDG 3.8 monitoring indicators from WHO and World Bank.'},
]
# UN System Data Commons (UNSD) counterparts used for source triangulation and live refresh. factor converts to ASCEND units.
UNDC_REST = 'https://unsd-datacommons.gcp.un-icc.cloud/core/api/v2'
UNDC_NOTE = 'UN System Data Commons (United Nations Statistics Division) knowledge graph; the underlying series retain their publishers\' terms (WHO, UN SDG Global Database). Retrieved via the public V2 observation endpoint.'
UNDC = {
'uhc': {'variable':'undata/sdg/SH_ACS_UNHC_25','factor':1,'unitNote':'index 0–100, SDG Global Database (2025 methodology)'},
'cata10': {'variable':'undata/who/FINP_CHE_SH.HE_THRESHOLD--T_10','factor':1,'unitNote':'% of population, WHO financial-protection series'},
'physicians': {'variable':'undata/sdg/SH_MED_DEN.OCCUPATION--ISCO08_221','factor':0.1,'unitNote':'SDG 3.c.1 medical doctors per 10,000, converted to per 1,000'},
'spending': {'variable':'undata/who/GHED_PER_CAP.HE_CATEGORY--CHE','factor':1,'unitNote':'WHO Global Health Expenditure Database, current health expenditure per capita, current US$'},
'oop': {'variable':'undata/who/GHED_SH.HE_CATEGORY--OOP_HE__REF_EXP_CAT--CHE','factor':1,'unitNote':'WHO GHED, out-of-pocket expenditure as % of current health expenditure'},
'life': {'variable':'undata/unicef/DM_LIFE_EXP','factor':1,'unitNote':'UNICEF data warehouse (UN WPP basis), both sexes; the series extends to the current WPP estimate year'},
'population': {'variable':'undata/unicef/DM_POP','factor':1,'unitNote':'UNICEF data warehouse (UN WPP basis), total population'},
# Derived cross-check only (no single Commons variable): WHO NHWA head-counts of surgeons, anaesthesiologists and
# obstetricians divided by World Bank population of the same year, per 100,000. One observation per economy, 2009–2015.
'sao': {'derived':True,'variables':['undata/who/HWF_CNT.HWF_TYPE--SURGEON','undata/who/HWF_CNT.HWF_TYPE--ANAESTH','undata/who/HWF_CNT.HWF_TYPE--OBSTETRICIAN'],'factor':1,'unitNote':'sum of WHO head-counts / World Bank population × 100,000; counts are single 2009–2015 observations'},
}
# Indicators searched in the Commons (MCP search_indicators anchored on Brazil and Kenya, 20 Oct 2026) with no counterpart.
UNDC_NONE = {
'spending_ppp':'GHED per-capita slice is published in current US$ only; no PPP series in this deployment.',
'volume':'No surgical-volume series; nearest hits are trachoma surgeries and caesarean-section shares, which are not comparable.',
'catastrophic':'No surgery-specific catastrophic-expenditure series; the Commons holds general catastrophic (FINP_CHE_SH) and impoverishing (FINP_IMP_SH) health-spending shares.',
'impoverishing':'No surgery-specific impoverishing-expenditure series; general FINP_IMP_SH poverty-line series exist but measure all health spending.',
}
ACCESS_GROUP = {'id':'access','label':'Geographic access (modelled)','indicators':['access2h','access1h','traveltime'],'note':'Population-weighted travel time to the nearest healthcare facility, computed by ASCEND at 1 km from the Malaria Atlas Project 2019 travel-time surfaces (motorised and walking-only) and WorldPop 2020 counts. Any facility, not Bellwether surgical capacity: a modelled upper bound on geographic surgical access.'}
def utcnow():
return datetime.datetime.now(datetime.timezone.utc).isoformat(timespec='seconds')
def fetch(job):
name,url,note = job
record = {'id':name,'name':name.replace('_',' ').title(),'url':url,'accessedAt':utcnow(),'licenseNote':note}
attempts=6 if 'ghoapi' in url else 4
for attempt in range(attempts):
try:
req = urllib.request.Request(url,headers={'User-Agent':'ASCEND-research-prototype/2.0','Accept':'application/json','Accept-Encoding':'gzip'})
with urllib.request.urlopen(req,timeout=90) as response:
body=response.read()
if body[:2] == b'\x1f\x8b': body=gzip.decompress(body)
payload=json.loads(body.decode('utf-8'))
record.update(status='success',httpStatus=response.status,bytes=len(body),attempts=attempt+1)
(RAW/f'{name}.json').write_text(json.dumps(payload,ensure_ascii=False))
return name,payload,record
except Exception as exc:
record.update(status='failed',error=str(exc)[:300],attempts=attempt+1)
time.sleep(2+3*attempt)
cached=RAW/f'{name}.json'
if cached.exists():
try:
payload=json.loads(cached.read_text())
record.update(status='cached',note='Live retrieval failed; the previously retrieved raw response in data_raw/ was reused unchanged. Timestamp is the file modification time.',
accessedAt=datetime.datetime.fromtimestamp(cached.stat().st_mtime,datetime.timezone.utc).isoformat(timespec='seconds'))
return name,payload,record
except Exception: pass
return name,None,record
def clean_text(text):
return html.unescape(re.sub('<[^>]+>', ' ', text or '')).strip()
def wb_rows(payload):
return payload[1] if isinstance(payload,list) and len(payload)>1 and isinstance(payload[1],list) else []
def to_series(values):
"""{year:value} -> sorted [[year,value],...] with nulls dropped and finite check."""
out=[]
for year in range(YEARS[0],YEARS[1]+1):
v=values.get(year)
if v is None: continue
v=float(v)
if not math.isfinite(v): continue
out.append([year,round(v,4)])
return out
def validate(data):
assert data['kind'] == 'observed/retrieved'
assert data['version'] == VERSION
ids=[c['id'] for c in data['countries']]
assert len(ids)==len(set(ids)) and len(ids) >= 190, len(ids)
keys=set(data['indicators'])
for g in data['indicatorGroups']:
assert set(g['indicators']) <= keys
observations=0; nonempty=0
for c in data['countries']:
assert re.fullmatch(r'[A-Z]{3}',c['id']) and c['name'] and c['region'] and c['income']
assert c['lat'] is None or -90 <= c['lat'] <= 90
assert c['lon'] is None or -180 <= c['lon'] <= 180
assert set(c['series']) == keys
for key,series in c['series'].items():
years=[y for y,_ in series]
assert years==sorted(years) and len(set(years))==len(years)
assert all(YEARS[0] <= y <= YEARS[1] for y in years)
assert all(isinstance(v,(int,float)) and math.isfinite(v) for _,v in series)
observations+=len(series); nonempty+=bool(series)
for source in data['sources']:
assert source['url'].startswith('https://') and source['accessedAt'] and source['licenseNote']
assert data['evidence']['classification'] == 'related literature; not model effect estimates'
for p in data['evidence']['publications']:
assert p['title'] and p['url'].startswith('https://')
assert data['trials']['classification'] == 'registered studies; not evidence of efficacy or surgery relevance'
for p in data['trials']['studies']:
assert p['nctId'].startswith('NCT') and p['url'].startswith('https://clinicaltrials.gov/study/')
for c in data['connectorProofs']:
assert 'schemaFields' in c and 'patientData' not in c
coverage={k:sum(1 for c in data['countries'] if c['series'][k]) for k in keys}
return {'countries':len(ids),'indicators':len(keys),'series':len(ids)*len(keys),'nonEmptySeries':nonempty,'observations':observations,
'countriesWithAnyValue':coverage,'publications':len(data['evidence']['publications']),'trials':len(data['trials']['studies'])}
def build():
RAW.mkdir(parents=True,exist_ok=True)
jobs=[('worldbank_countries','https://api.worldbank.org/v2/country?format=json&per_page=400',WB_NOTE)]
for key,meta in INDICATORS.items():
if meta['source']=='worldbank':
jobs.append((f'worldbank_{key}',f"https://api.worldbank.org/v2/country/all/indicator/{meta['id']}?format=json&date={YEARS[0]}:{YEARS[1]}&per_page=20000",WB_NOTE))
else:
jobs.append((f'who_{key}',f"https://ghoapi.azureedge.net/api/{meta['id']}",WHO_NOTE))
queries={
'europe_pmc_access':'(TITLE_ABS:"surgical access" OR TITLE_ABS:"access to surgery" OR TITLE_ABS:"access to surgical care") AND (TITLE_ABS:"global surgery" OR TITLE_ABS:"health system" OR TITLE_ABS:"low- and middle-income")',
'europe_pmc_capacity':'(TITLE_ABS:"surgical capacity" OR TITLE_ABS:"surgical workforce") AND (TITLE_ABS:financing OR TITLE_ABS:access OR TITLE_ABS:"health system")',
'europe_pmc_indicators':'TITLE_ABS:"Lancet Commission on Global Surgery" AND TITLE_ABS:indicators'
}
for key,m in UNDC.items():
for i,var in enumerate(m.get('variables') or [m['variable']]):
jobs.append((f'undc_{key}' if not m.get('derived') else f'undc_{key}_{i}',UNDC_REST+'/observation?'+urllib.parse.urlencode([('variable.dcids',var),('entity.expression','Earth<-containedInPlace+{typeOf:Country}'),('date',''),('select','entity'),('select','variable'),('select','value'),('select','date')]),UNDC_NOTE))
for key,q in queries.items():
jobs.append((key,'https://www.ebi.ac.uk/europepmc/webservices/rest/search?'+urllib.parse.urlencode({'query':q,'format':'json','pageSize':6,'resultType':'core','sort':'CITED desc'}),EPMC_NOTE))
jobs.extend([
('demas_cnes','https://apidadosabertos.saude.gov.br/cnes/estabelecimentos?codigo_uf=35&limit=1&offset=0',GOV_NOTE),
('demas_sih','https://apidadosabertos.saude.gov.br/assistencia-a-saude/sih-procedimentos-hospitalares?nu_ano=2023&limit=1&offset=0',GOV_NOTE),
('clinical_trials','https://clinicaltrials.gov/api/v2/studies?'+urllib.parse.urlencode({'query.term':'AREA[BriefTitle]("surgical access" OR "access to surgery" OR "access to surgical care" OR "global surgery" OR "surgical capacity" OR "surgical workforce" OR "surgical care delivery" OR "surgical outreach") AND AREA[StudyType]INTERVENTIONAL','pageSize':6,'format':'json'}),CT_NOTE)
])
responses={}; sources=[]
with concurrent.futures.ThreadPoolExecutor(max_workers=4) as pool:
for name,payload,record in pool.map(fetch,jobs):
responses[name]=payload;sources.append(record)
print(name,record['status'],record.get('bytes',''),flush=True)
(RAW/'request_manifest.json').write_text(json.dumps(sources,indent=2))
metadata=wb_rows(responses['worldbank_countries'])
if not metadata:
raise RuntimeError('Country metadata unavailable; no substitute snapshot was fabricated. Inspect raw request manifest.')
economies=[m for m in metadata if m.get('region',{}).get('value') and m['region']['value']!='Aggregates']
# indicator tables keyed by iso3
tables={}
for key,meta in INDICATORS.items():
if meta['source']=='worldbank':
rows=wb_rows(responses.get(f'worldbank_{key}'))
table={}
for row in rows:
iso=row.get('countryiso3code')
if not iso or row.get('value') is None: continue
table.setdefault(iso,{})[int(row['date'])]=row['value']
tables[key]=(table,'success' if rows else 'unavailable',f'worldbank_{key}')
else:
payload=responses.get(f'who_{key}') or {}
table={}
for row in payload.get('value',[]):
if row.get('SpatialDimType')!='COUNTRY' or row.get('NumericValue') is None: continue
if (row.get('Dim1') or None)!=meta.get('dim1'): continue
table.setdefault(row['SpatialDim'],{})[int(row['TimeDim'])]=row['NumericValue']
tables[key]=(table,'success' if table else 'unavailable',f'who_{key}')
access=None
if ACCESS.exists():
access=json.loads(ACCESS.read_text())
for k,meta in access['indicators'].items():
INDICATORS[k]={**meta,'source':'ascend-geo'}
order=['access2h','access1h','traveltime','access2h_walk','access1h_walk','traveltime_walk']
GROUPS.append({**ACCESS_GROUP,'indicators':[k for k in order if k in access['indicators']]})
sources.append({'id':'ascend_geo_access','name':'ASCEND geographic-access pipeline','url':access['provenance']['methodUrl'],'accessedAt':access['generatedAt'],'licenseNote':access['provenance']['licenseNote'],'status':'computed','note':access['provenance']['summary']})
for s in access['provenance']['inputs']:
sources.append({'id':s['id'],'name':s['name'],'url':s['url'],'accessedAt':access['generatedAt'],'licenseNote':s['licenseNote'],'status':'success'})
undc_tables={}
def undc_table(payload,var,factor):
table={}
for entity,rec in (payload.get('byVariable',{}).get(var,{}).get('byEntity',{}) or {}).items():
mt=re.fullmatch(r'country/([A-Z]{3})',entity)
if not mt: continue
facets=rec.get('orderedFacets') or []
if not facets: continue
vals={}
for o in facets[0].get('observations',[]):
try: y=int(str(o['date'])[:4]); v=float(o['value'])*factor
except Exception: continue
if math.isfinite(v): vals[y]=v
if vals: table[mt.group(1)]=sorted((y,round(v,4)) for y,v in vals.items())
return table
for key,m in UNDC.items():
if m.get('derived'):
parts=[undc_table(responses.get(f'undc_{key}_{i}') or {},var,1) for i,var in enumerate(m['variables'])]
undc_tables[key]=('derived',parts); continue
payload=responses.get(f'undc_{key}') or {}
table={}
for entity,rec in (payload.get('byVariable',{}).get(m['variable'],{}).get('byEntity',{}) or {}).items():
mt=re.fullmatch(r'country/([A-Z]{3})',entity)
if not mt: continue
facets=rec.get('orderedFacets') or []
if not facets: continue
vals={}
for o in facets[0].get('observations',[]):
try: y=int(str(o['date'])[:4]); v=float(o['value'])*m['factor']
except Exception: continue
if math.isfinite(v): vals[y]=v
if vals: table[mt.group(1)]=sorted((y,round(v,4)) for y,v in vals.items())
undc_tables[key]=table
for key,val in list(undc_tables.items()):
if isinstance(val,tuple) and val[0]=='derived':
parts=val[1]; table={}
poptab=tables['population'][0]
for iso in set.intersection(*[set(p) for p in parts]) if parts else set():
years=set.intersection(*[{y for y,_ in p[iso]} for p in parts])
out=[]
for y in sorted(years):
pop=poptab.get(iso,{}).get(y)
if not pop: continue
total=sum(dict(p[iso])[y] for p in parts)
out.append((y,round(total/float(pop)*1e5,4)))
if out: table[iso]=out
undc_tables[key]=table
countries=[]
for meta in sorted(economies,key=lambda m:m['name']):
iso=meta['id']
c={'id':iso,'iso2':meta.get('iso2Code'),'name':meta['name'],'region':meta['region']['value'].strip(),'income':meta['incomeLevel']['value'].strip(),
'capital':meta.get('capitalCity') or None,
'lat':float(meta['latitude']) if meta.get('latitude') else None,'lon':float(meta['longitude']) if meta.get('longitude') else None,
'series':{}}
for key in INDICATORS:
if key in tables:
c['series'][key]=to_series(tables[key][0].get(iso,{}))
elif access:
rec=access['countries'].get(iso,{}); v=rec.get(key)
# Small-island artefact guard: a microstate whose nearest mapped facility is across open water
# (mean travel time > 4 h with < 100,000 people) reflects a gap in the facility database, not access.
suffix='_walk' if key.endswith('_walk') else ''
artefact=rec.get('popTotal2020',0)<100000 and (rec.get('traveltime'+suffix) or 0)>240
c['series'][key]=[[access['referenceYear'],round(float(v),4)]] if v is not None and math.isfinite(float(v)) and not artefact else []
c['crosscheck']={}
for key,table in undc_tables.items():
ser=table.get(iso)
if ser: c['crosscheck'][key]=[list(ser[-1]),len(ser)] # latest [year,value] and number of observations held by UN Data Commons
countries.append(c)
# agreement summary at the latest common year, per indicator
agreement={}
for key,table in undc_tables.items():
n=agree=0;diffs=[]
for c in countries:
ser=table.get(c['id']);snap=dict(c['series'].get(key,[]))
if not ser or not snap: continue
common=[p for p in ser if p[0] in snap]
if not common: continue
y,v=common[-1];a=snap[y];n+=1;rel=abs(a-v)/max(abs(a),abs(v),1e-9);diffs.append(rel)
if rel<=0.05: agree+=1
agreement[key]={'economiesCompared':n,'withinFivePercent':agree,'medianRelativeDifference':round(sorted(diffs)[len(diffs)//2],4) if diffs else None,'undcEconomies':len(table)}
indicator_defs={}
for key,meta in INDICATORS.items():
status=tables[key][1] if key in tables else ('computed' if access else 'unavailable')
indicator_defs[key]={**meta,'sourceId':tables[key][2] if key in tables else 'ascend_geo_access','retrievalStatus':status,
'countriesWithData':sum(1 for c in countries if c['series'][key])}
if key in UNDC:
m=UNDC[key]; first=m.get('variable') or m['variables'][0]
indicator_defs[key]['undc']={**m,'rest':UNDC_REST+'/node?'+urllib.parse.urlencode({'nodes':first,'property':'->name'}),'status':'success' if undc_tables.get(key) else 'unavailable','agreement':agreement.get(key)}
elif key in UNDC_NONE:
indicator_defs[key]['undc']={'status':'no-counterpart','note':UNDC_NONE[key]}
publications=[]; seen=set()
for key,q in queries.items():
response=responses[key] or {}
for p in response.get('resultList',{}).get('result',[]):
ident=p.get('doi') or p.get('pmid') or p['id']
if ident in seen: continue
seen.add(ident)
source=p.get('source','MED');id_=p['id']
publications.append({'id':id_,'title':clean_text(p.get('title')),'authors':p.get('authorString',''),'year':int(p['pubYear']) if p.get('pubYear') else None,
'doi':p.get('doi'),'pmid':p.get('pmid'),'url':f'https://europepmc.org/article/{source}/{id_}',
'abstract':clean_text(p.get('abstractText')),'journal':p.get('journalInfo',{}).get('journal',{}).get('title',''),
'citedBy':p.get('citedByCount'),
'classification':'related literature; not model effect estimates','query':q,'sourceId':key})
studies=[]
for row in (responses['clinical_trials'] or {}).get('studies',[]):
p=row['protocolSection']; ident=p['identificationModule']; design=p.get('designModule',{}); status=p.get('statusModule',{})
locations=p.get('contactsLocationsModule',{}).get('locations',[])
countries_in=sorted({loc.get('country') for loc in locations if loc.get('country')})
studies.append({'nctId':ident['nctId'],'title':ident.get('briefTitle',''),'status':status.get('overallStatus',''),
'phase':design.get('phases',[]),'studyType':design.get('studyType',''),'briefSummary':p.get('descriptionModule',{}).get('briefSummary',''),
'url':'https://clinicaltrials.gov/study/'+ident['nctId'],'countries':countries_in,'locations':[{k:loc[k] for k in ['facility','city','country'] if k in loc} for loc in locations[:6]],
'sourceId':'clinical_trials','lastUpdate':status.get('lastUpdatePostDateStruct',{}).get('date')})
proofs=[]
for name,key in [('demas_cnes','estabelecimentos'),('demas_sih','sih_procedimentos_hospitalares')]:
response=responses[name] or {}; rows=response.get(key,[])
proofs.append({'id':name,'label':'CNES establishment schema' if name=='demas_cnes' else 'SIH hospital-procedure schema',
'status':'success' if rows else 'unavailable','schemaFields':sorted(rows[0].keys()) if rows else [],'sampleRowsRetrieved':len(rows),
'sourceId':name,'note':'Connectivity and schema proof only (example national registry). One response row does not establish data completeness, historical coverage, surgical relevance, or analytic validity.'})
data={'version':VERSION,'kind':'observed/retrieved','accessedAt':utcnow(),'yearRange':list(YEARS),
'description':'All World Bank economies: contextual health-system indicators, Lancet Commission on Global Surgery indicators, SDG 3.8 coverage and protection indicators'+(', and ASCEND-modelled geographic access' if access else '')+'. Observational comparisons do not establish intervention effects.',
'indicatorGroups':GROUPS,'indicators':indicator_defs,'countries':countries,
'sources':sources,'apiRegistry':[{k:v for k,v in r.items() if k not in ['bytes']} for r in sources],
'evidence':{'classification':'related literature; not model effect estimates','selection':'Three explicit Europe PMC queries on surgical access, capacity and LCoGS indicators, six most-cited results per query, deduplicated. Convenience discovery set, not a systematic review. No relevance or risk-of-bias adjudication.','status':'success' if publications else 'unavailable','publications':publications},
'trials':{'classification':'registered studies; not evidence of efficacy or surgery relevance','selection':'First six ClinicalTrials.gov API results for interventional studies with surgical access, capacity, workforce or delivery terms in the title. No country filter; registry coverage of health-system interventions is thin.','status':'success' if studies else 'unavailable','studies':studies},
'connectorProofs':proofs,
'limitations':['All World Bank economies are included; small states and territories often lack observations and remain visibly missing.',
'Latest values can refer to different years; the observation year is always shown next to the value.',
'Expenditure is shown in current US dollars and, separately, in PPP international dollars; neither is inflation-adjusted across years.',
'Missing values remain null; no interpolation, imputation or carry-forward in fixed-year mode.',
'Lancet Commission on Global Surgery indicators are compiled from heterogeneous national reports with irregular years and definitions.',
'National ecological indicators cannot estimate effects of a surgical intervention.',
'Bibliographic and registry retrieval is not a systematic evidence synthesis.',
'No patient-level records are included in this distributable snapshot.']+
(['Geographic access is modelled from a travel-time surface to any healthcare facility and 2020 population counts; it is not the LCoGS Bellwether 2-hour indicator and overstates surgical access.',
'Access values are omitted where fewer than half the population lies on valid travel-time pixels, and for microstates (<100,000 people) whose mean travel time exceeds four hours, which indicates missing facility points rather than inaccessibility.'] if access else [])}
data['undc']={'rest':UNDC_REST,'mcp':'https://unsd-datacommons.gcp.un-icc.cloud/mcp','documentation':'https://projects.officialstatistics.org/undata2/undatacommons-mcp/','portal':'https://data.un.org/undatacommons/','agreement':agreement,
'note':'Eight ASCEND indicators carry a UN System Data Commons counterpart (seven single variables plus a derived SAO density) (DCID in indicators[*].undc). The snapshot stores each economy\'s latest UN Data Commons value for triangulation; the app can also refresh these series live from the browser because the endpoint allows cross-origin requests.'}
data['validation']=validate(data)
DEST.parent.mkdir(parents=True,exist_ok=True)
DEST.write_text(json.dumps(data,ensure_ascii=False,separators=(',',':')))
print(json.dumps(data['validation'],indent=2))
print('bytes',DEST.stat().st_size)
if __name__ == '__main__':
parser=argparse.ArgumentParser();parser.add_argument('--validate',action='store_true');args=parser.parse_args()
if args.validate: print(json.dumps(validate(json.loads(DEST.read_text())),indent=2))
else: build()