"""Evaluate endpoint volume inference; no fluid solver or extra dependencies needed."""
import argparse,json,math
from pathlib import Path
ROOT=Path(__file__).resolve().parent
VOLUME_KEYS=('unobserved_poured_ml','unobserved_retained_ml','unobserved_spilled_ml')
def credit(error,full,zero):return max(0.,min(1.,(zero-error)/(zero-full)))
def evaluate(answer,truth):
 for k in VOLUME_KEYS:
  if type(answer.get(k)) not in (int,float) or not math.isfinite(answer[k]) or answer[k]<0:raise ValueError('Expected finite nonnegative volume: '+k)
 if type(answer.get('overflow_occurred')) is not bool:raise ValueError('overflow_occurred must be a JSON boolean')
 tolerances=((8,32),(5,20),(12,40));errors={k:abs(answer[k]-truth[k]) for k in VOLUME_KEYS}
 parts={k:credit(errors[k],*t) for k,t in zip(VOLUME_KEYS,tolerances)}
 residual=abs(answer[VOLUME_KEYS[0]]-answer[VOLUME_KEYS[1]]-answer[VOLUME_KEYS[2]])
 parts['conservation']=credit(residual,2,15);parts['overflow']=float(answer['overflow_occurred']==truth['overflow_occurred'])
 score=sum(parts[k]*w for k,w in zip(parts,(.35,.25,.25,.10,.05)))
 return {'score':score,'components':parts,'absolute_errors_ml':errors,'conservation_residual_ml':residual}
def main():
 ap=argparse.ArgumentParser();ap.add_argument('answer');ap.add_argument('--output');args=ap.parse_args()
 report=evaluate(json.loads(Path(args.answer).read_text()),json.loads((ROOT/'answers.json').read_text()))
 print(json.dumps(report,indent=2))
 if args.output:Path(args.output).write_text(json.dumps(report,indent=2)+'\n')
if __name__=='__main__':main()
