"""Summarize manually supplied evaluation rows; never calls or grades a model.
Missing charge is unknown, not zero. All attempts including failures contribute.
Currency must be consistent per candidate. Not a financial forecast.
"""
from __future__ import annotations
import argparse
import json
import math
from collections import defaultdict
from pathlib import Path

def number(value, field):
    if isinstance(value,bool) or not isinstance(value,(int,float)) or not math.isfinite(value) or value < 0:
        raise ValueError(field + " must be a finite nonnegative number")
    return value

def summarize(packet):
    if not isinstance(packet,dict):
        raise ValueError("Expected an evaluation record object")
    rows=packet.get("rows")
    if not isinstance(rows,list):
        raise ValueError("rows must be a list")
    groups=defaultdict(list); seen=set(); planned=defaultdict(set)
    for row in rows:
        if not isinstance(row,dict):
            raise ValueError("Each evaluation row must be an object")
        candidate=row["candidate"];task=row["task_id"];attempt=row["attempt"]
        if not isinstance(candidate,str) or not candidate or not isinstance(task,str) or not task:
            raise ValueError("candidate and task_id must be nonempty")
        if type(attempt) is not int or attempt < 1:
            raise ValueError("attempt must be a positive integer")
        key=(candidate,task,attempt)
        if key in seen: raise ValueError("duplicate candidate/task/attempt")
        seen.add(key); planned[candidate].add(task)
        status=row.get("status")
        if status not in ("not_run","completed","failed","unknown"):
            raise ValueError("unknown observation status")
        if status == "not_run":
            if row.get("accepted") is not None or row.get("charge") is not None:
                raise ValueError("not_run must not contain invented acceptance or charge")
            groups[candidate];continue
        if (row.get("accepted") is not None and type(row.get("accepted")) is not bool) or (row.get("accepted") is True and status != "completed"):
            raise ValueError("acceptance must not contradict run status")
        if row.get("charge") is not None: number(row["charge"],"charge")
        if row.get("review_minutes") is not None: number(row["review_minutes"],"review_minutes")
        groups[candidate].append(row)
    result={}
    for candidate, observations in groups.items():
        sequence=defaultdict(list)
        for row in observations:
            sequence[row["task_id"]].append(row["attempt"])
        for attempts in sequence.values():
            if sorted(attempts) != list(range(1,max(attempts)+1)):
                raise ValueError("Observed attempts must be contiguous from one; missing attempts leave costs incomplete")
        tasks={x["task_id"] for x in observations}
        accepted={x["task_id"] for x in observations if x.get("accepted") is True}
        currencies={x.get("currency") for x in observations}
        if len(currencies)>1 or (observations and (not next(iter(currencies)) or not isinstance(next(iter(currencies)),str))):
            raise ValueError("Use one named currency per candidate")
        complete=bool(observations) and all(x.get("charge") is not None and x.get("charge_reconciled") is True and x.get("billing_evidence") for x in observations)
        cost=sum(x["charge"] for x in observations) if complete else None
        if cost is not None and not math.isfinite(cost): raise ValueError("Charge total overflow")
        labor=sum(x["review_minutes"] for x in observations) if observations and all(x.get("review_minutes") is not None for x in observations) else None
        if labor is not None and not math.isfinite(labor): raise ValueError("Review time total overflow")
        result[candidate]={"observed_attempts":len(observations),"unresolved_attempts":sum(x["status"] == "unknown" or x.get("accepted") is None for x in observations),"attempted_tasks":len(tasks),
            "planned_tasks":len(planned[candidate]),"accepted_tasks":len(accepted),
            "first_attempt_accepted_tasks":sum(x.get("accepted") is True and x["attempt"]==1 for x in observations),
            "all_planned_tasks_attempted":tasks==planned[candidate],
            "reconciled_charge":cost,"currency":next(iter(currencies)) if observations else None,
            "charge_per_accepted_task":cost/len(accepted) if cost is not None and accepted else None,
            "recorded_review_minutes":labor}
    return {"cohort":packet.get("cohort"),"candidates":result,
            "winner":"not_determined","boundary":"Caller supplies observations and grades. No live API or independent assessment is performed."}

def main():
    p=argparse.ArgumentParser(description=__doc__);p.add_argument("records");a=p.parse_args()
    try:
        result=summarize(json.loads(Path(a.records).read_text(encoding="utf-8")))
    except (OSError,ValueError,KeyError,TypeError):
        print("Invalid or inconsistent evaluation records");return 2
    print(json.dumps(result,indent=2));return 0

if __name__ == "__main__":
    raise SystemExit(main())
