"""Reproducible availability capstone with an independent expected observation grid."""
from __future__ import annotations
import csv
import hashlib
import json
from pathlib import Path

ROOT=Path(__file__).resolve().parent
PRODUCTS=('A','B')
DATES=('2026-01-01','2026-01-02','2026-01-03','2026-01-04')
FIELDS=['product','observed_date','coverage','available_units','sold_units']


def read_rows():
    with (ROOT/'availability.csv').open(encoding='utf-8',newline='') as handle:
        reader=csv.DictReader(handle)
        if reader.fieldnames!=FIELDS:raise ValueError('unexpected CSV schema')
        return list(reader)


def nonnegative_integer(value):
    if not isinstance(value,str) or not value.isascii() or not value.isdecimal():
        raise ValueError('expected nonnegative integer text')
    return int(value)


def analyze(rows):
    expected={(product,day) for product in PRODUCTS for day in DATES}
    indexed={}
    for row in rows:
        if set(row)!=set(FIELDS):raise ValueError('unexpected row schema')
        key=(row['product'],row['observed_date'])
        if key not in expected:raise ValueError('observation outside declared scope')
        if key in indexed:raise ValueError('duplicate product-date observation')
        nonnegative_integer(row['sold_units'])
        if row['coverage']=='known':
            available=nonnegative_integer(row['available_units'])
        elif row['coverage']=='unknown' and row['available_units']=='':
            available=None
        else:raise ValueError('coverage and availability disagree')
        indexed[key]=available
    missing=sorted(expected-indexed.keys())
    known=sum(value is not None for value in indexed.values())
    out=sum(value==0 for value in indexed.values())
    unknown=len(expected)-known
    return {'scheduled_snapshots':len(expected),'received_rows':len(rows),
            'known_snapshots':known,'unknown_snapshots':unknown,
            'missing_rows':[list(key) for key in missing],
            'observed_stockouts':out,'coverage':known/len(expected),
            'known_snapshot_stockout_rate':out/known if known else None,
            'all_snapshot_lower_bound':out/len(expected),
            'all_snapshot_upper_bound':(out+unknown)/len(expected),
            'decision':'Repair coverage and investigate observed zero-stock snapshots; lost demand is not identified.'}


def verify():
    rows=read_rows();baseline=analyze(rows)
    assert (baseline['scheduled_snapshots'],baseline['known_snapshots'],baseline['unknown_snapshots'],baseline['observed_stockouts'])==(8,6,2,2)
    assert (baseline['coverage'],baseline['all_snapshot_lower_bound'],baseline['all_snapshot_upper_bound'])==(.75,.25,.5)
    missing=analyze(rows[1:])
    assert missing['scheduled_snapshots']==8 and missing['known_snapshots']==5
    assert missing['missing_rows']==[['A','2026-01-01']] and missing['all_snapshot_upper_bound']==.625
    mutations=[rows+[dict(rows[0])],
               [dict(row,available_units='0') if row['coverage']=='unknown' else dict(row) for row in rows],
               [dict(rows[0],available_units='-1'),*rows[1:]]]
    for mutation in mutations:
        try:analyze(mutation)
        except ValueError:pass
        else:raise AssertionError('invalid mutation accepted')
    return {'checks_passed':5,'checks':['baseline reference values','missing row preserves scheduled denominator',
            'duplicate key rejected','unknown with invented zero rejected','negative availability rejected']}


def main():
    report=analyze(read_rows())
    report['verification']=verify()
    report['source_sha256']=hashlib.sha256((ROOT/'availability.csv').read_bytes()).hexdigest()
    report['code_sha256']=hashlib.sha256(Path(__file__).read_bytes()).hexdigest()
    report['synthetic']=True
    (ROOT/'capstone-result.json').write_text(json.dumps(report,indent=2)+'\n',encoding='utf-8')
    print(json.dumps(report,indent=2))


if __name__=='__main__':main()
