"""Reproduce the frozen FAERS 2026Q2 reporting analysis offline.
No network calls or dependency installation. Outputs go to a new directory.
The historical execution deadline is enforced only when explicitly requested.
"""
import argparse
from pathlib import Path
parser=argparse.ArgumentParser(description=__doc__)
parser.add_argument('--archive',default='faers_ascii_2026q2.zip')
parser.add_argument('--config',default=str(Path(__file__).with_name('configuration.json')))
parser.add_argument('--output-dir',default='rerun-output')
parser.add_argument('--enforce-session-deadline',action='store_true')
args=parser.parse_args()

from collections import Counter
from datetime import datetime, timezone
import zipfile, csv, re, io, json, hashlib, unicodedata, sys, platform, math
from pathlib import Path
import pandas as pd
import numpy as np
import scipy
from scipy.stats import fisher_exact
CONFIG = json.loads(Path(args.config).read_text(encoding='utf-8'))
ARCHIVE_PATH = Path(args.archive)
OUTPUT_DIR = Path(args.output_dir)
OUTPUT_DIR.mkdir(parents=True,exist_ok=False)
EXPECTED_SHA256 = '90213500721ecaf976ee45d8ad04aa2dbb80861d93c87fef67ccaf515919bf2a'
assert hashlib.file_digest(ARCHIVE_PATH.open('rb'),'sha256').hexdigest() == EXPECTED_SHA256
TABLES = {'DEMO':'ASCII/DEMO26Q2.txt','DRUG':'ASCII/DRUG26Q2.txt','REAC':'ASCII/REAC26Q2.txt'}
audit = {'quarter':CONFIG['quarter'],'source_sha256':EXPECTED_SHA256,'parsing':{},'selection':{},'joins':{},'validation':{}}
def dump_json(name,value):
    (OUTPUT_DIR/name).write_text(json.dumps(value,indent=2,ensure_ascii=False,allow_nan=False),encoding='utf-8')
def normalize_names(s):
    return s.fillna('').str.normalize('NFKC').str.upper().str.replace(r'[^\w]+',' ',regex=True).str.replace('_',' ',regex=False).str.replace(r'\s+',' ',regex=True).str.strip()
def normalize_pt(s):
    return s.fillna('').str.normalize('NFKC').str.upper().str.replace(r'\s+',' ',regex=True).str.strip()
def table_chunks(label,cols=None):
    with zipfile.ZipFile(ARCHIVE_PATH) as z:
        with z.open(TABLES[label]) as f:
            yield from pd.read_csv(f,sep='$',dtype=str,keep_default_na=False,encoding='utf-8',quoting=csv.QUOTE_NONE,usecols=cols,chunksize=CONFIG['chunk_size'],on_bad_lines='error')
with zipfile.ZipFile(ARCHIVE_PATH) as z:
    for label,name in TABLES.items():
        counts=Counter(); malformed=[]; invalid_utf8=[]; non_ascii=0
        with z.open(name) as f:
            header=f.readline().rstrip(b'\r\n').decode('ascii').split('$')
            for lineno,line in enumerate(f,start=2):
                raw=line.rstrip(b'\r\n')
                n=raw.count(b'$')+1
                counts[n]+=1
                if n!=len(header) and len(malformed)<10: malformed.append({'line':lineno,'fields':n})
                if not raw.isascii(): non_ascii+=1
                try: raw.decode('utf-8')
                except UnicodeDecodeError:
                    if len(invalid_utf8)<10: invalid_utf8.append(lineno)
        audit['parsing'][label]={'columns':header,'expected_columns':len(header),'data_lines':sum(counts.values()),'field_count_histogram':dict(counts),'malformed_examples':malformed,'invalid_utf8_examples':invalid_utf8,'non_ascii_lines':non_ascii}
        assert not malformed and not invalid_utf8, f'Input parsing audit failed: {label}'
    delete_lines=z.read('Deleted/DELETE26Q2.txt').decode('utf-8-sig').splitlines()
    deleted_ids={s.strip() for s in delete_lines if s.strip()}
    assert all(re.fullmatch(r'[0-9]+',s) for s in deleted_ids),'Non-numeric deleted-case record'
