#!/usr/bin/env python3
"""Reproduce the 2026-09-14 R/U arithmetic from the included source snapshot.

Python 3.10+; standard library only. No network access, scraping, or source
re-verification is performed. Running this script does not change verification
dates. Run beside the CSV/JSON files, or pass --data-dir PATH.
"""
from __future__ import annotations
import argparse
import csv
import json
import sys
from decimal import Decimal, InvalidOperation, ROUND_HALF_UP, localcontext
from pathlib import Path
from typing import Any

D = Decimal
VERSION = '2026-09-14.2'
FACTORS = {'R_IT': D('0.1761102'), 'U_IT': D('5.678263'),
           'R_TH': D('0.1762280'), 'U_TH': D('5.674466')}


def rows(path: Path) -> list[dict[str, str]]:
    with path.open(encoding='utf-8-sig', newline='') as handle:
        return list(csv.DictReader(handle))


def rounded(value: Decimal, places: int = 12) -> str:
    return format(value.quantize(D(1).scaleb(-places), rounding=ROUND_HALF_UP), f'.{places}f')


def check_value(record: dict[str, str], key: str, value: Decimal, label: str) -> None:
    expected = rounded(value)
    if record.get(key) != expected:
        raise ValueError(f'{label}: {key} = {record.get(key)!r}; expected {expected}')


def verify(directory: Path) -> dict[str, Any]:
    doors = rows(directory / 'door-rating-audit-2026-09-14.csv')
    chart = rows(directory / 'table4-conversion-chart-nist.csv')
    with localcontext() as context:
        context.prec = 36
        numeric_pairs: set[tuple[Decimal, Decimal]] = set()
        reciprocals: list[Decimal] = []
        ratios: list[Decimal] = []
        manufacturers: set[str] = set()
        matches = 0
        for record in doors:
            r, u = D(record['published_r']), D(record['published_u'])
            if not (r.is_finite() and u.is_finite() and r > 0 and u > 0):
                raise ValueError('R and U inputs must be positive finite numbers.')
            label = record['manufacturer'] + ' ' + record['model']
            calculations = {'inverse_of_published_r': 1/r,
                            'equivalent_assembly_r_from_u': 1/u,
                            'ratio_published_u_to_inverse_r': u*r,
                            'r_SI_m2K_per_W': r*FACTORS['R_IT'],
                            'u_SI_W_per_m2K': u*FACTORS['U_IT']}
            for key, value in calculations.items():
                check_value(record, key, value, label)
            if record['dataset_version'] != VERSION:
                raise ValueError(f'{label}: unexpected dataset version')
            numeric_pairs.add((r, u))
            manufacturers.add(record['manufacturer'])
            reciprocals.append(1/u)
            ratios.append(r*u)
            matches += (1/r == u)
        for record in chart:
            r = D(record['r_ip'])
            if not r.is_finite() or r <= 0:
                raise ValueError('Conversion inputs must be positive finite numbers.')
            for key, value in {'u_ip':1/r, 'r_si_IT':r*FACTORS['R_IT'],
                               'u_si_IT':FACTORS['U_IT']/r,
                               'r_si_th':r*FACTORS['R_TH'],
                               'u_si_th':FACTORS['U_TH']/r}.items():
                check_value(record, key, value, 'Conversion R='+record['r_ip'])
        displayed = [D(rounded(x, 2)) for x in reciprocals]
        report: dict[str, Any] = {
            'version': VERSION, 'model_records':len(doors),
            'manufacturer_names':len(manufacturers), 'distinct_numeric_pairs':len(numeric_pairs),
            'matching_reciprocals':matches, 'conversion_rows':len(chart),
            'minimum_U_times_R':rounded(min(ratios), 2),
            'maximum_U_times_R':rounded(max(ratios), 2),
            'equivalent_assembly_R_minimum_rounded':rounded(min(reciprocals), 2),
            'equivalent_assembly_R_maximum_rounded':rounded(max(reciprocals), 2),
            'count_with_displayed_equivalent_R_4_17_through_6_67':
                sum(D('4.17') <= x <= D('6.67') for x in displayed),
            'U_0_24_vs_0_17_percent_denominator_0_17':rounded((D('.24')-D('.17'))/D('.17')*100, 1),
            'R_18_3_vs_16_4_percent_denominator_16_4':rounded((D('18.3')-D('16.4'))/D('16.4')*100,1),
            'U_IT_vs_TH_percent_IT_denominator':rounded((FACTORS['U_IT']-FACTORS['U_TH'])/FACTORS['U_IT']*100,3),
            'live_sources_reverified':False,
        }
        expected = {'model_records':19,'manufacturer_names':3,'distinct_numeric_pairs':14,
                    'matching_reciprocals':0,'conversion_rows':27,
                    'count_with_displayed_equivalent_R_4_17_through_6_67':16}
        for key,value in expected.items():
            if report[key] != value:
                raise ValueError(f'{key}: computed {report[key]!r}, expected {value!r}')
        combined = json.loads((directory/'r-value-vs-u-value-data-2026-09-14.json').read_text(encoding='utf-8'))
        if combined['paired_door_records'] != doors or combined['conversion_chart'] != chart:
            raise ValueError('The combined JSON does not match the CSV records.')
        return report


def main() -> int:
    parser = argparse.ArgumentParser(description=__doc__)
    parser.add_argument('--data-dir', type=Path, default=Path(__file__).resolve().parent)
    parser.add_argument('--output', type=Path, help='Optional output path for a calculation report; no source files are changed.')
    args = parser.parse_args()
    try:
        report = verify(args.data_dir)
        text = json.dumps(report, ensure_ascii=False, indent=2) + '\n'
        if args.output:
            args.output.write_text(text, encoding='utf-8')
        print(text, end='')
        return 0
    except (OSError, ValueError, KeyError, InvalidOperation, csv.Error) as error:
        print(f'Verification failed: {error}', file=sys.stderr)
        return 1

if __name__ == '__main__':
    raise SystemExit(main())
