CRITICAL=['opportunity_id','region_requirement','service_availability','integration_state','quota_headroom_units','forecast_peak_units','capacity_headroom_units','support_current_hours','support_required_hours','compliance_state','operational_owner','evidence_ref']
ALLOWED={'SERVICEABLE','CONDITIONAL','UNSERVICEABLE','HOLD','PARTIAL','DISPUTED','STALE','BLOCKED'}
def evaluate(case):
    missing=[k for k in CRITICAL if case.get(k) in (None,'',[],'UNKNOWN')]
    stale=case.get('capacity_evidence_age_days',0)>case.get('max_capacity_evidence_age_days',30)
    quota_ok=(case.get('quota_headroom_units') is not None and case.get('forecast_peak_units') is not None and float(case['quota_headroom_units'])>=float(case['forecast_peak_units']))
    capacity_ok=(case.get('capacity_headroom_units') is not None and case.get('forecast_peak_units') is not None and float(case['capacity_headroom_units'])>=float(case['forecast_peak_units']))
    hard_constraints_pass=bool(case.get('service_availability')=='AVAILABLE' and case.get('data_residency_state')=='PASS' and case.get('compliance_state')=='PASS' and quota_ok and capacity_ok and case.get('integration_state') in {'SUPPORTED','GA','PARTIAL'})
    support_gap=(case.get('support_current_hours') is not None and case.get('support_required_hours') is not None and float(case['support_current_hours'])>float(case['support_required_hours']))
    integration_gap=case.get('integration_state')=='PARTIAL'
    exception_required=bool(support_gap or integration_gap)
    exception_valid=(not exception_required) or bool(case.get('exception_approved') and case.get('operational_owner') not in (None,'','UNKNOWN') and case.get('exception_cost') is not None and case.get('exception_expiry_valid'))
    state='HOLD';reason='INSUFFICIENT_EVIDENCE'
    if case.get('blocked_by_policy'):
        state='BLOCKED';reason='OWNER_OR_POLICY_BLOCK'
    elif missing:
        state='HOLD';reason='CRITICAL_EVIDENCE_MISSING'
    elif stale:
        state='STALE';reason='CAPACITY_EVIDENCE_STALE'
    elif case.get('evidence_conflict'):
        state='DISPUTED';reason='EVIDENCE_CONFLICT'
    elif case.get('material_change_not_recomputed'):
        state='PARTIAL';reason='MATERIAL_CHANGE_RECOMPUTE_REQUIRED'
    elif case.get('integration_state')=='UNAVAILABLE' or case.get('service_availability')!='AVAILABLE' or case.get('data_residency_state')!='PASS' or case.get('compliance_state')!='PASS':
        state='UNSERVICEABLE';reason='HARD_CONSTRAINT_FAIL'
    elif not quota_ok:
        state='UNSERVICEABLE';reason='QUOTA_BELOW_FORECAST_PEAK'
    elif not capacity_ok:
        state='UNSERVICEABLE';reason='CAPACITY_BELOW_FORECAST_PEAK'
    elif exception_required and not exception_valid:
        if case.get('exception_expired'):
            state='UNSERVICEABLE';reason='EXCEPTION_EXPIRED'
        else:
            state='HOLD';reason='EXCEPTION_AUTHORITY_INCOMPLETE'
    elif exception_required and exception_valid:
        state='CONDITIONAL';reason='BOUNDED_EXCEPTION_ACTIVE'
    else:
        state='SERVICEABLE';reason='ALL_HARD_CONSTRAINTS_PASS'
    recompute_required=bool(stale or case.get('material_change_not_recomputed') or case.get('evidence_conflict'))
    return {'decision_state':state,'reason_code':reason,'hard_constraints_pass':hard_constraints_pass,'exception_required':exception_required,'exception_cost':float(case.get('exception_cost') or 0),'exception_valid':exception_valid,'missing_critical_count':len(missing),'recompute_required':recompute_required}