audit['deletions']={'file':'Deleted/DELETE26Q2.txt','interpretation':'Nonempty numeric lines interpreted as CASEID; file contains no header. No additional deletion instruction was found in accompanying extracted documentation.','nonempty_lines':sum(bool(s.strip()) for s in delete_lines),'distinct_caseids':len(deleted_ids)}
demo_cols=['primaryid','caseid','caseversion','fda_dt','init_fda_dt','sex','age','age_cod','reporter_country','rept_cod']
demo=pd.concat(table_chunks('DEMO',demo_cols),ignore_index=True)
audit['selection']['demo_rows']=len(demo)
audit['selection']['exact_duplicate_selected_field_rows']=int(demo.duplicated().sum())
demo=demo.drop_duplicates()
dupes=demo[demo.duplicated('primaryid',keep=False)]
assert dupes.empty,'Conflicting duplicate PRIMARYID rows require investigation'
valid_ids=demo.primaryid.str.fullmatch(r'[0-9]+') & demo.caseid.str.fullmatch(r'[0-9]+')
audit['selection']['invalid_identifier_rows']=int((~valid_ids).sum())
demo_valid=demo.loc[valid_ids].copy()
audit['selection']['deleted_case_rows']=int(demo_valid.caseid.isin(deleted_ids).sum())
audit['deletions']['overlap_caseids']=int(demo_valid.loc[demo_valid.caseid.isin(deleted_ids),'caseid'].nunique())
demo_valid=demo_valid.loc[~demo_valid.caseid.isin(deleted_ids)].copy()
demo_valid['_date']=pd.to_datetime(demo_valid.fda_dt,format='%Y%m%d',errors='coerce')
demo_valid['_pid']=pd.to_numeric(demo_valid.primaryid,errors='raise')
audit['selection']['invalid_or_missing_fda_date_rows']=int(demo_valid['_date'].isna().sum())
audit['selection']['cases_with_multiple_rows']=int((demo_valid.groupby('caseid').size()>1).sum())
audit['selection']['duplicate_case_date_rows']=int(demo_valid.duplicated(['caseid','fda_dt'],keep=False).sum())
selected=demo_valid.sort_values(['caseid','_date','_pid'],na_position='first',kind='stable').drop_duplicates('caseid',keep='last').copy()
version_nums=pd.to_numeric(demo_valid.caseversion,errors='coerce')
max_versions=version_nums.groupby(demo_valid.caseid).max()
selected_versions=pd.to_numeric(selected.caseversion,errors='coerce')
audit['selection']['selected_not_maximum_numeric_caseversion']=int((selected_versions.to_numpy() != selected.caseid.map(max_versions).to_numpy()).sum())
audit['selection']['rows_removed_by_latest_case_selection']=len(demo_valid)-len(selected)
audit['selection']['latest_cases_before_drug_reaction_eligibility']=len(selected)
audit['selection']['selected_fda_date_min']=str(selected['_date'].min().date())
audit['selection']['selected_fda_date_max']=str(selected['_date'].max().date())
audit['selection']['selected_fda_dates_outside_quarter']=int(((selected['_date']<pd.Timestamp('2026-04-01'))|(selected['_date']>pd.Timestamp('2026-06-30'))).sum())
assert selected.caseid.is_unique and selected.primaryid.is_unique
all_demo_ids=set(demo_valid.primaryid)
selected_ids=set(selected.primaryid)
selected_case_map=selected.set_index('primaryid').caseid
dump_json('configuration.json',CONFIG)
dump_json('audit-preliminary.json',audit)
print(json.dumps(audit,indent=2))

if args.enforce_session_deadline:
    assert datetime.now(timezone.utc) < datetime.fromisoformat(CONFIG['deadline_utc'].replace('Z','+00:00')), 'Deadline reached'
