def evaluate(c):
    def out(state, reason, action='KEEP_DEDICATED'):
        peak=float(c.get('peak_capacity_requirement') or 0)
        reserved=float(c.get('reserved_capacity') or 0)
        monthly=float(c.get('monthly_reserved_cost') or 0)
        units=float(c.get('projected_useful_units') or 0)
        floor=round(monthly/units,4) if units>0 else None
        util=round((float(c.get('observed_avg_load') or 0)/reserved*100),1) if reserved>0 else None
        return {'decision_state':state,'reason_code':reason,'recommended_action':action,'capacity_ok':bool(reserved>=peak and peak>0),'unit_cost_floor':floor,'automation_ok':bool(float(c.get('automation_coverage_pct') or 0)>=80),'version_parity_ok':bool(c.get('product_version_matches')),'evidence_ref':c.get('evidence_ref')}
    if c.get('owner_blocked'): return out('BLOCKED','OWNER_POLICY_BLOCK')
    if not c.get('evidence_ref'): return out('HOLD','MISSING_EVIDENCE')
    if c.get('evidence_age_days',0)>30: return out('STALE','DECISIVE_EVIDENCE_STALE')
    if c.get('material_change_not_recomputed'): return out('PARTIAL','RECOMPUTE_REQUIRED')
    if c.get('drift_state')=='UNKNOWN': return out('HOLD','DRIFT_UNKNOWN')
    hard=bool(c.get('regulatory_constraint') or c.get('performance_constraint'))
    if not hard:
        if c.get('pooled_isolation_compliant'):
            if c.get('migration_path')!='DEFINED': return out('HOLD','MIGRATION_EVIDENCE_INCOMPLETE','MIGRATE_TO_POOLED_PENDING_EVIDENCE')
            return out('REJECT','DEDICATED_REASON_REMOVED','MIGRATE_TO_POOLED')
        return out('REJECT','NO_HARD_ISOLATION_REASON')
    if c.get('configuration_delta') and not c.get('configuration_delta_approved'): return out('REJECT','UNAPPROVED_CUSTOMER_FORK')
    if not c.get('product_version_matches'): return out('REJECT','VERSION_PARITY_FAILED')
    if c.get('drift_state')=='DRIFTED': return out('REJECT','UNMANAGED_DRIFT')
    if float(c.get('reserved_capacity') or 0)<float(c.get('peak_capacity_requirement') or 0): return out('REJECT','RESERVED_CAPACITY_BELOW_PEAK')
    if float(c.get('automation_coverage_pct') or 0)<80: return out('REJECT','AUTOMATION_COVERAGE_TOO_LOW')
    monthly=float(c.get('monthly_reserved_cost') or 0); ceiling=float(c.get('monthly_cost_ceiling') or 0)
    if ceiling>0 and monthly>ceiling and not c.get('exception_authority'): return out('REJECT','COST_FLOOR_ABOVE_ACCEPTED_CEILING')
    if not c.get('observability_binding') or not c.get('backup_restore_profile'): return out('HOLD','OPERATIONS_EVIDENCE_INCOMPLETE')
    if c.get('migration_path') not in ('DEFINED','NOT_REQUIRED') or not c.get('decommission_trigger'): return out('HOLD','LIFECYCLE_EVIDENCE_INCOMPLETE')
    return out('ELIGIBLE','ALL_GATES_PASS')
