const fs=require('fs');const W=__dirname, D=W+'/../nfhs-dl'; const tot=JSON.parse(fs.readFileSync(W+'/totals.json'));const laws=JSON.parse(fs.readFileSync(W+'/laws.json')); const POLICY=['Alaska','Virginia']; const years=["2015-16","2016-17","2017-18","2018-19","2021-22","2022-23","2023-24","2024-25","2025-26"]; // population ages 14-17 const pop={}; function load(f,cols){const L=fs.readFileSync(D+'/'+f,'utf8').split(/\r?\n/);const h=L[0].split(','); for(const l of L.slice(1)){const c=l.split(',');if(c[0]!=='040')continue;const sex=+c[5],age=+c[6];if(sex===0||age<14||age>17)continue; for(const [y,col] of cols){const v=+c[h.indexOf(col)];const k=c[4]+'|'+y+'|'+sex;pop[k]=(pop[k]||0)+v;}}} load('sc-est2019-agesex-civ.csv',[["2015-16","POPEST2015_CIV"],["2016-17","POPEST2016_CIV"],["2017-18","POPEST2017_CIV"],["2018-19","POPEST2018_CIV"]]); load('sc-est2025-agesex-civ.csv',[["2021-22","POPEST2021_CIV"],["2022-23","POPEST2022_CIV"],["2023-24","POPEST2023_CIV"],["2024-25","POPEST2024_CIV"],["2025-26","POPEST2025_CIV"]]); const law=Object.fromEntries(laws.map(l=>[l.state,l])); const rows=tot.map(r=>{const g=pop[r.state+'|'+r.year+'|2'],b=pop[r.state+'|'+r.year+'|1'];if(!g)throw r.state; const L=law[r.state];const group=L?'law':POLICY.includes(r.state)?'policy':'noLaw'; return {state:r.state,schoolYear:r.year,group,lawFirstYear:L?L.firstYear:null,girls:r.girls,boys:r.boys,totalReported:r.total,girlsPop14to17:g,boysPop14to17:b, girlsPer1000:+(1000*r.girls/g).toFixed(1),boysPer1000:+(1000*r.boys/b).toFixed(1),girlsSharePct:+(100*r.girls/(r.girls+r.boys)).toFixed(2)};}); fs.writeFileSync(W+'/panel.json',JSON.stringify(rows)); const get=(s,y)=>rows.find(r=>r.state===s&&r.schoolYear===y); const states=[...new Set(rows.map(r=>r.state))]; function mean(a){return a.reduce((x,y)=>x+y,0)/a.length} function sd(a){const m=mean(a);return Math.sqrt(a.reduce((x,y)=>x+(y-m)**2,0)/(a.length-1))} // Welch CI with t approx function tq(df){// 97.5% t quantile approx const z=1.959964;return z+ (z**3+z)/(4*df)+(5*z**5+16*z**3+3*z)/(96*df*df);} function welch(a,b){const ma=mean(a),mb=mean(b),va=sd(a)**2/a.length,vb=sd(b)**2/b.length;const se=Math.sqrt(va+vb);const df=(va+vb)**2/(va**2/(a.length-1)+vb**2/(b.length-1));const t=tq(df); return {diff:ma-mb,se,ciLow:ma-mb-t*se,ciHigh:ma-mb+t*se,df,meanA:ma,meanB:mb,nA:a.length,nB:b.length};} function did(metric,pre,post,filterLaw,filterCtl){const A=[],B=[]; for(const s of states){const r0=get(s,pre),r1=get(s,post);const d=r1[metric]-r0[metric]; if(r0.group==='law'&&filterLaw(law[s]))A.push(d); else if(r0.group==='noLaw'&&filterCtl(s))B.push(d);} return welch(A,B);} const all=()=>true; const out={}; const early=l=>["2021-22","2022-23","2023-24"].includes(l.firstYear); const notEnj=l=>early(l)&&!l.enjoined; for(const m of ['girlsPer1000','boysPer1000','girlsSharePct']){ out[m]={main:did(m,'2018-19','2025-26',early,all), y2425:did(m,'2018-19','2024-25',early,all), y2223:did(m,'2018-19','2022-23',early,all), noEnjoined:did(m,'2018-19','2025-26',notEnj,all), noWA:did(m,'2018-19','2025-26',early,s=>s!=='Washington'), placeboPre:did(m,'2015-16','2018-19',early,all), cohortA:did(m,'2018-19','2025-26',l=>l.firstYear==='2021-22',all), cohortB:did(m,'2018-19','2025-26',l=>l.firstYear==='2022-23',all), cohortC_from2223:did(m,'2022-23','2025-26',l=>l.firstYear==='2023-24',all), cohortB_from2122:did(m,'2021-22','2025-26',l=>l.firstYear==='2022-23',all), };} // group means by year (unweighted state means and pooled totals) const series=[]; for(const y of years)for(const grp of ['law','noLaw']){const rs=rows.filter(r=>r.schoolYear===y&&r.group===grp&&(grp==='noLaw'||early(law[r.state]))); const G=rs.reduce((a,r)=>a+r.girls,0),GP=rs.reduce((a,r)=>a+r.girlsPop14to17,0),B=rs.reduce((a,r)=>a+r.boys,0),BP=rs.reduce((a,r)=>a+r.boysPop14to17,0); series.push({schoolYear:y,group:grp,nStates:rs.length,girls:G,boys:B,pooledGirlsPer1000:+(1000*G/GP).toFixed(1),pooledBoysPer1000:+(1000*B/BP).toFixed(1),meanGirlsPer1000:+mean(rs.map(r=>r.girlsPer1000)).toFixed(1),meanGirlsSharePct:+mean(rs.map(r=>r.girlsSharePct)).toFixed(2)});} fs.writeFileSync(W+'/results.json',JSON.stringify({did:out,series},null,1)); console.table(series); for(const m in out){console.log(m);for(const k in out[m]){const o=out[m][k];console.log(' ',k.padEnd(18),o.diff.toFixed(2),'['+o.ciLow.toFixed(2)+','+o.ciHigh.toFixed(2)+']','law',o.meanA.toFixed(2),'ctl',o.meanB.toFixed(2),o.nA,o.nB);}}