drug_present=set(); reaction_present=set()
drug_ids_all=set(); reaction_ids_all=set()
exposure_any={m:set() for m in CONFIG['medicines']}
exposure_suspect={m:set() for m in CONFIG['medicines']}
event_ids={p:set() for p in CONFIG['reactions']}
matched_names=Counter(); ambiguous_names=Counter(); roles_all=Counter()
matched_row_counts=Counter(); event_row_counts=Counter()
drug_rows=0; drug_unusable=0; drug_mismatch=0; reaction_rows=0; reaction_unusable=0; reaction_mismatch=0
patterns={m:r'(?<!\w)(?:'+'|'.join(re.escape(x) for x in aliases)+r')(?!\w)' for m,aliases in CONFIG['medicines'].items()}
near_pattern=r'SEMAG|OZEMP|RYBEL|WEGOV|LIRAG|VICTO|SAXEND'
for chunk in table_chunks('DRUG',['primaryid','caseid','drug_seq','role_cod','drugname','prod_ai']):
    drug_rows+=len(chunk)
    drug_ids_all.update(chunk.primaryid)
    roles_all.update(chunk.role_cod)
    chunk=chunk.loc[chunk.primaryid.isin(selected_ids)].copy()
    drug_mismatch+=int((chunk.caseid != chunk.primaryid.map(selected_case_map)).sum())
    names=normalize_names(chunk.drugname); ingredients=normalize_names(chunk.prod_ai)
    usable=names.ne('') | ingredients.ne('')
    drug_unusable+=int((~usable).sum())
    drug_present.update(chunk.loc[usable,'primaryid'])
    any_hit=pd.Series(False,index=chunk.index)
    for medicine,pattern in patterns.items():
        hit=names.str.contains(pattern,regex=True) | ingredients.str.contains(pattern,regex=True)
        any_hit=any_hit|hit
        hit_rows=chunk.loc[hit]
        exposure_any[medicine].update(hit_rows.primaryid)
        suspect_rows=hit_rows.loc[hit_rows.role_cod.str.upper().isin(CONFIG['sensitivity_roles'])]
        exposure_suspect[medicine].update(suspect_rows.primaryid)
        matched_row_counts[(medicine,'any')]+=len(hit_rows)
        matched_row_counts[(medicine,'suspect')]+=len(suspect_rows)
        matched_names.update((medicine,row.drugname,row.prod_ai,row.role_cod) for row in hit_rows.itertuples())
    near=(names.str.contains(near_pattern,regex=True)|ingredients.str.contains(near_pattern,regex=True))&~any_hit
    ambiguous_names.update((row.drugname,row.prod_ai,row.role_cod) for row in chunk.loc[near].itertuples())
for chunk in table_chunks('REAC',['primaryid','caseid','pt']):
    reaction_rows+=len(chunk)
    reaction_ids_all.update(chunk.primaryid)
    chunk=chunk.loc[chunk.primaryid.isin(selected_ids)].copy()
    reaction_mismatch+=int((chunk.caseid != chunk.primaryid.map(selected_case_map)).sum())
    pts=normalize_pt(chunk.pt)
    usable=pts.ne('')
    reaction_unusable+=int((~usable).sum())
    reaction_present.update(chunk.loc[usable,'primaryid'])
    for pt in CONFIG['reactions']:
        hit=pts.eq(pt)
        event_ids[pt].update(chunk.loc[hit,'primaryid'])
        event_row_counts[pt]+=int(hit.sum())
assert drug_rows==audit['parsing']['DRUG']['data_lines']
assert reaction_rows==audit['parsing']['REAC']['data_lines']
assert drug_mismatch==0 and reaction_mismatch==0,'CASEID join mismatch'
eligible_ids=selected_ids & drug_present & reaction_present
matrix=selected.loc[selected.primaryid.isin(eligible_ids),['primaryid','caseid','caseversion','fda_dt','init_fda_dt','sex','age','age_cod','reporter_country','rept_cod']].copy()
matrix=matrix.sort_values('primaryid',key=lambda s:pd.to_numeric(s),kind='stable').reset_index(drop=True)
for medicine in CONFIG['medicines']:
    matrix[medicine+'_any']=matrix.primaryid.isin(exposure_any[medicine]).astype('int8')
    matrix[medicine+'_suspect']=matrix.primaryid.isin(exposure_suspect[medicine]).astype('int8')
for pt in CONFIG['reactions']:
    matrix[pt.lower()]=matrix.primaryid.isin(event_ids[pt]).astype('int8')
