from decimal import Decimal, InvalidOperation
from datetime import datetime
from copy import deepcopy
import json
LINES={
'L1':'Invoice: INV-DEMO-017',
'L2':'Invoice date: 14/06/2025',
'L3':'Due date: [unreadable]',
'L4':'Tax: GBP 0.00',
'L5':'Total due: GBP 120.00',
}
LABELS={'total':'Total due: ','tax':'Tax: ','due_date':'Due date: ','purchase_order':'Purchase order: '}
def validate(field,c):
 # A deliberately bounded contract checker for this known fixture.
 # It does not implement the JSON Schema specification or OCR.
 required={'state','raw','value','evidence'}
 if set(c)!=required or c['state'] not in {'present','not_stated','unreadable','conflict'}:return 'shape_reject'
 if not isinstance(c['evidence'],list) or any(not isinstance(v,str) for v in c['evidence']):return 'shape_reject'
 if c['raw'] is not None and not isinstance(c['raw'],str):return 'shape_reject'
 if c['value'] is not None and not isinstance(c['value'],str):return 'shape_reject'
 if c['state']!='present':
  if c['value'] is not None:return 'shape_reject'
  return 'retain_unresolved'
 if not c['value'] or not c['raw'] or not c['evidence']:return 'shape_reject'
 if len(c['evidence'])!=1 or c['evidence'][0] not in LINES:return 'evidence_reject'
 line=LINES[c['evidence'][0]]
 if not line.startswith(LABELS[field]):return 'evidence_reject'
 raw=line[len(LABELS[field]):]
 if raw!=c['raw']:return 'evidence_reject'
 try:
  if field in {'tax','total'}:
   if not raw.startswith('GBP '):return 'normalisation_reject'
   n=Decimal(raw[4:])
   if not n.is_finite() or n<0 or n.as_tuple().exponent!=-2:return 'normalisation_reject'
   value=format(n,'.2f')
  elif field=='due_date':value=datetime.strptime(raw,'%d/%m/%Y').date().isoformat()
  else:value=raw
 except (ValueError,InvalidOperation):return 'normalisation_reject'
 return 'accept_candidate' if value==c['value'] else 'normalisation_reject'
cases=[
 ('total with evidence','total',dict(state='present',raw='GBP 120.00',value='120.00',evidence=['L5']),'accept_candidate'),
 ('explicit zero tax','tax',dict(state='present',raw='GBP 0.00',value='0.00',evidence=['L4']),'accept_candidate'),
 ('PO not found','purchase_order',dict(state='not_stated',raw=None,value=None,evidence=[]),'retain_unresolved'),
 ('unreadable due date','due_date',dict(state='unreadable',raw='[unreadable]',value=None,evidence=['L3']),'retain_unresolved'),
 ('invoice date copied as due date','due_date',dict(state='present',raw='14/06/2025',value='2025-06-14',evidence=['L2']),'evidence_reject'),
 ('zero inserted into missing PO','purchase_order',dict(state='not_stated',raw=None,value='0',evidence=[]),'shape_reject'),
 ('tax normalised to wrong value','tax',dict(state='present',raw='GBP 0.00',value='120.00',evidence=['L4']),'normalisation_reject'),
 ('invented total quote','total',dict(state='present',raw='GBP 125.00',value='125.00',evidence=['L5']),'evidence_reject'),
]
for title,field,candidate,want in cases:
 got=validate(field,candidate)
 assert got==want,(title,got,want)
 print(f'{title}: {got}')
print('8/8 deterministic fixture assertions passed; no model or OCR evaluated.')
