// Dataset v2 checks: catch-up (baseline-level) regression, timing of the rise, and states that verify every athlete's sex. // Usage: node v2-checks.js (reads ../panel2.json, ../results2.json, ./laws.json; writes ../v2-checks.json) const fs=require('fs'),path=require('path'); const P=JSON.parse(fs.readFileSync(path.join(__dirname,'..','panel2.json'))); const R=JSON.parse(fs.readFileSync(path.join(__dirname,'..','results2.json'))); const L=JSON.parse(fs.readFileSync(path.join(__dirname,'laws.json')));const law=Object.fromEntries(L.map(l=>[l.state,l])); const early=s=>law[s]&&['2021-22','2022-23','2023-24'].includes(law[s].firstYear); const g=(s,y)=>P.find(r=>r.state===s&&r.schoolYear===y);const st=[...new Set(P.map(r=>r.state))]; const noLaw=st.filter(s=>g(s,'2018-19').group==='noLaw');const lawS=st.filter(early); const mean=a=>a.reduce((x,y)=>x+y,0)/a.length;const med=a=>{a=[...a].sort((x,y)=>x-y);const n=a.length;return n%2?a[(n-1)/2]:(a[n/2-1]+a[n/2])/2;}; const r1=x=>+x.toFixed(1),r2=x=>+x.toFixed(2); // t quantile (two-sided 95%) for df via Cornish-Fisher approximation 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 ols(X,y){const k=X[0].length,n=y.length;const A=Array.from({length:k},()=>Array(k).fill(0)),b=Array(k).fill(0); for(let i=0;i{const N=m.length,M=m.map((r,i)=>[...r,...Array.from({length:N},(_,j)=>i===j?1:0)]);for(let i=0;ir.slice(N));}; const I=inv(A),beta=I.map(r=>r.reduce((s,v,j)=>s+v*b[j],0));const e=y.map((yi,i)=>yi-X[i].reduce((s,v,j)=>s+v*beta[j],0)); const S=Array.from({length:k},()=>Array(k).fill(0));for(let i=0;iI[0].map((_,j)=>r.reduce((s,v,m)=>s+v*S[m].reduce((t,w,q)=>t+w*I[q][j],0),0))); return beta.map((bb,i)=>{const se=Math.sqrt(V[i][i]*n/(n-k)),t=tq(n-k);const rr=Math.abs(bb)<1?r2:r1;return {estimate:rr(bb),standardError:r2(se),ciLow:rr(bb-t*se),ciHigh:rr(bb+t*se),df:n-k};});} const out={}; // 1. Catch-up: change in girls per 1,000 regressed on law dummy and baseline level (HC1 robust SE) out.catchUp={}; for(const [a,b] of [['2018-19','2025-26'],['2018-19','2024-25'],['2015-16','2018-19']]){ const X=[],y=[],Xc=[],yc=[]; for(const s of [...lawS,...noLaw]){const d=g(s,b).girlsPer1000-g(s,a).girlsPer1000;const base=g(s,a).girlsPer1000-450;X.push([1,lawS.includes(s)?1:0,base]);y.push(d);if(noLaw.includes(s)){Xc.push([1,base]);yc.push(d);}} const f=ols(X,y),fc=ols(Xc,yc);out.catchUp[a+'_'+b]={lawGapControllingBaseline:f[1],baselineSlopeAll:f[2],baselineSlopeNoLawOnly:fc[1],n:y.length};} // 2. Timing: how many law states were above their 2018-19 rate, and pooled rates, by year out.timing=['2021-22','2022-23','2023-24','2024-25','2025-26'].map(y=>{let G0=0,P0=0,G1=0,P1=0,C0=0,Q0=0,C1=0,Q1=0; for(const s of lawS){G0+=g(s,'2018-19').girls;P0+=g(s,'2018-19').girlsPop14to17;G1+=g(s,y).girls;P1+=g(s,y).girlsPop14to17;} for(const s of noLaw){C0+=g(s,'2018-19').girls;Q0+=g(s,'2018-19').girlsPop14to17;C1+=g(s,y).girls;Q1+=g(s,y).girlsPop14to17;} return {schoolYear:y,lawStatesAbove2018_19:lawS.filter(s=>g(s,y).girlsPer1000>g(s,'2018-19').girlsPer1000).length,lawStates:lawS.length, noLawStatesAbove2018_19:noLaw.filter(s=>g(s,y).girlsPer1000>g(s,'2018-19').girlsPer1000).length,noLawStates:noLaw.length, lawPooledGirlsPer1000:r1(1000*G1/P1),noLawPooledGirlsPer1000:r1(1000*C1/Q1),lawPooled2018_19:r1(1000*G0/P0),noLawPooled2018_19:r1(1000*C0/Q0)};}); // 3. States that require proof of sex from every athlete (Nebraska: doctor-signed document; New Hampshire: birth certificate or other evidence; Oklahoma: annual parent affidavit) const clean=noLaw.filter(s=>!R.flaggedStates.includes(s)); out.universal=[['Nebraska','doctor-signed document confirming sex, every athlete on a boys\' or girls\' team','2024-25','2025-26'], ['New Hampshire','birth certificate or other evidence of sex at birth','2023-24','2025-26'], ['Oklahoma','annual parent affidavit of sex at birth, every athlete','2018-19','2025-26'], ['Oklahoma','annual parent affidavit of sex at birth, every athlete','2021-22','2025-26']].map(([s,req,a,b])=>{ const ch=(x,m)=>g(x,b)[m]-g(x,a)[m];const row={state:s,requirement:req,from:a,to:b}; for(const m of ['girlsPer1000','boysPer1000','girlsSharePct']){row[m+'Change']=m==='girlsSharePct'?r2(ch(s,m)):r1(ch(s,m));row[m+'NoLawMedianChange']=m==='girlsSharePct'?r2(med(noLaw.map(x=>ch(x,m)))):r1(med(noLaw.map(x=>ch(x,m))));} row.girlsPer1000From=g(s,a).girlsPer1000;row.girlsPer1000To=g(s,b).girlsPer1000;row.girlsFrom=g(s,a).girls;row.girlsTo=g(s,b).girls;return row;}); fs.writeFileSync(path.join(__dirname,'..','v2-checks.json'),JSON.stringify(out,null,1));console.log(JSON.stringify(out,null,1));