CRM Support Console — Agent Skill
Environment
- Base URL: Read
environment_access.md first. If it specifies GDPEVO_ENV_BASE_URL, use that. Otherwise default to the URL in the task prompt.
- Health check:
GET /health → {ok, service}
- Catalog:
GET /api/catalog lists all endpoints and record counts (generated seed data).
API Reference
Service Tickets (internet / voice / video)
| Endpoint |
Key Fields |
GET /api/tickets/<id> |
ticket_id, account_id, service_area, service_type, subscribed_mbps, status, issue_summary |
GET /api/accounts/<id> |
account_id, name, status ("Active" |
GET /api/outages?service_area=<area> |
Array of {outage_id, active, service_area, service_types[], eta_hours, impact_score} |
GET /api/diagnostics/<ticket_id> |
bandwidth_mbps, jitter_ms, latency_ms, root_causes[] |
GET /api/troubleshooting/<ticket_id> |
steps[], post_bandwidth_mbps, post_jitter_ms, post_latency_ms |
Mobile Cases
| Endpoint |
Key Fields |
GET /api/cases/<case_id> |
case_id, customer_id, line_id, device_id, issue_type, customer_location |
GET /api/lines/<line_id> |
line_id, customer_id, device_id, plan_id, status ("Active" |
GET /api/devices/<device_id> |
sim_status, mobile_data_enabled, phone_roaming_enabled, data_saver_mode, vpn_connected, network_mode_preference, signal_strength, speed_test, messaging_permissions{sms,storage}, airplane_mode, can_send_mms, mmsc_url_present, wifi_calling_enabled, model |
GET /api/plans/<plan_id> |
data_limit_gb, data_refueling_price_per_gb, monthly_price_usd |
GET /api/bills (list) |
bill_id, customer_id, amount_due_usd, status ("Paid" |
GET /api/customers (list) |
customer_id, name, phone_number, status |
Enterprise
| Endpoint |
Key Fields |
GET /api/enterprise/incidents/<id> |
incident_id, enterprise_account_id, severity, engineering_owner, account_owner, product, status, summary |
GET /api/enterprise/export-runs?incident_id=<id> |
Array of {run_date, status, failure_code, exported_record_count} |
GET /api/enterprise/messages?query=<text> |
Array of {message_id, author, body, channel, created_at} |
GET /api/enterprise/sla/<ent_account_id> |
credit_trigger, monthly_export_credit_percent, executive_contact |
GET /api/enterprise/accounts (list) |
enterprise_account_id, name, account_owner, finance_owner, tier |
Decision SOPs
SOP A: Offline Ticket Batch Resolution (train_001 style)
Per ticket, follow this decision tree in order:
Step 1 — Account Validation
GET /api/accounts/<account_id>
- 404 →
final_resolution_status: "FAILED", resolution_route: "INVALID_ACCOUNT", all issue flags false, no diagnostic
- status = "Suspended" →
"FAILED", "INELIGIBLE_ACCOUNT", all issue flags false, no diagnostic
- auth failure (
last_login_status = "FAILURE" or account_recovery_status = "FAILURE") → "FAILED", "AUTH_FAILED", all false, no diagnostic
Step 2 — Outage Check
GET /api/outages?service_area=<ticket.service_area> (or scan GET /api/outages for matching service_area)
- If an active outage covers the account's
service_area AND service_type → "PENDING_ACTION", "OUTAGE_WAIT", populate outage_id, all issue flags false, no diagnostic needed
Step 3 — Diagnostic
GET /api/diagnostics/<ticket_id>
- Set
diagnostic_needed: true
- Set issue flags using thresholds:
latency_issue: latency_ms > 80
stability_issue: jitter_ms > 20
bandwidth_issue: bandwidth_mbps < subscribed_mbps
GET /api/troubleshooting/<ticket_id>
Step 4 — Resolution Decision
- Auto-troubleshooting succeeds: post-troubleshooting metrics show significant improvement AND root causes are software/config fixable (CONFIGURATION_DRIFT, VOICE_PROFILE_STALE, etc.) →
"RESOLVED", "AUTO_TROUBLESHOOTING", escalation_team: "NONE"
- Auto-troubleshooting insufficient: post metrics still below thresholds AND root causes indicate physical/network damage (FIBER_DROP_DAMAGE, SIGNAL_LOSS, BACKBONE_CAPACITY, PROVISIONING_STALE) →
"ESCALATED", "ESCALATION"
- FIBER_DROP_DAMAGE / SIGNAL_LOSS →
escalation_team: "FIELD_OPS"
- BACKBONE_CAPACITY →
escalation_team: "NETWORK_ENGINEERING"
- PROVISIONING_STALE →
escalation_team: "TIER2_SUPPORT"
SOP B: Queue Quality Review (train_004 style)
Uses key_blocker instead of per-issue booleans.
| Condition |
status |
route_team |
key_blocker |
diagnostic_required |
| Account 404 |
FAILED |
NONE |
INVALID_ACCOUNT |
false |
| Auth FAILURE |
FAILED |
NONE |
AUTH_FAILED |
false |
| Account Suspended |
FAILED |
ACCOUNTS_PAYABLE |
OVERDUE_SUSPENSION |
false |
| Active outage matches area+type |
PENDING_ACTION |
NONE |
ACTIVE_OUTAGE |
false |
| Root cause BACKBONE_CAPACITY, ts can't fix |
ESCALATED |
NETWORK_ENGINEERING |
NETWORK_CAPACITY |
true |
| Root cause PROVISIONING_STALE, ts can't fix |
ESCALATED |
TIER2_SUPPORT |
PROVISIONING_STALE |
true |
| Root cause fixable by auto-ts, post metrics improved |
RESOLVED |
NONE |
NONE |
true |
How to determine "ts can fix" vs "ts can't fix": Compare post-troubleshooting metrics to thresholds. If post metrics are still poor (e.g., post bandwidth far below subscribed, or post latency still >100), troubleshooting was insufficient → escalate. Root causes like BACKBONE_CAPACITY and PROVISIONING_STALE are typically beyond auto-ts capability.
SOP C: Mobile Case Queue (train_002 style)
Per case, query the case, then its line, device, plan, and bills to find the customer's bill.
| Situation |
primary_action |
secondary_action |
final_route |
Notes |
sim_status: "missing" + no service |
RESEAT_SIM |
NO_ACTION |
SELF_SERVICE |
|
Line suspended (suspension_reason: "OVERDUE_BILL") |
SEND_PAYMENT_REQUEST |
RESUME_LINE_REBOOT |
BILLING_RECOVERY |
Find overdue bill for customer_id → bill_id, charge_amount_usd |
Abroad + phone_roaming_enabled: false (line roaming ON) |
TOGGLE_ROAMING |
NO_ACTION |
SELF_SERVICE |
Device-side toggle |
can_send_mms: false with messaging_permissions.storage: false |
GRANT_MESSAGING_PERMISSION |
NO_ACTION |
SELF_SERVICE |
permission: "storage" |
Slow data + vpn_connected: true |
DISCONNECT_VPN |
NO_ACTION |
SELF_SERVICE |
|
All self-service cases → permission: "NONE", bill_id: "", charge_amount_usd: 0.0 |
|
|
|
|
Bill lookup for suspended lines: GET /api/bills → filter by customer_id, pick the one with status: "Overdue". Use its bill_id and amount_due_usd.
SOP D: Mobile Data Recovery (train_005 style)
| Situation |
primary_action |
final_route |
carrier_update |
Charge |
Data over plan limit (data_used_gb > data_limit_gb) + customer accepted refuel |
REFUEL_DATA |
DATA_RECOVERY |
false |
refuel_gb × price_per_gb (from plan) |
Abroad + line.roaming_enabled: false + device.phone_roaming_enabled: true |
ENABLE_LINE_ROAMING |
CARRIER_UPDATE |
true |
0.00 |
data_saver_mode: true + slow data |
TOGGLE_DATA_SAVER |
DEVICE_SETTING_FIX |
false |
0.00 |
network_mode_preference: "3g_only" + slow data |
SET_NETWORK_MODE |
DEVICE_SETTING_FIX |
false |
0.00 |
mobile_data_enabled: false + no data |
TOGGLE_MOBILE_DATA |
DEVICE_SETTING_FIX |
false |
0.00 |
Carrier update required: Only true for ENABLE_LINE_ROAMING (line-side roaming change). Device-side roaming toggle (TOGGLE_ROAMING) does NOT need carrier update.
Distinction TOGGLE_ROAMING vs ENABLE_LINE_ROAMING:
- Device has roaming off, line has roaming on → TOGGLE_ROAMING (device-side, no carrier update)
- Device has roaming on, line has roaming off → ENABLE_LINE_ROAMING (line-side, carrier update required)
SOP E: Enterprise Export Complaint Response (train_003 style)
GET /api/enterprise/incidents/<incident_id> → severity, owners, enterprise_account_id
GET /api/enterprise/export-runs?incident_id=<id> → identify failure window, backfill days, root cause from failure_code
GET /api/enterprise/messages?query=<company or incident keywords> → find root cause context; check for alert-archive channel messages
GET /api/enterprise/sla/<enterprise_account_id> → SLA credit percent
GET /api/enterprise/accounts → find account owner, finance owner, company name
Root cause category: Map failure_code to human-readable category. STALE_CREDENTIAL → "stale credential after rotation".
Contributing alert issue: If any message's channel is "export-alerts-archive" → "ARCHIVED_ALERT_ROUTE". Otherwise → "NONE".
Failure window: From export runs: min(run_date) where status=FAILED to max(run_date) where status=FAILED. failed_days = count of FAILED runs.
Backfill days: Same as failed_days (each failed day needs one backfill day).
SLA credit: Use monthly_export_credit_percent from SLA endpoint.
Naming conventions (use actual company name from enterprise account):
channel_name: lowercase-hyphen company name (e.g., "Asteri Retail Inc." → "asteri-retail-inc")
evidence_folder: "<Company> <Month Year> Investigation" (infer month/year from incident received_at or run dates)
report_title: "<Company> Export Failure - Resolution Report"
Share permissions: From response_requirements.permission_users_to_include (preserve order). Assign "view" to finance-related users, "edit" to engineering/ops users.
Response status: If SLA credit > 0 → "NEEDS_FINANCE_REVIEW". Otherwise → "READY_TO_SEND" (default).
Output Conventions
Always
- Preserve input order in arrays (ticket list order, case_id ascending order).
- Return only JSON conforming exactly to the provided answer template.
- Empty string
"" for absent IDs (outage_id, bill_id), not null.
"NONE" string for absent enum team/permission values, not empty string or null.
- Numeric fields: charge amounts with exactly 2 decimals; data_gb with 1 decimal; integers without decimals.
- Booleans:
true/false (JSON literals), never strings.
Batch summaries
- Counts must be integers matching the decision arrays exactly.
tickets_requiring_customer_wait: count of items where resolution requires customer to wait (OUTAGE_WAIT, billing recovery, human transfer). Not simply PENDING_ACTION count — only those where the customer must wait for an external resolution.
Common Pitfalls
- Outage priority: Always check outages BEFORE running diagnostics. If an active outage covers the service area and type, skip diagnostics entirely — the ticket is PENDING_ACTION/OUTAGE_WAIT.
- Account-not-found (404): The account_id in the ticket may be bogus (e.g., "BAD-5403"). Treat 404 as INVALID_ACCOUNT → FAILED, no diagnostic.
- Auth failure vs Suspension: Both are pre-check failures but map differently. Auth failure → AUTH_FAILED. Suspension → OVERDUE_SUSPENSION or INELIGIBLE_ACCOUNT.
- Post-troubleshooting evaluation: Don't assume troubleshooting always works. Compare post metrics to thresholds. If still failing, escalate.
- TOGGLE_ROAMING vs ENABLE_LINE_ROAMING: Check BOTH
line.roaming_enabled AND device.phone_roaming_enabled. The disabled side determines the action and whether a carrier update is needed.
- Bill matching: Match bills by
customer_id, not case_id or line_id. Pick the one with status: "Overdue".
- Missing diagnostics/troubleshooting: If a ticket is blocked pre-check (outage, invalid account, auth failure), diagnostics and troubleshooting may still exist but should NOT be fetched — the pre-check result governs.
- Enterprise message search: Try multiple search terms (company name, incident ID, "credential", product name). Messages may be sparse.
- Timestamp/month inference: Use the incident
received_at or export run dates to determine the month/year for folder/report naming.
- Precision: charge_amount_usd requires exactly 2 decimal places; data_refuel_gb requires exactly 1 decimal place. Use
0.0 / 0.00 not 0.
1---2name: fewshot-attempt-01-303description: CRM Support Console — Agent Skill4---5# CRM Support Console — Agent Skill67## Environment8- **Base URL**: Read `environment_access.md` first. If it specifies `GDPEVO_ENV_BASE_URL`, use that. Otherwise default to the URL in the task prompt.9- **Health check**: `GET /health` → `{ok, service}`10- **Catalog**: `GET /api/catalog` lists all endpoints and record counts (generated seed data).1112---1314## API Reference1516### Service Tickets (internet / voice / video)17| Endpoint | Key Fields |18|---|---|19| `GET /api/tickets/<id>` | `ticket_id`, `account_id`, `service_area`, `service_type`, `subscribed_mbps`, `status`, `issue_summary` |20| `GET /api/accounts/<id>` | `account_id`, `name`, `status` ("Active"|"Suspended"), `tier`, `service_area`, `authentication.last_login_status` ("SUCCESS"|"FAILURE"), `authentication.account_recovery_status` ("" or "FAILURE") |21| `GET /api/outages?service_area=<area>` | Array of `{outage_id, active, service_area, service_types[], eta_hours, impact_score}` |22| `GET /api/diagnostics/<ticket_id>` | `bandwidth_mbps`, `jitter_ms`, `latency_ms`, `root_causes[]` |23| `GET /api/troubleshooting/<ticket_id>` | `steps[]`, `post_bandwidth_mbps`, `post_jitter_ms`, `post_latency_ms` |2425### Mobile Cases26| Endpoint | Key Fields |27|---|---|28| `GET /api/cases/<case_id>` | `case_id`, `customer_id`, `line_id`, `device_id`, `issue_type`, `customer_location` |29| `GET /api/lines/<line_id>` | `line_id`, `customer_id`, `device_id`, `plan_id`, `status` ("Active"|"Suspended"), `suspension_reason`, `roaming_enabled`, `data_used_gb` |30| `GET /api/devices/<device_id>` | `sim_status`, `mobile_data_enabled`, `phone_roaming_enabled`, `data_saver_mode`, `vpn_connected`, `network_mode_preference`, `signal_strength`, `speed_test`, `messaging_permissions{sms,storage}`, `airplane_mode`, `can_send_mms`, `mmsc_url_present`, `wifi_calling_enabled`, `model` |31| `GET /api/plans/<plan_id>` | `data_limit_gb`, `data_refueling_price_per_gb`, `monthly_price_usd` |32| `GET /api/bills` (list) | `bill_id`, `customer_id`, `amount_due_usd`, `status` ("Paid"|"Overdue"), `due_date` |33| `GET /api/customers` (list) | `customer_id`, `name`, `phone_number`, `status` |3435### Enterprise36| Endpoint | Key Fields |37|---|---|38| `GET /api/enterprise/incidents/<id>` | `incident_id`, `enterprise_account_id`, `severity`, `engineering_owner`, `account_owner`, `product`, `status`, `summary` |39| `GET /api/enterprise/export-runs?incident_id=<id>` | Array of `{run_date, status, failure_code, exported_record_count}` |40| `GET /api/enterprise/messages?query=<text>` | Array of `{message_id, author, body, channel, created_at}` |41| `GET /api/enterprise/sla/<ent_account_id>` | `credit_trigger`, `monthly_export_credit_percent`, `executive_contact` |42| `GET /api/enterprise/accounts` (list) | `enterprise_account_id`, `name`, `account_owner`, `finance_owner`, `tier` |4344---4546## Decision SOPs4748### SOP A: Offline Ticket Batch Resolution (train_001 style)4950Per ticket, follow this decision tree in order:5152**Step 1 — Account Validation**53- `GET /api/accounts/<account_id>`54- **404** → `final_resolution_status: "FAILED"`, `resolution_route: "INVALID_ACCOUNT"`, all issue flags false, no diagnostic55- **status = "Suspended"** → `"FAILED"`, `"INELIGIBLE_ACCOUNT"`, all issue flags false, no diagnostic56- **auth failure** (`last_login_status = "FAILURE"` or `account_recovery_status = "FAILURE"`) → `"FAILED"`, `"AUTH_FAILED"`, all false, no diagnostic5758**Step 2 — Outage Check**59- `GET /api/outages?service_area=<ticket.service_area>` (or scan `GET /api/outages` for matching `service_area`)60- If an **active** outage covers the account's `service_area` AND `service_type` → `"PENDING_ACTION"`, `"OUTAGE_WAIT"`, populate `outage_id`, all issue flags false, no diagnostic needed6162**Step 3 — Diagnostic**63- `GET /api/diagnostics/<ticket_id>`64- Set `diagnostic_needed: true`65- Set issue flags using thresholds:66 - `latency_issue`: `latency_ms > 80`67 - `stability_issue`: `jitter_ms > 20`68 - `bandwidth_issue`: `bandwidth_mbps < subscribed_mbps`69- `GET /api/troubleshooting/<ticket_id>`7071**Step 4 — Resolution Decision**72- **Auto-troubleshooting succeeds**: post-troubleshooting metrics show significant improvement AND root causes are software/config fixable (CONFIGURATION_DRIFT, VOICE_PROFILE_STALE, etc.) → `"RESOLVED"`, `"AUTO_TROUBLESHOOTING"`, `escalation_team: "NONE"`73- **Auto-troubleshooting insufficient**: post metrics still below thresholds AND root causes indicate physical/network damage (FIBER_DROP_DAMAGE, SIGNAL_LOSS, BACKBONE_CAPACITY, PROVISIONING_STALE) → `"ESCALATED"`, `"ESCALATION"`74 - FIBER_DROP_DAMAGE / SIGNAL_LOSS → `escalation_team: "FIELD_OPS"`75 - BACKBONE_CAPACITY → `escalation_team: "NETWORK_ENGINEERING"`76 - PROVISIONING_STALE → `escalation_team: "TIER2_SUPPORT"`7778### SOP B: Queue Quality Review (train_004 style)7980Uses `key_blocker` instead of per-issue booleans.8182| Condition | status | route_team | key_blocker | diagnostic_required |83|---|---|---|---|---|84| Account 404 | FAILED | NONE | INVALID_ACCOUNT | false |85| Auth FAILURE | FAILED | NONE | AUTH_FAILED | false |86| Account Suspended | FAILED | ACCOUNTS_PAYABLE | OVERDUE_SUSPENSION | false |87| Active outage matches area+type | PENDING_ACTION | NONE | ACTIVE_OUTAGE | false |88| Root cause BACKBONE_CAPACITY, ts can't fix | ESCALATED | NETWORK_ENGINEERING | NETWORK_CAPACITY | true |89| Root cause PROVISIONING_STALE, ts can't fix | ESCALATED | TIER2_SUPPORT | PROVISIONING_STALE | true |90| Root cause fixable by auto-ts, post metrics improved | RESOLVED | NONE | NONE | true |9192**How to determine "ts can fix" vs "ts can't fix"**: Compare post-troubleshooting metrics to thresholds. If post metrics are still poor (e.g., post bandwidth far below subscribed, or post latency still >100), troubleshooting was insufficient → escalate. Root causes like BACKBONE_CAPACITY and PROVISIONING_STALE are typically beyond auto-ts capability.9394### SOP C: Mobile Case Queue (train_002 style)9596Per case, query the case, then its line, device, plan, and bills to find the customer's bill.9798| Situation | primary_action | secondary_action | final_route | Notes |99|---|---|---|---|---|100| `sim_status: "missing"` + no service | RESEAT_SIM | NO_ACTION | SELF_SERVICE | |101| Line suspended (`suspension_reason: "OVERDUE_BILL"`) | SEND_PAYMENT_REQUEST | RESUME_LINE_REBOOT | BILLING_RECOVERY | Find overdue bill for customer_id → `bill_id`, `charge_amount_usd` |102| Abroad + `phone_roaming_enabled: false` (line roaming ON) | TOGGLE_ROAMING | NO_ACTION | SELF_SERVICE | Device-side toggle |103| `can_send_mms: false` with `messaging_permissions.storage: false` | GRANT_MESSAGING_PERMISSION | NO_ACTION | SELF_SERVICE | `permission: "storage"` |104| Slow data + `vpn_connected: true` | DISCONNECT_VPN | NO_ACTION | SELF_SERVICE | |105| All self-service cases → `permission: "NONE"`, `bill_id: ""`, `charge_amount_usd: 0.0` | | | | |106107**Bill lookup for suspended lines**: `GET /api/bills` → filter by `customer_id`, pick the one with `status: "Overdue"`. Use its `bill_id` and `amount_due_usd`.108109### SOP D: Mobile Data Recovery (train_005 style)110111| Situation | primary_action | final_route | carrier_update | Charge |112|---|---|---|---|---|113| Data over plan limit (`data_used_gb > data_limit_gb`) + customer accepted refuel | REFUEL_DATA | DATA_RECOVERY | false | `refuel_gb × price_per_gb` (from plan) |114| Abroad + `line.roaming_enabled: false` + `device.phone_roaming_enabled: true` | ENABLE_LINE_ROAMING | CARRIER_UPDATE | **true** | 0.00 |115| `data_saver_mode: true` + slow data | TOGGLE_DATA_SAVER | DEVICE_SETTING_FIX | false | 0.00 |116| `network_mode_preference: "3g_only"` + slow data | SET_NETWORK_MODE | DEVICE_SETTING_FIX | false | 0.00 |117| `mobile_data_enabled: false` + no data | TOGGLE_MOBILE_DATA | DEVICE_SETTING_FIX | false | 0.00 |118119**Carrier update required**: Only true for ENABLE_LINE_ROAMING (line-side roaming change). Device-side roaming toggle (TOGGLE_ROAMING) does NOT need carrier update.120121**Distinction TOGGLE_ROAMING vs ENABLE_LINE_ROAMING**:122- Device has roaming off, line has roaming on → TOGGLE_ROAMING (device-side, no carrier update)123- Device has roaming on, line has roaming off → ENABLE_LINE_ROAMING (line-side, carrier update required)124125### SOP E: Enterprise Export Complaint Response (train_003 style)1261271. `GET /api/enterprise/incidents/<incident_id>` → severity, owners, enterprise_account_id1282. `GET /api/enterprise/export-runs?incident_id=<id>` → identify failure window, backfill days, root cause from `failure_code`1293. `GET /api/enterprise/messages?query=<company or incident keywords>` → find root cause context; check for alert-archive channel messages1304. `GET /api/enterprise/sla/<enterprise_account_id>` → SLA credit percent1315. `GET /api/enterprise/accounts` → find account owner, finance owner, company name132133**Root cause category**: Map `failure_code` to human-readable category. `STALE_CREDENTIAL` → "stale credential after rotation".134135**Contributing alert issue**: If any message's `channel` is `"export-alerts-archive"` → `"ARCHIVED_ALERT_ROUTE"`. Otherwise → `"NONE"`.136137**Failure window**: From export runs: `min(run_date)` where status=FAILED to `max(run_date)` where status=FAILED. `failed_days` = count of FAILED runs.138139**Backfill days**: Same as `failed_days` (each failed day needs one backfill day).140141**SLA credit**: Use `monthly_export_credit_percent` from SLA endpoint.142143**Naming conventions** (use actual company name from enterprise account):144- `channel_name`: lowercase-hyphen company name (e.g., "Asteri Retail Inc." → "asteri-retail-inc")145- `evidence_folder`: `"<Company> <Month Year> Investigation"` (infer month/year from incident received_at or run dates)146- `report_title`: `"<Company> Export Failure - Resolution Report"`147148**Share permissions**: From `response_requirements.permission_users_to_include` (preserve order). Assign `"view"` to finance-related users, `"edit"` to engineering/ops users.149150**Response status**: If SLA credit > 0 → `"NEEDS_FINANCE_REVIEW"`. Otherwise → `"READY_TO_SEND"` (default).151152---153154## Output Conventions155156### Always157- Preserve input order in arrays (ticket list order, case_id ascending order).158- Return **only JSON** conforming exactly to the provided answer template.159- Empty string `""` for absent IDs (outage_id, bill_id), not `null`.160- `"NONE"` string for absent enum team/permission values, not empty string or null.161- Numeric fields: charge amounts with exactly 2 decimals; data_gb with 1 decimal; integers without decimals.162- Booleans: `true`/`false` (JSON literals), never strings.163164### Batch summaries165- Counts must be integers matching the decision arrays exactly.166- `tickets_requiring_customer_wait`: count of items where resolution requires customer to wait (OUTAGE_WAIT, billing recovery, human transfer). Not simply `PENDING_ACTION` count — only those where the customer must wait for an external resolution.167168---169170## Common Pitfalls1711721. **Outage priority**: Always check outages BEFORE running diagnostics. If an active outage covers the service area and type, skip diagnostics entirely — the ticket is PENDING_ACTION/OUTAGE_WAIT.1732. **Account-not-found (404)**: The account_id in the ticket may be bogus (e.g., "BAD-5403"). Treat 404 as INVALID_ACCOUNT → FAILED, no diagnostic.1743. **Auth failure vs Suspension**: Both are pre-check failures but map differently. Auth failure → AUTH_FAILED. Suspension → OVERDUE_SUSPENSION or INELIGIBLE_ACCOUNT.1754. **Post-troubleshooting evaluation**: Don't assume troubleshooting always works. Compare post metrics to thresholds. If still failing, escalate.1765. **TOGGLE_ROAMING vs ENABLE_LINE_ROAMING**: Check BOTH `line.roaming_enabled` AND `device.phone_roaming_enabled`. The disabled side determines the action and whether a carrier update is needed.1776. **Bill matching**: Match bills by `customer_id`, not `case_id` or `line_id`. Pick the one with `status: "Overdue"`.1787. **Missing diagnostics/troubleshooting**: If a ticket is blocked pre-check (outage, invalid account, auth failure), diagnostics and troubleshooting may still exist but should NOT be fetched — the pre-check result governs.1798. **Enterprise message search**: Try multiple search terms (company name, incident ID, "credential", product name). Messages may be sparse.1809. **Timestamp/month inference**: Use the incident `received_at` or export run dates to determine the month/year for folder/report naming.18110. **Precision**: charge_amount_usd requires exactly 2 decimal places; data_refuel_gb requires exactly 1 decimal place. Use `0.0` / `0.00` not `0`.