const fs=require('fs');const W=process.argv[2]||__dirname; const rows=JSON.parse(fs.readFileSync(W+'/panel.json'));const laws=JSON.parse(fs.readFileSync(W+'/laws.json'));const law=Object.fromEntries(laws.map(l=>[l.state,l])); const J=f=>JSON.parse(fs.readFileSync(W+'/'+f+'.json')); const ff={"2018-19":J('f1819'),"2024-25":J('f2425'),"2025-26":J('f2526')},wr={"2018-19":J('w1819'),"2024-25":J('w2425'),"2025-26":J('w2526')}; for(const r of rows){if(ff[r.schoolYear]){r.girlsFlagFootball=ff[r.schoolYear][r.state]||0;r.girlsWrestling=wr[r.schoolYear][r.state]||0; r.girlsExFlagWrestlingPer1000=+(1000*(r.girls-r.girlsFlagFootball-r.girlsWrestling)/r.girlsPop14to17).toFixed(1);} else {r.girlsFlagFootball=null;r.girlsWrestling=null;r.girlsExFlagWrestlingPer1000=null;}} fs.writeFileSync(W+'/panel2.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))]; const mean=a=>a.reduce((x,y)=>x+y,0)/a.length,sd=a=>{const m=mean(a);return Math.sqrt(a.reduce((x,y)=>x+(y-m)**2,0)/(a.length-1))}; function tq(df){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 {estimate:+(ma-mb).toFixed(1),standardError:+se.toFixed(1),ciLow:+(ma-mb-t*se).toFixed(1),ciHigh:+(ma-mb+t*se).toFixed(1),ciLevel:0.95,lawMeanChange:+ma.toFixed(1),noLawMeanChange:+mb.toFixed(1),nLaw:a.length,nNoLaw:b.length};} const early=l=>l&&["2021-22","2022-23","2023-24"].includes(l.firstYear); const JUMP=['Kentucky','Vermont','Washington','District of Columbia']; function did(m,pre,post,fl=()=>true,fc=()=>true,pct=false){const A=[],B=[];for(const s of states){const a=get(s,pre),b=get(s,post);if(a[m]==null||b[m]==null)continue;const d=pct?100*(b[m]/a[m]-1):b[m]-a[m]; if(a.group==='law'&&early(law[s])&&fl(s))A.push(d);else if(a.group==='noLaw'&&fc(s))B.push(d);}return welch(A,B);} // pooled (population-weighted) difference with state bootstrap function pooled(m,pre,post){const ratio=(rs,y,num,den)=>{let N=0,D=0;for(const s of rs){const r=get(s,y);N+=r[num];D+=r[den];}return 1000*N/D;}; const num=m==='girls'?'girls':'boys',den=m==='girls'?'girlsPop14to17':'boysPop14to17'; const L=states.filter(s=>get(s,pre).group==='law'&&early(law[s])),C=states.filter(s=>get(s,pre).group==='noLaw'); const est=rs=>ratio(rs,post,num,den)-ratio(rs,pre,num,den);const e=est(L)-est(C); let seed=12345;const rnd=()=>{seed=(seed*1103515245+12345)%2147483648;return seed/2147483648;};const bs=[]; for(let i=0;i<5000;i++){const l=L.map(()=>L[Math.floor(rnd()*L.length)]),c=C.map(()=>C[Math.floor(rnd()*C.length)]);bs.push(est(l)-est(c));} bs.sort((a,b)=>a-b);return {estimate:+e.toFixed(1),ciLow:+bs[124].toFixed(1),ciHigh:+bs[4874].toFixed(1),ciLevel:0.95,lawChange:+est(L).toFixed(1),noLawChange:+est(C).toFixed(1)};} const R={ girls_1819_2526:did('girlsPer1000','2018-19','2025-26'), girls_1819_2425:did('girlsPer1000','2018-19','2024-25'), girls_pct_1819_2526:did('girlsPer1000','2018-19','2025-26',undefined,undefined,true), boys_1819_2526:did('boysPer1000','2018-19','2025-26'), boys_1819_2425:did('boysPer1000','2018-19','2024-25'), share_1819_2526:did('girlsSharePct','2018-19','2025-26'), share_1819_2425:did('girlsSharePct','2018-19','2024-25'), placebo_girls_1516_1819:did('girlsPer1000','2015-16','2018-19'), placebo_share_1516_1819:did('girlsSharePct','2015-16','2018-19'), exFW_1819_2526:did('girlsExFlagWrestlingPer1000','2018-19','2025-26'), exFW_1819_2425:did('girlsExFlagWrestlingPer1000','2018-19','2024-25'), noEnjoined_girls_1819_2526:did('girlsPer1000','2018-19','2025-26',s=>!law[s].enjoined), noJump_girls_1819_2526:did('girlsPer1000','2018-19','2025-26',s=>!JUMP.includes(s),s=>!JUMP.includes(s)), noJump_share_1819_2526:did('girlsSharePct','2018-19','2025-26',s=>!JUMP.includes(s),s=>!JUMP.includes(s)), pooled_girls_1819_2526:pooled('girls','2018-19','2025-26'), pooled_boys_1819_2526:pooled('boys','2018-19','2025-26'), cohortA_girls:did('girlsPer1000','2018-19','2025-26',s=>law[s].firstYear==='2021-22'), cohortB_girls:did('girlsPer1000','2018-19','2025-26',s=>law[s].firstYear==='2022-23'), cohortC_girls_2223_2526:did('girlsPer1000','2022-23','2025-26',s=>law[s].firstYear==='2023-24'), }; // share of law-state girls with drop const drops=states.filter(s=>{const a=get(s,'2018-19');return a.group==='law'&&early(law[s])&&get(s,'2025-26').girlsPer1000{const a=get(s,'2018-19');return a.group==='noLaw'&&get(s,'2025-26').girlsPer1000get(x,pre).group==='noLaw').map(x=>get(x,'2025-26').girlsPer1000-get(x,pre).girlsPer1000);R.late[s]={pre,girlsPer1000Pre:a.girlsPer1000,girlsPer1000_2526:b.girlsPer1000,change:+(b.girlsPer1000-a.girlsPer1000).toFixed(1),noLawMeanChange:+mean(ctl).toFixed(1)};} fs.writeFileSync(W+'/results2.json',JSON.stringify(R,null,1));console.log(JSON.stringify(R,null,0).replace(/\},"/g,'},\n"')); // --- data-quality robustness: drop states whose NFHS totals repeat a prior year exactly or move >25% in one year (girls), 2018-19 to 2025-26 const ys=["2018-19","2021-22","2022-23","2023-24","2024-25","2025-26"]; const flagged=states.filter(s=>{for(let i=1;i0.25)return true;}return false;}); R.flaggedStates=flagged; R.noFlagged_girls_1819_2526=did('girlsPer1000','2018-19','2025-26',s=>!flagged.includes(s),s=>!flagged.includes(s)); R.noFlagged_share_1819_2526=did('girlsSharePct','2018-19','2025-26',s=>!flagged.includes(s),s=>!flagged.includes(s)); const med=a=>{const b=[...a].sort((x,y)=>x-y);const n=b.length;return n%2?b[(n-1)/2]:(b[n/2-1]+b[n/2])/2;}; const chg=(grp)=>states.filter(s=>{const a=get(s,'2018-19');return grp==='law'?(a.group==='law'&&early(law[s])):a.group==='noLaw';}).map(s=>get(s,'2025-26').girlsPer1000-get(s,'2018-19').girlsPer1000); R.medians={lawMedianChange:+med(chg('law')).toFixed(1),noLawMedianChange:+med(chg('noLaw')).toFixed(1)}; fs.writeFileSync(W+'/results2.json',JSON.stringify(R,null,1)); console.log('flagged',flagged.join(', '));console.log(JSON.stringify(R.noFlagged_girls_1819_2526),JSON.stringify(R.noFlagged_share_1819_2526),JSON.stringify(R.medians));