import {COUNTRIES,INDICATORS,IDS,COUNTRY_IDS,finite,escapeHTML,formatValue,latest,subset,coverage,status,timeline,compareCountries,crossCountryGap,paired,pearson,change,toCSV,validateSession,normalizeRows} from './core.js'; import {makeWorldBankURL,requestWorldBank} from './worldbank.js'; import {createDemo} from './demo-data.js'; const $=id=>document.getElementById(id); const COLOR=['#007e77','#3562af','#df8c43','#8b6cc2','#d75872','#4a9d9c','#b78d27','#3f8294']; const STATE={rows:createDemo(),mode:'demo',url:null,loadedAt:null,loadedCountries:[],activeView:'overview'}; const saved=['ARG','BOL','BRA','CHL','COL','PRY']; function populate(){ $('countries').innerHTML=COUNTRY_IDS.map(c=>``).join(''); $('indicator').innerHTML=IDS.map(id=>``).join(''); $('compare-indicator').innerHTML=IDS.map(id=>``).join(''); $('gap-year').value='2020'; } function chosen(){return [...$('countries').selectedOptions].map(o=>o.value);} function filters(){const countries=chosen(),start=Number($('start').value),end=Number($('end').value),ind=$('indicator').value,other=$('compare-indicator').value; if(!countries.length)throw new Error('Choose at least one country.');if(countries.length>8)throw new Error('Choose at most eight countries.');if(!Number.isInteger(start)||!Number.isInteger(end)||start<1990||end>2026||start>end)throw new Error('Choose a valid year range between 1990 and 2026.'); return {countries,start,end,ind,other};} function setAlert(text,type='demo'){const box=$('alert');box.textContent=text;box.className='alert '+type;} function withFilters(fn){try{return fn(filters());}catch(e){setAlert(e.message,'error');return null;}} function esc(n){return escapeHTML(n);} function num(n,d=1){return n===null||n===undefined?'N/A':Number(n).toLocaleString('en-US',{maximumFractionDigits:d});} function smallTitle(t){return esc(t);} function empty(msg){return `
| Rank | Economy | Indicator | Reported value | Year | Lag |
|---|---|---|---|---|---|
| ${i+1} | ${esc(COUNTRIES[x.country])} | ${esc(INDICATORS[f.ind].short)} | ${esc(formatValue(x.point.value,f.ind))} | ${x.point.year} | ${f.end-x.point.year} |
| Economy | First → last | Change (indicator units) | Reported years |
|---|
${esc(INDICATORS[f.ind].tip)} Change uses earliest and latest available observations, not necessarily the requested boundary years.
`; } function renderGaps(f){ const selected=f.countries;$('gap-a').innerHTML=selected.map(c=>``).join('');$('gap-b').innerHTML=selected.map(c=>``).join(''); const priorA=$('gap-a').dataset.last,priorB=$('gap-b').dataset.last;$('gap-a').value=selected.includes(priorA)?priorA:selected[0];$('gap-b').value=selected.includes(priorB)?priorB:(selected[1]||selected[0]); const yr=Number($('gap-year').value);if(yr| Economy | First reported | Last reported | Change | Available years |
|---|---|---|---|---|
| ${esc(COUNTRIES[s.iso3])} | Missing | |||
| ${esc(COUNTRIES[s.iso3])} | ${esc(formatValue(a.value,f.ind))} (${a.year}) | ${esc(formatValue(b.value,f.ind))} (${b.year}) | ${d?(d.amount>0?'+':'')+esc(num(d.amount,2)):'N/A'} | ${s.points.length} |
${esc(COUNTRIES[a])}: ${esc(formatValue(z.a,f.ind))} · ${esc(COUNTRIES[b])}: ${esc(formatValue(z.b,f.ind))}. This is a descriptive cross-country difference, not an estimate of within-country equity.
`:empty('At least one of these countries has no observation for this exact year. Choose another year; values are never filled in.');} function renderRelations(f){const a=f.ind,b=f.other;if(a===b){$('scatter-chart').innerHTML=empty('Choose a different association variable.');$('relationship-meta').textContent='Comparing a measure with itself is not useful.';return;} const pairs=paired(STATE.rows,a,b,f.countries,f.start,f.end);if(pairs.length<3){$('scatter-chart').innerHTML=empty('Too few matched country-year observations. Try a wider period or a series with better coverage.');$('relationship-meta').textContent=`Only ${pairs.length} valid matched pairs. At least three are required.`;return;} const w=800,h=355,p={l:85,r:25,t:25,b:72},[xmin,xmax]=bounds(pairs.map(x=>x[a])),[ymin,ymax]=bounds(pairs.map(x=>x[b]));const xx=x=>p.l+(x-xmin)/(xmax-xmin)*(w-p.l-p.r),yy=y=>h-p.b-(y-ymin)/(ymax-ymin)*(h-p.t-p.b); let svg=`'; const r=pearson(pairs,a,b);$('scatter-chart').innerHTML=svg; $('relationship-meta').innerHTML=`Paired observations: ${pairs.length} (country-year pairs) · Pearson r: ${r===null?'N/A':num(r,3)} · Selected countries: ${f.countries.length}.| Economy | ${cols.map(id=>`${esc(INDICATORS[id].short)} | `).join('')}
|---|
Color darkness approximates the share of selected years with a reported value. Coverage is not a quality certificate.
`; const st=status(sample,f.countries,IDS,f.start,f.end);$('source-audit').innerHTML=`Data mode: ${STATE.mode==='live'?'World Bank WDI API, source 2':STATE.mode==='demo'?'SYNTHETIC DEMO (not official)':'User imported, source unverified'}.
Period: ${f.start}–${f.end}. Coverage: ${st.count}/${st.eligible} possible country-year-indicator observations. Reporting lag: ${st.meanLag===null?'N/A':st.meanLag.toFixed(1)+' years'} averaged over available series.
UNESCO UIS supplies many education series reproduced in World Development Indicators. Gross enrollment and primary completion definitions should not be interpreted as graduation rates, and gross ratios can exceed 100%. Missing values stay missing. Cross-country coverage and vintage differ. GDP per capita and Internet use are contextual, not direct school outcomes.
API: ${STATE.url?`Exact query link`:'Not queried live in this session.'} · World Bank API documentation · Indicator metadata glossary
`; } function makeBrief(f){const s=subset(STATE.rows,f.countries,f.ind,f.start,f.end),rank=compareCountries(STATE.rows,f.countries,f.ind,f.start,f.end),st=status(STATE.rows,f.countries,IDS,f.start,f.end),pairs=paired(STATE.rows,f.ind,f.other,f.countries,f.start,f.end);const r=f.ind===f.other?null:pearson(pairs,f.ind,f.other);const mode=STATE.mode==='live'?'OFFICIAL WORLD BANK WDI OBSERVATIONS':STATE.mode==='demo'?'SYNTHETIC DEMO, NOT CITABLE':'USER-IMPORTED DATA, SOURCE UNVERIFIED'; const lines=[`# Education Data Lab: Comparative Research Brief`,``,`**Data status:** ${mode}`,`**Period:** ${f.start}–${f.end}`,`**Countries:** ${f.countries.map(c=>COUNTRIES[c]).join(', ')}`,`**Primary measure:** ${INDICATORS[f.ind].label} (${INDICATORS[f.ind].unit})`,`**Association measure:** ${INDICATORS[f.other].label}`,`**Generated:** ${new Date().toISOString()}`,``,`## Observations`,`- ${s.length} non-missing observations for the primary measure; ${st.count}/${st.eligible} country-year-indicator cells observed across the 8 tracked series.`,`- Latest reported values may have different reporting years and must not be described as same-year comparisons.`]; if(rank.length){const top=rank[0];lines.push(`- Highest latest *reported* value within these selected economies: ${COUNTRIES[top.country]} (${formatValue(top.point.value,f.ind)}, ${top.point.year}). This is not a causal ranking or necessarily the latest calendar year.`);}else lines.push('- No latest reported observation is available for this measure in the selected window.'); for(const item of rank.slice(0,8))lines.push(`- ${COUNTRIES[item.country]}: ${formatValue(item.point.value,f.ind)} (${item.point.year})`); lines.push('','## Exploratory association');if(r!==null)lines.push(`- Pearson r for ${pairs.length} matched country-year pairs: ${r.toFixed(3)}. Descriptive only; repeated country-years are not independent and **no causal or significance inference** follows.`);else lines.push('- Insufficient or unsuitable paired observations for correlation.'); lines.push('','## Methodological safeguards','- Missing years are not interpolated, carried forward or imputed.','- Gross education indicators may exceed 100%; they are not net participation rates.','- Data vintages, coverage, definitions and reporting frequency differ across countries.','- This brief is an exploratory descriptive analysis, not an impact evaluation or recommendation for resource allocation.','',`## Provenance`,`- Mode: ${mode}`,`- Query: ${STATE.url||'No live query; generated demo or imported file.'}`,'- World Bank Indicators API v2, WDI source 2: https://datahelpdesk.worldbank.org/knowledgebase/articles/889392','- Definitions: https://databank.worldbank.org/metadataglossary/world-development-indicators/','');return lines.join('\n');} function renderBrief(f){const txt=makeBrief(f);$('brief-text').innerHTML=txt.split('\n').map(line=>{ if(line.startsWith('# '))return `• ${esc(line.slice(2))}
`; return line?`${esc(line)}
`:'';}).join('');} function render(){withFilters(f=>{updateTop(f);renderOverview(f);renderTrends(f);renderGaps(f);renderRelations(f);renderQuality(f);renderBrief(f);});} function rowsForSelection(){const f=filters();return STATE.rows.filter(r=>f.countries.includes(r.iso3)&&r.year>=f.start&&r.year<=f.end);} function download(text,name,type){const blob=new Blob([text],{type});const u=URL.createObjectURL(blob);const a=document.createElement('a');a.href=u;a.download=name;document.body.appendChild(a);a.click();a.remove();setTimeout(()=>URL.revokeObjectURL(u),10000);} function sendExport(type){withFilters(f=>{const suf=STATE.mode==='demo'?'SYNTHETIC_DEMO':STATE.mode==='live'?'WORLD_BANK':'USER_IMPORTED';const pre='education-data-lab_'+suf+'_'+new Date().toISOString().slice(0,10); if(type==='csv')download(toCSV(rowsForSelection()),pre+'.csv','text/csv;charset=utf-8'); else if(type==='md')download(makeBrief(f),pre+'_research-brief.md','text/markdown;charset=utf-8'); else download(JSON.stringify({format:'edlab-session-v1',mode:STATE.mode,sourceURL:STATE.url,exportedAt:new Date().toISOString(),filters:f,rows:rowsForSelection(),disclaimer:'Demo values are synthetic; imported files are unverified; live source is World Bank WDI.'},null,2),pre+'.json','application/json');});} function bind(){ for(const id of ['countries','indicator','start','end','compare-indicator'])$(id).addEventListener('change',render); document.querySelectorAll('.tab').forEach(btn=>btn.addEventListener('click',()=>{document.querySelectorAll('.tab').forEach(t=>t.classList.toggle('active',t===btn));document.querySelectorAll('.view').forEach(v=>v.classList.toggle('active',v.id==='view-'+btn.dataset.view));STATE.activeView=btn.dataset.view;})); for(const id of ['gap-a','gap-b','gap-year'])$(id).addEventListener('change',()=>withFilters(renderGapResult)); $('load-demo').addEventListener('click',()=>{STATE.rows=createDemo();STATE.mode='demo';STATE.url=null;STATE.loadedAt=null;STATE.loadedCountries=[];render();}); $('load-live').addEventListener('click',async()=>{const f=withFilters(x=>x);if(!f)return;const btn=$('load-live');btn.disabled=true;btn.textContent='Loading official data…';setAlert('Fetching official observations from the World Bank. Please wait.','live'); try{const {rows,url}=await requestWorldBank(f.countries,IDS,f.start,f.end);STATE.rows=rows;STATE.mode='live';STATE.url=url;STATE.loadedAt=new Date().toLocaleString();STATE.loadedCountries=[...f.countries];render();} catch(e){setAlert('Live data could not be loaded: '+e.message+'. Previously shown data were not replaced. You can use the clearly labeled demo meanwhile.','error');} finally{btn.disabled=false;btn.textContent='↗ Load World Bank data';} }); $('download-csv').addEventListener('click',()=>sendExport('csv'));$('export-md').addEventListener('click',()=>sendExport('md'));$('export-json').addEventListener('click',()=>sendExport('json')); $('import-json').addEventListener('click',()=>$('import-file').click());$('import-file').addEventListener('change',async(e)=>{const file=e.target.files?.[0];if(!file)return;try{if(file.size>6e6)throw new Error('Maximum file size is 6 MB');const json=JSON.parse(await file.text());const parsed=validateSession(json);STATE.rows=parsed.rows;STATE.mode='imported';STATE.url=null;STATE.loadedCountries=[];STATE.loadedAt=new Date().toLocaleString();render();}catch(error){setAlert('Import failed: '+error.message,'error');}finally{e.target.value='';}}); } populate();bind();render();