audit['joins']={'drug_rows_parsed':drug_rows,'reaction_rows_parsed':reaction_rows,'drug_distinct_primaryids':len(drug_ids_all),'reaction_distinct_primaryids':len(reaction_ids_all),'drug_primaryids_absent_from_valid_nondeleted_demo':len(drug_ids_all-all_demo_ids),'reaction_primaryids_absent_from_valid_nondeleted_demo':len(reaction_ids_all-all_demo_ids),'drug_caseid_mismatches_selected':drug_mismatch,'reaction_caseid_mismatches_selected':reaction_mismatch,'unusable_drug_rows_selected':drug_unusable,'unusable_reaction_rows_selected':reaction_unusable,'role_counts_all_drug_rows':dict(roles_all)}
audit['selection'].update({'selected_without_usable_drug':len(selected_ids-drug_present),'selected_without_usable_reaction':len(selected_ids-reaction_present),'selected_missing_both':len(selected_ids-(drug_present|reaction_present)),'eligible_cases':len(matrix),'excluded_for_drug_or_reaction':len(selected_ids)-len(matrix)})
audit['missingness_eligible']={col:int(matrix[col].eq('').sum()) for col in ['sex','age','age_cod','reporter_country','rept_cod']}
audit['membership']={'drug_report_counts':{m:{'any_role':int(matrix[m+'_any'].sum()),'suspect_role':int(matrix[m+'_suspect'].sum()),'matched_rows_any_role_selected':matched_row_counts[(m,'any')],'matched_rows_suspect_role_selected':matched_row_counts[(m,'suspect')]} for m in CONFIG['medicines']},'reaction_report_counts':{p:{'unique_eligible_reports':int(matrix[p.lower()].sum()),'matched_rows_selected':event_row_counts[p]} for p in CONFIG['reactions']},'both_medicines_any_role':int((matrix.semaglutide_any.astype(bool)&matrix.liraglutide_any.astype(bool)).sum())}
audit['validation'].update({'unique_caseids':bool(matrix.caseid.is_unique),'unique_primaryids':bool(matrix.primaryid.is_unique),'all_selected_have_drug_and_reaction':eligible_ids==selected_ids,'full_quarter_background_no_target_filter':True,'no_demographic_geographic_indication_filter':True})
matched_table=pd.DataFrame([{'medicine':k[0],'drugname':k[1],'prod_ai':k[2],'role_cod':k[3],'drug_rows':n} for k,n in sorted(matched_names.items())],columns=['medicine','drugname','prod_ai','role_cod','drug_rows'])
ambiguous_table=pd.DataFrame([{'drugname':k[0],'prod_ai':k[1],'role_cod':k[2],'drug_rows':n} for k,n in sorted(ambiguous_names.items())],columns=['drugname','prod_ai','role_cod','drug_rows'])
assert len(matrix)==len(eligible_ids) and matrix.caseid.is_unique and matrix.primaryid.is_unique
matrix.to_csv(OUTPUT_DIR/'eligible-case-matrix.csv.gz',index=False,compression={'method':'gzip','mtime':0})
matched_table.to_csv(OUTPUT_DIR/'matched-drug-names.csv',index=False)
ambiguous_table.to_csv(OUTPUT_DIR/'nearby-unmatched-drug-names.csv',index=False)
demo_valid.loc[demo_valid.caseid.duplicated(keep=False),demo_cols].to_csv(OUTPUT_DIR/'multiple-case-versions.csv',index=False)
outside=(selected['_date']<pd.Timestamp('2026-04-01'))|(selected['_date']>pd.Timestamp('2026-06-30'))
selected.loc[outside,demo_cols].to_csv(OUTPUT_DIR/'out-of-quarter-fda-dates.csv',index=False)
dump_json('cohort-audit.json',audit)
with zipfile.ZipFile(OUTPUT_DIR/'cohort-audit.zip','w',compression=zipfile.ZIP_DEFLATED) as z:
    for name in ['configuration.json','cohort-audit.json','eligible-case-matrix.csv.gz','matched-drug-names.csv','nearby-unmatched-drug-names.csv','multiple-case-versions.csv','out-of-quarter-fda-dates.csv']:
        z.write(OUTPUT_DIR/name,name)
print(json.dumps({'selection':audit['selection'],'joins':audit['joins'],'membership':audit['membership'],'matched_name_rows':len(matched_table),'nearby_unmatched_name_rows':len(ambiguous_table)},indent=2))
print('Multiple case versions:',demo_valid.loc[demo_valid.caseid.duplicated(keep=False),['primaryid','caseid','caseversion','fda_dt']].to_dict('records'))
print('Out-of-quarter dates retained:',selected.loc[outside,['primaryid','caseid','fda_dt']].to_dict('records'))
print('Unmatched candidates:',ambiguous_table.to_dict('records')[:15])

