"""Apply a declared decision-cost contract to ID-aligned predictions.""" import math from baseline import LABELS, evaluate def validate_costs(costs): if not isinstance(costs, dict) or set(costs) != set(LABELS): raise ValueError('cost matrix needs exactly the known actual labels') for actual, row in costs.items(): if not isinstance(row, dict) or set(row) != set(LABELS): raise ValueError('each cost row needs exactly the known predictions') for predicted, value in row.items(): if type(value) not in (int, float) or not math.isfinite(value) or value < 0: raise ValueError('costs must be finite nonnegative numbers') if actual == predicted and value != 0: raise ValueError('this error-cost contract requires zero diagonal costs') return costs def score(cases, predictions, costs): costs = validate_costs(costs) if not cases: raise ValueError('cannot score an empty reference set') expected = {} for case in cases: if not isinstance(case['id'], str) or not case['id'] or case['id'] in expected: raise ValueError('reference IDs must be nonempty and unique') if case['label'] not in LABELS: raise ValueError('unknown reference label') expected[case['id']] = case received = {} for prediction in predictions: id, label = prediction['id'], prediction['label'] if not isinstance(id, str) or not id: raise ValueError('prediction IDs must be nonempty strings') if id in received: raise ValueError('duplicate prediction ID') if id not in expected or label not in LABELS: raise ValueError('unknown prediction ID or label') received[id] = label if set(received) != set(expected): raise ValueError('every reference case needs one prediction') # Reuse the baseline's evaluator, but align by ID before its ordered input. ordered = [received[case['id']] for case in cases] metrics = evaluate(ordered, cases) errors = [{'id': case['id'], 'actual': case['label'], 'predicted': predicted, 'cost': costs[case['label']][predicted]} for case, predicted in zip(cases, ordered) if predicted != case['label']] total_cost = math.fsum(error['cost'] for error in errors) return {'metrics': metrics, 'errors': errors, 'total_cost': total_cost, 'cost_per_case': round(total_cost / len(cases), 6)}