# ConnectWise SLA Evaluation Recipe Reference: `sla_evaluation.md` — How to evaluate SLA state and score tickets for prioritization using the CW v3 API. ## Fields to Read | Field | Type | Meaning | |-------|------|---------| | `isInSla` | bool | False = breached | | `slaStatus` | string | e.g. "Respond by Tue 04/21 3:37 PM UTC-07" | | `respondMinutes` | int | Minutes in "respond" phase | | `resPlanMinutes` | int | Minutes in "resplan" phase | | `resolveMinutes` | int | Minutes in "resolve" phase | | `escalationLevel` | int | Tier (higher = worse) | | `priority.level` | string | Critical / High / Medium / Low | | `severity` | string | Critical / High / Medium / Low | | `impact` | string | Critical / High / Medium / Low | | `_info.dateEntered` | ISO string | Canonical creation time | ## Composite Scoring Formula ```python score = 0 if sla_overdue: score += 1000 elif sla_state == 'Due <24h': score += 500 elif sla_state == 'Due <72h': score += 200 score += (escalation or 0) * 50 prio_map = { 'Priority 1 - Critical': 400, 'Priority 2 - High': 200, 'Priority 3 - Medium': 50, 'Priority 4 - Low': 10 } score += prio_map.get(priority_name, 0) if severity in ('Critical', 'High'): score += 100 if impact in ('Critical', 'High'): score += 100 if date_entered: age_days = (now - date_entered).total_seconds() / 86400 score += min(int(age_days) * 5, 300) ``` ## SLA State Parsing ```python import re from datetime import datetime def parse_sla(sla_status_text): if not sla_status_text or sla_status_text == 'Resolved': return sla_status_text or 'No SLA' m = re.search(r'(Respond|Resolve)\s+by\s+(.+)', sla_status_text, re.IGNORECASE) if m: due_text = m.group(2).strip() try: dt = datetime.strptime(due_text, '%a %m/%d %I:%M %p %Z%z') if dt < now: return 'Overdue' hours = (dt - now).total_seconds() / 3600 if hours < 24: return 'Due <24h' elif hours < 72: return 'Due <72h' return 'On Track' except ValueError: return 'Due (unparsed)' return sla_status_text ``` ## RMM Alert Categories (for filtering / triage) Keyword-based categorization of common Sophos/RMM-generated summaries: | Category | Keywords | |----------|----------| | Firewall Sophos Offline | `firewall` + `sophos` | | Firewall Heartbeat | `not sending heartbeat` | | Gateway Down | `gateway` + `down` | | Malware Detection | `malicious`, `c2/` | | Device Offline | `has not contacted` | | Disk/Storage | `disk`, `drive`, `storage`, `c:` | | CPU Alert | `cpu` | | Memory Alert | `memory`, `ram` | | Reboot Required | `reboot`, `restart` | | AV Alert | `antivirus`, `virus` | ## Bulk Close Recipe Update stale "new from RMM" tickets to "Self-Healed": ```python # 1. Fetch all open tickets # 2. Filter: status == 'new from RMM' AND _info.dateEntered > 7 days ago # 3. PATCH each: [{"op": "replace", "path": "/status/id", "value": 425}] # Status ID 425 = "Self-Healed" on board 6 (Managed Service Alerts) ``` **Never auto-close malware detections without human review.**