from decimal import Decimal, localcontext
def calculate_2x2(a,b,c,d):
    raw_counts=[a,b,c,d]
    correction=0.5 if min(raw_counts)==0 else 0.0
    aa,bb,cc,dd=[v+correction for v in raw_counts]
    raw_ror=(a*d)/(b*c) if b*c else (float('inf') if a*d else float('nan'))
    ror=(aa*dd)/(bb*cc)
    se=math.sqrt(sum(1/x for x in [aa,bb,cc,dd]))
    low=math.exp(math.log(ror)-1.96*se); high=math.exp(math.log(ror)+1.96*se)
    fisher=fisher_exact([[a,b],[c,d]],alternative='two-sided')
    with localcontext() as ctx:
        ctx.prec=40
        ratio_decimal=float((Decimal(str(aa))/Decimal(str(bb)))/(Decimal(str(cc))/Decimal(str(dd))))
    assert math.isclose(ror,ratio_decimal,rel_tol=1e-12)
    if math.isfinite(raw_ror): assert math.isclose(raw_ror,float(fisher.statistic),rel_tol=1e-12,abs_tol=1e-15)
    return {'ror_raw':raw_ror,'ror':ror,'ci95_low':low,'ci95_high':high,'correction_added_to_each_cell':correction,'sparse_any_cell_lt5':min(raw_counts)<CONFIG['sparse_cell_threshold'],'fisher_p_two_sided':float(fisher.pvalue),'fisher_p_underflow_to_zero':fisher.pvalue==0}
result_rows=[]
for analysis,role in [('primary_any_role','any'),('sensitivity_ps_ss','suspect')]:
    for medicine in CONFIG['medicines']:
        drug=matrix[medicine+'_'+role].astype(bool)
        for pt in CONFIG['reactions']:
            event=matrix[pt.lower()].astype(bool)
            a=int((drug&event).sum()); b=int((drug&~event).sum())
            c=int((~drug&event).sum()); d=int((~drug&~event).sum())
            assert a+b+c+d==len(matrix)
            assert a+b==int(drug.sum()) and a+c==int(event.sum())
            result_rows.append({'analysis':analysis,'medicine':medicine,'reaction_pt':pt,'a_drug_event':a,'b_drug_no_event':b,'c_no_drug_event':c,'d_neither':d,'n_eligible':len(matrix),'n_drug_reports':a+b,'n_event_reports':a+c,**calculate_2x2(a,b,c,d)})
results=pd.DataFrame(result_rows)
results['holm_p_primary_only']=float('nan')
primary_idx=results.index[results.analysis.eq('primary_any_role')]
p=results.loc[primary_idx,'fisher_p_two_sided'].to_numpy()
order=np.argsort(p,kind='stable')
adjusted_sorted=np.minimum(1,np.maximum.accumulate((len(p)-np.arange(len(p)))*p[order]))
adjusted=np.empty(len(p)); adjusted[order]=adjusted_sorted
results.loc[primary_idx,'holm_p_primary_only']=adjusted
assert len(primary_idx)==4 and len(results)==8
results.to_csv(OUTPUT_DIR/'reporting-results.csv',index=False,float_format='%.17g')
print(results[['analysis','medicine','reaction_pt','a_drug_event','b_drug_no_event','c_no_drug_event','d_neither','ror','ci95_low','ci95_high','fisher_p_two_sided','holm_p_primary_only']].to_string(index=False))

# Independent source reconstruction: csv records + scalar token matching, not pandas membership.
if args.enforce_session_deadline:
    assert datetime.now(timezone.utc) < datetime.fromisoformat(CONFIG['deadline_utc'].replace('Z','+00:00')), 'Deadline reached'
def raw_records(member):
    with zipfile.ZipFile(ARCHIVE_PATH) as z:
        with z.open(member) as fb:
            with io.TextIOWrapper(fb,encoding='utf-8',newline='') as ft:
                reader=csv.reader(ft,delimiter='$',quoting=csv.QUOTE_NONE)
                header=next(reader)
                yield header
                yield from reader
with zipfile.ZipFile(ARCHIVE_PATH) as z:
    independent_deleted={x.strip() for x in z.read('Deleted/DELETE26Q2.txt').decode('utf-8-sig').splitlines() if x.strip()}
raw_demo=raw_records(TABLES['DEMO']); head=next(raw_demo); pos={name:i for i,name in enumerate(head)}
independent_latest={}
for row in raw_demo:
    pid=row[pos['primaryid']]; cid=row[pos['caseid']]; dt=row[pos['fda_dt']]
    if not (pid.isascii() and pid.isdigit() and cid.isascii() and cid.isdigit()) or cid in independent_deleted: continue
    try: rank_date=datetime.strptime(dt,'%Y%m%d').toordinal()
    except ValueError: rank_date=-1
    candidate=(rank_date,int(pid),pid)
    if cid not in independent_latest or candidate>independent_latest[cid]: independent_latest[cid]=candidate
