#!/usr/bin/env python3
"""Offline public-copy integrity and arithmetic check, written for the v2 Q045 correction edition."""
from pathlib import Path
import csv
import hashlib
import json
from collections import Counter

ROOT = Path(__file__).resolve().parent
EXPECTED = {
    'index.html', 'README.md', 'summary.csv', 'summary.json', 'claim-support.json',
    'implementation-excerpt.md', 'verify.py', 'samples/sample-q009.json',
    'samples/sample-q045.json', 'samples/sample-q053.json',
    'CORRECTION-Q045.md', 'correction-q045.json',
}

def require(condition, message):
    if not condition:
        raise SystemExit('FAIL: ' + message)

entries = {}
for line in (ROOT / 'SHA256SUMS.txt').read_text().splitlines():
    digest, name = line.split('  ', 1)
    require(name not in entries and name in EXPECTED, 'Unexpected or duplicate file')
    entries[name] = digest
require(set(entries) == EXPECTED, 'Checksum inventory differs')
actual_files = {str(p.relative_to(ROOT)) for p in ROOT.rglob('*') if p.is_file()}
require(actual_files == EXPECTED | {'SHA256SUMS.txt'}, 'Directory inventory differs')
for name, digest in entries.items():
    require(hashlib.sha256((ROOT / name).read_bytes()).hexdigest() == digest, 'Checksum: ' + name)

summary = json.loads((ROOT / 'summary.json').read_text())
rows = list(csv.DictReader((ROOT / 'summary.csv').open(newline='')))
for row in rows:
    row['count'] = int(row['count'])
    require(row['count'] > 0, 'Non-positive cell')
    for key in ['eci_issue', 'unverified_profile_merge', 'inference_only_issue']:
        require(row[key] in ['true', 'false'], 'Boolean format')
        row[key] = row[key] == 'true'
require(rows == summary['contingency'], 'CSV/JSON cells differ')
require(len({tuple(r[k] for k in ['phase','identity_state','eci_issue','unverified_profile_merge','inference_only_issue']) for r in rows}) == len(rows), 'Duplicate aggregate cells')
for phase in ['baseline', 'retest']:
    selected = [r for r in rows if r['phase'] == phase]
    total = summary['totals'][phase]
    count = lambda predicate: sum(r['count'] for r in selected if predicate(r))
    require(count(lambda r: True) == total['denominator'] == 20, phase + ' denominator')
    distribution = {state: count(lambda r: r['identity_state'] == state) for state in sorted({r['identity_state'] for r in selected})}
    require(distribution == total['identity_distribution'], phase + ' identity distribution')
    require(count(lambda r: r['identity_state'] in ['MIXED','WRONG_ENTITY']) == total['confirmed_identity_contamination'], phase + ' confirmed contamination')
    require(count(lambda r: r['identity_state'] in ['MIXED','WRONG_ENTITY'] or r['unverified_profile_merge']) == total['broad_identity_sensitivity'], phase + ' sensitivity')
    for key in ['eci_issue','inference_only_issue','unverified_profile_merge']:
        require(count(lambda r: r[key]) == total[key], phase + ' ' + key)
    require(count(lambda r: r['identity_state'] == 'TARGET' and r['eci_issue']) == total['target_with_eci_issue'], phase + ' TARGET with ECI')
require([summary['totals'][p]['confirmed_identity_contamination'] for p in ['baseline','retest']] == [10,2], '10 to 2')
require([summary['totals'][p]['broad_identity_sensitivity'] for p in ['baseline','retest']] == [10,6], '10 to 6')
require([summary['totals'][p]['eci_issue'] for p in ['baseline','retest']] == [12,16], '12 to 16')
require([summary['totals'][p]['inference_only_issue'] for p in ['baseline','retest']] == [12,15], '12 to 15')
require(summary['totals']['retest']['target_with_eci_issue'] == 11 and summary['totals']['retest']['identity_distribution']['TARGET'] == 13, '11 of 13')
for phase, expected in [('baseline',(2,200)),('retest',(1,20))]:
    coverage = [r for r in summary['coverage'] if r['phase'] == phase]
    require(tuple(sum(r[k] for r in coverage) for k in ['primary','follow_up']) == expected, phase + ' coverage')
    require(all(r['valid_saved_answers'] == r['primary']+r['follow_up'] and r['exact_isolated_follow_up_requests'] == r['follow_up'] for r in coverage), phase + ' coverage consistency')
for name in EXPECTED:
    if name.endswith('.json'):
        json.loads((ROOT / name).read_text())
correction = json.loads((ROOT / 'correction-q045.json').read_text())
require(correction['record_key'] == {
    'run_id': 'followup-ru-v1-20260915T175235Z-dc68f0',
    'phase': 'baseline', 'provider': 'perplexity', 'model': 'sonar', 'question_id': 'Q045'
}, 'Correction targets the precise baseline record')
require(correction['current_assessment_status'] == 'PRIOR_BASIS_NOT_CONFIRMED', 'Correction status')
require(correction['revised_totals'] == summary['totals'], 'Correction/current totals')
require(correction['previous_totals']['retest'] == summary['totals']['retest'], 'Retest totals unchanged')
previous_baseline = dict(correction['previous_totals']['baseline'])
for key in ['eci_issue', 'inference_only_issue', 'target_with_eci_issue']:
    previous_baseline[key] -= 1
require(previous_baseline == summary['totals']['baseline'], 'Only the three affected baseline totals change')
old_record = correction['previous_record']
new_record = correction['current_counting_record']
expected_record = dict(old_record, eci_issue=False, inference_only_issue=False)
require(old_record['phase'] == 'baseline' and old_record['question_id'] == 'Q045'
        and old_record['identity_state'] == 'TARGET'
        and old_record['eci_issue'] is True and old_record['inference_only_issue'] is True
        and old_record['unverified_profile_merge'] is False, 'Original counting state')
require(new_record == expected_record, 'Only Q045 ECI/inference counting state changes')
cell_keys = ['phase', 'identity_state', 'eci_issue', 'unverified_profile_merge', 'inference_only_issue']
def cell_key(row):
    return tuple(row[key] for key in cell_keys)
previous = Counter({cell_key(r): r['count'] for r in correction['previous_contingency']})
require(len(previous) == len(correction['previous_contingency']), 'Unique original cells')
expected = previous.copy()
require(expected[cell_key(old_record)] == 10, 'Original baseline TARGET positive cell')
expected[cell_key(old_record)] -= 1
expected[cell_key(new_record)] += 1
require(expected == Counter({cell_key(r): r['count'] for r in rows}), 'Exactly one baseline cell transition')
sample = json.loads((ROOT / 'samples/sample-q045.json').read_text())
require(sample['record_key'] == correction['record_key'], 'Sample/correction identity')
require(sample['assessment']['classification']['baseline']['evidence_integrity_problem'] is False,
        'Withdrawn Q045 basis is not counted as confirmed')
require(sample['assessment']['classification']['baseline']['assessment_status'] == 'PRIOR_BASIS_NOT_CONFIRMED',
        'No flawless-answer interpretation of false')
require(summary['version'] == '2.0', 'Version 2 summary')
print('PASS: 12 files; hashes, CSV/JSON, coverage, distributions, sensitivity, ECI arithmetic and single-record correction agree.')
print('This does not independently validate withheld records, classification judgments, truth or publication effects.')
