"""E: same cached crop evidence, alter only which real OCR reads are available.""" from pathlib import Path import json import copy import statistics from analyze import decide,propagate,metrics,infer_groups DIR=Path(__file__).resolve().parent OUT=DIR/'evidence' def main(): data=json.loads((OUT/'observations.json').read_text()); analysis=json.loads((OUT/'analysis.json').read_text()) families=json.loads((DIR/'group-truth.json').read_text())['families'] report={'provenance':data['provenance'],'arms':{}} for res,d in data['resolutions'].items(): obs=d['observations']; sig=data['signatures']; groups=analysis['resolutions'][res]['appearance_groups'] lookup={o['id']:o for o in obs} visual={o['id']:decide(o,'strict',sig,use_text=False) for o in obs} # Only uncertainty in the visual evidence schedules expensive text. ambiguous={o['id'] for o in obs if not visual[o['id']] or len(o['rank'])>1 and o['rank'][0][1]<5} arms={ 'every_attempt':{'ocr_ids':{o['id'] for o in obs},'attempts':2}, 'once_per_track':{'ocr_ids':{o['id'] for o in obs},'attempts':2}, 'ambiguous_track':{'ocr_ids':ambiguous,'attempts':2}, 'group_representative':{'ocr_ids':{max(group,key=lambda i:lookup[i]['sharpness']) for group in groups if any(i in ambiguous for i in group)},'attempts':2}, } result={} for name,arm in arms.items(): if name=='group_representative': source={i:max(g,key=lambda k:lookup[k]['sharpness']) for g in groups for i in g} else:source={i:i for i in lookup} masked=[] for o in obs: clone=copy.deepcopy(o); origin=source[o['id']] # Representative's read is reusable only when its group # correspondence passed the boundary guard. Read is not a # perfect label; it goes through the unchanged policy. if origin not in arm['ocr_ids']: clone['text']={};clone['read']='' else: clone['text']=lookup[origin]['text'];clone['read']=lookup[origin]['read'] masked.append(clone) bases={o['id']:decide(o,'coverage-first',sig) for o in masked} available_groups=infer_groups(d,analysis['resolutions'][res]['guarded_edges'],{o['id']:decide(o,'strict',sig) for o in masked}) fused,changes=propagate(masked,available_groups,bases,sig,guarded=True) number=len(arm['ocr_ids'])*(arm['attempts'] if name=='every_attempt' else 1) ocr_ms=sum(lookup[i]['ocr_ms'] for i in arm['ocr_ids'])*(arm['attempts'] if name=='every_attempt' else 1) local_ms=sum(o['local_ms'] for o in obs)*arm['attempts'] result[name]={'individual':metrics(masked,bases,families),'group':metrics(masked,fused,families,bases),'ocr_calls':number,'ocr_calls_per_facing':number/len(obs),'ocr_ms_serial_projection':round(ocr_ms,1),'matcher_calls':len(obs)*arm['attempts'],'matcher_ms_serial_projection':round(local_ms,1),'read_coverage':sum(bool(lookup[i]['read']) for i in arm['ocr_ids']),'ocr_ids':sorted(arm['ocr_ids']),'rescues':changes} report['arms'][res]=result print(res) for name,r in result.items():print(name,r['ocr_calls'],r['group']['shown'],r['group']['correct'],r['group']['wrong'],r['ocr_ms_serial_projection']) (OUT/'ocr-ablation.json').write_text(json.dumps(report,ensure_ascii=False,indent=2)) if __name__=='__main__':main()