independent_selected={v[2] for v in independent_latest.values()}
assert independent_selected==selected_ids, 'Independent deduplication mismatch'
independent_drug_present=set(); independent_reaction_present=set()
independent_drugs={m:{'any':set(),'suspect':set()} for m in CONFIG['medicines']}
independent_events={p:set() for p in CONFIG['reactions']}
alias_sets={m:set(a) for m,a in CONFIG['medicines'].items()}
def scalar_tokens(value):
    return set(re.sub(r'[\W_]+',' ',unicodedata.normalize('NFKC',value).upper()).split())
raw_drug=raw_records(TABLES['DRUG']); head=next(raw_drug); pos={name:i for i,name in enumerate(head)}
for row in raw_drug:
    pid=row[pos['primaryid']]
    if pid not in independent_selected: continue
    tokens=scalar_tokens(row[pos['drugname']]) | scalar_tokens(row[pos['prod_ai']])
    if tokens: independent_drug_present.add(pid)
    for medicine,aliases in alias_sets.items():
        if not aliases.isdisjoint(tokens):
            independent_drugs[medicine]['any'].add(pid)
            if row[pos['role_cod']].upper() in CONFIG['sensitivity_roles']: independent_drugs[medicine]['suspect'].add(pid)
raw_reac=raw_records(TABLES['REAC']); head=next(raw_reac); pos={name:i for i,name in enumerate(head)}
for row in raw_reac:
    pid=row[pos['primaryid']]
    if pid not in independent_selected: continue
    term=' '.join(unicodedata.normalize('NFKC',row[pos['pt']]).upper().split())
    if term: independent_reaction_present.add(pid)
    if term in independent_events: independent_events[term].add(pid)
independent_eligible=independent_selected & independent_drug_present & independent_reaction_present
assert independent_eligible==set(matrix.primaryid)
validation_rows=[]
for row in results.itertuples():
    role='any' if row.analysis=='primary_any_role' else 'suspect'
    D=independent_drugs[row.medicine][role]&independent_eligible
    E=independent_events[row.reaction_pt]&independent_eligible
    independent_counts=[len(D&E),len(D-E),len(E-D),len(independent_eligible-(D|E))]
    observed_counts=[row.a_drug_event,row.b_drug_no_event,row.c_no_drug_event,row.d_neither]
    assert independent_counts==observed_counts
    expected_D=set(matrix.loc[matrix[row.medicine+'_'+role].eq(1),'primaryid'])
    expected_E=set(matrix.loc[matrix[row.reaction_pt.lower()].eq(1),'primaryid'])
    assert D==expected_D and E==expected_E
    validation_rows.append({'analysis':row.analysis,'medicine':row.medicine,'reaction_pt':row.reaction_pt,'independent_counts':independent_counts,'exact_membership_and_counts_match':True})
assert calculate_2x2(10,20,30,40)['ror']==2/3
zero_check=calculate_2x2(0,10,5,100)
assert zero_check['ror_raw']==0 and zero_check['correction_added_to_each_cell']==0.5
assert math.isclose(zero_check['ror'],(0.5*100.5)/(10.5*5.5),rel_tol=1e-12)
primary=results.loc[results.analysis.eq('primary_any_role')]
# Independent Holm computation by sorted tuples and scalar running maximum.
running=0.0; holm_independent={}
for rank,(idx,row) in enumerate(primary.sort_values('fisher_p_two_sided',kind='stable').iterrows()):
    running=max(running,min(1.0,(4-rank)*row.fisher_p_two_sided))
    holm_independent[idx]=running
assert all(math.isclose(results.loc[idx,'holm_p_primary_only'],v,rel_tol=1e-14,abs_tol=0) for idx,v in holm_independent.items())
validation={'status':'passed','independent_eligible_cases':len(independent_eligible),'independent_latest_report_selection_matches':True,'independent_raw_csv_token_membership_matches':True,'table_count':len(validation_rows),'comparisons':validation_rows,'ratio_decimal_and_scipy_checks':'passed','holm_independent_scalar_check':'passed','zero_cell_and_known_ratio_checks':'passed','all_primary_totals_equal_full_background':bool((primary.n_eligible==len(independent_eligible)).all()),'tested_utc':datetime.now(timezone.utc).isoformat(),'native_replay_performed':False}
audit['validation'].update({'independent_source_reconstruction':'passed','all_eight_2x2_tables_exact':'passed','independent_ratio_calculation':'passed','holm_adjustment_check':'passed'})
dump_json('validation.json',validation)
dump_json('analysis-audit.json',audit)
print(json.dumps(validation,indent=2))

print('REPRODUCTION AND INDEPENDENT CHECKS PASSED')
