"""Check quotes, provenance and numeric coverage; this is not a factual entailment model.
Usage: python3 validate.py --self-test
Uses only the Python standard library. Does not generate prose or call a model.
"""
import argparse, copy, json, re
from datetime import datetime, timezone
from pathlib import Path
ROOT=Path(__file__).resolve().parent

def check(source, output):
    def require(condition, reason):
        if not condition: raise ValueError(reason)
    require(source.get('synthetic') is True and output.get('synthetic') is True,'Synthetic status missing')
    turns={t['id']:t['text'] for t in source['turns']}
    require(8<=len(turns)<=12 and len(turns)==len(source['turns']),'Invalid transcript turn count or duplicate ID')
    blocks=[output['article']['title'],*output['article']['paragraphs'],*output['social']]
    lookup={b['id']:b for b in blocks}
    require(len(lookup)==len(blocks),'Duplicate output IDs')
    require(len(output['social'])==3,'Expected three social excerpts')
    require('Fictional' in output['article']['disclosure'],'Article fictional disclosure missing')
    for block in blocks:
        require(block['turn_ids'] and all(t in turns for t in block['turn_ids']),f"{block['id']}: bad source turn")
    for post in output['social']:
        require(post['text'].startswith('Fictional example:'),f"{post['id']}: missing standalone disclosure")
    registered_quotes=set()
    for quote in output['quotes']:
        require(quote['source_turn'] in turns,'Quote has unknown source')
        require(quote['span'] in turns[quote['source_turn']],'Quote differs from source text')
        for block_id in quote['output_ids']:
            require(block_id in lookup,'Quote has unknown output')
            require(quote['source_turn'] in lookup[block_id]['turn_ids'],'Quote source not cited by output')
            require(quote['span'] in lookup[block_id]['text'],'Registered quote absent from output')
            registered_quotes.add((block_id,quote['span']))
    for block in blocks:
        for span in re.findall('“([^”]+)”',block['text']):
            # Attribution punctuation is outside the verbatim phrase in this sample.
            require((block['id'],span.rstrip(',')) in registered_quotes,f"{block['id']}: unregistered/altered quote")
    number_coverage=set()
    for claim in output['numeric_claims']:
        require(claim['source_turn'] in turns,'Numeric claim has unknown source')
        require(claim['source_span'] in turns[claim['source_turn']],'Numeric supporting span changed')
        require(claim['value'] in re.findall(r'\b\d+(?:\.\d+)?\b',claim['source_span']),'Value absent from supporting span')
        for block_id in claim['output_ids']:
            require(block_id in lookup,'Numeric claim has unknown output')
            block=lookup[block_id]
            require(claim['source_turn'] in block['turn_ids'],'Numeric source not cited by output')
            require(claim['value'] in re.findall(r'\b\d+(?:\.\d+)?\b',block['text']),'Numeric registry contains absent output value')
            number_coverage.add((block_id,claim['value']))
    for block in blocks:
        for number in re.findall(r'\b\d+(?:\.\d+)?\b',block['text']):
            require((block['id'],number) in number_coverage,f"{block['id']}: unsupported number {number}")
    return {'source_turns':len(turns),'output_blocks':len(blocks),'social_excerpts':len(output['social']),'registered_quote_uses':len(registered_quotes),'numeric_uses':len(number_coverage)}

def self_test(source, output):
    mutations=[]
    bad=copy.deepcopy(output);bad['article']['paragraphs'][1]['text']=bad['article']['paragraphs'][1]['text'].replace('24 households','25 households');mutations.append(('unsupported number',bad))
    bad=copy.deepcopy(output);bad['social'][1]['text']=bad['social'][1]['text'].replace('busy room','large crowd');mutations.append(('altered quote',bad))
    bad=copy.deepcopy(output);bad['social'][2]['turn_ids']=['T99'];mutations.append(('unknown turn',bad))
    caught=[]
    for label, bad in mutations:
        try:check(source,bad)
        except ValueError:caught.append(label)
        else:raise AssertionError('Validator missed '+label)
    return caught

if __name__=='__main__':
    parser=argparse.ArgumentParser();parser.add_argument('--self-test',action='store_true');args=parser.parse_args()
    source=json.loads((ROOT/'interview.json').read_text());output=json.loads((ROOT/'outputs.json').read_text())
    result={'passed':True,'checked_at':datetime.now(timezone.utc).isoformat(),'checks':check(source,output),'scope':'Deterministic quote, turn-ID and digit-token checks plus assistant editorial review; not independent factual entailment or model performance measurement.'}
    if args.self_test:result['intentional_defects_rejected']=self_test(source,output)
    (ROOT/'validation-results.json').write_text(json.dumps(result,indent=2)+'\n')
    print(json.dumps(result,indent=2))
