CRITICAL_FIELDS = ['routing_rule_id','editorial_stage','task_class','provider','model_id','model_lifecycle_state','capability_match','data_policy_class','eval_suite_ref','quality_floor','latency_budget','cost_budget','fallback_route','fallback_trigger','degraded_state','route_decision','evidence_ref']

def _missing(c):
    return [k for k in CRITICAL_FIELDS if c.get(k) in (None,'',[],{})]

def evaluate(c):
    missing=_missing(c)
    # route_decision/degraded_state are declared learner outputs; don't let their absence drive the oracle.
    decisive=[k for k in missing if k not in ('route_decision','degraded_state')]
    if decisive:
        decision='HOLD_EVIDENCE_MISSING'; reason='DECISIVE_EVIDENCE_MISSING'; selected='NONE'; degraded='HOLD'; approval='NOT_ALLOWED'; fallback_used=False
    elif c.get('data_policy_match') is False:
        decision='BLOCKED_DATA_POLICY'; reason='DATA_POLICY_MISMATCH'; selected='NONE'; degraded='BLOCKED'; approval='NOT_ALLOWED'; fallback_used=False
    elif c.get('capability_match')!='PASS':
        decision='BLOCKED_CAPABILITY'; reason='CAPABILITY_MISMATCH'; selected='NONE'; degraded='BLOCKED'; approval='NOT_ALLOWED'; fallback_used=False
    elif c.get('model_lifecycle_state') in ('UNKNOWN','UNVERIFIED'):
        decision='HOLD_LIFECYCLE_UNKNOWN'; reason='LIFECYCLE_UNKNOWN'; selected='NONE'; degraded='HOLD'; approval='NOT_ALLOWED'; fallback_used=False
    else:
        primary_ok=(c.get('model_lifecycle_state')=='ACTIVE' and c.get('primary_eval_present') and c.get('primary_eval_score',0)>=c.get('quality_floor',1) and c.get('primary_latency_ms',10**9)<=c.get('latency_budget',0) and c.get('primary_cost',10**9)<=c.get('cost_budget',0))
        if primary_ok:
            decision='ROUTE_PRIMARY'; reason='PRIMARY_PASSES_ALL_GATES'; selected=c.get('primary_route'); degraded='NONE'; approval='NORMAL'; fallback_used=False
        else:
            # classify primary failure before testing fallback
            if c.get('model_lifecycle_state') in ('RETIRED','DEPRECATED_UNAVAILABLE'):
                primary_reason='PRIMARY_LIFECYCLE_INVALID'
            elif not c.get('primary_eval_present'):
                primary_reason='PRIMARY_EVAL_MISSING'
            elif c.get('primary_eval_score',0)<c.get('quality_floor',1):
                primary_reason='PRIMARY_QUALITY_FLOOR_FAIL'
            else:
                primary_reason='PRIMARY_BUDGET_FAIL'
            fb_complete=all(c.get(k) not in (None,'',[],{}) for k in ['fallback_route','fallback_lifecycle_state','fallback_capability_match','fallback_eval_present','fallback_data_policy_match'])
            fb_ok=(fb_complete and c.get('fallback_lifecycle_state')=='ACTIVE' and c.get('fallback_capability_match')=='PASS' and c.get('fallback_data_policy_match') is True and c.get('fallback_eval_present') is True and c.get('fallback_eval_score',0)>=c.get('quality_floor',1) and c.get('fallback_latency_ms',10**9)<=c.get('latency_budget',0) and c.get('fallback_cost',10**9)<=c.get('cost_budget',0))
            if fb_ok:
                decision='DEGRADED_FALLBACK_REVIEW'; reason=primary_reason; selected=c.get('fallback_route'); degraded='DEGRADED'; approval='REVIEW_BEFORE_RELEASE'; fallback_used=True
            elif c.get('fallback_eval_present') is False and c.get('fallback_lifecycle_state')=='ACTIVE' and c.get('fallback_capability_match')=='PASS' and c.get('fallback_data_policy_match') is True:
                decision='HOLD_EVAL_MISSING'; reason='FALLBACK_EVAL_MISSING'; selected='NONE'; degraded='HOLD'; approval='NOT_ALLOWED'; fallback_used=False
            elif primary_reason=='PRIMARY_LIFECYCLE_INVALID':
                decision='BLOCKED_LIFECYCLE'; reason=primary_reason; selected='NONE'; degraded='BLOCKED'; approval='NOT_ALLOWED'; fallback_used=False
            elif primary_reason=='PRIMARY_QUALITY_FLOOR_FAIL':
                decision='BLOCKED_QUALITY_FLOOR'; reason=primary_reason; selected='NONE'; degraded='BLOCKED'; approval='NOT_ALLOWED'; fallback_used=False
            elif primary_reason=='PRIMARY_EVAL_MISSING':
                decision='HOLD_EVAL_MISSING'; reason=primary_reason; selected='NONE'; degraded='HOLD'; approval='NOT_ALLOWED'; fallback_used=False
            else:
                decision='HOLD_EVIDENCE_MISSING'; reason=primary_reason; selected='NONE'; degraded='HOLD'; approval='NOT_ALLOWED'; fallback_used=False
    return {'decision':decision,'selected_route':selected,'degraded_state':degraded,'approval_required':approval,'fallback_used':fallback_used,'reason_code':reason}
