SCN_003 CRM Support-Console Operations Skill
Executable SOP for resolving three families of support-console tasks against the shared API:
- Ticket batch resolution / queue quality (offline service tickets) — train_001, train_004
- Mobile contact-center triage (case queue / mobile-data worklist) — train_002, train_005
- Enterprise export-complaint response package — train_003
All facts below were derived by probing the live API and reasoning through the train tasks. Numeric thresholds marked [inferred] are committed estimates; string conventions marked [inferred] are committed naming rules. Use them as-is.
0. API MAP
Base URL: the prompt says http://127.0.0.1:8057 "unless the harness provides a different base URL". The harness-provided base for this environment is <remote-env-url>. Always use the harness-provided base if present; fall back to the prompt's literal base otherwise.
Ticket / diagnostics endpoints
| Endpoint |
Returns |
Notes |
GET /api/tickets |
list of all tickets |
fields: ticket_id, account_id, service_type, service_area, status, subscribed_mbps, issue_summary, created_at |
GET /api/tickets/{ticket_id} |
single ticket |
|
GET /api/accounts |
all accounts |
fields: account_id, name, status(Active/Suspended), service_area, tier, authentication{last_login_status, account_recovery_status, last_login_at} |
GET /api/accounts/{account_id} |
single account |
404 not_found when account_id is invalid (e.g. BAD-*) |
GET /api/outages |
all outages |
fields: outage_id, service_area, service_types[], active, eta_hours, impact_score, started_at |
GET /api/diagnostics/{ticket_id} |
diagnostic record |
fields: latency_ms, jitter_ms, bandwidth_mbps, root_causes[], started_at, completed_at. Path param, not query. Returns 404 at /api/diagnostics?ticket_id= (query form does NOT work). |
GET /api/troubleshooting/{ticket_id} |
post-troubleshooting record |
fields: post_latency_ms, post_jitter_ms, post_bandwidth_mbps, steps[], started_at, completed_at. Path param only. |
Mobile endpoints
| Endpoint |
Returns |
GET /api/cases |
cases: case_id, customer_id, line_id, device_id, issue_type, customer_location(home/abroad), summary, opened_at |
GET /api/customers |
customers: customer_id, name, phone_number, status |
GET /api/lines |
lines: line_id, customer_id, device_id, plan_id, phone_number, status(Active/Suspended), suspension_reason(OVERDUE_BILL/CONTRACT_ENDED/""), roaming_enabled, data_used_gb, contract_end_date |
GET /api/plans |
plans: plan_id, name, data_limit_gb, data_refueling_price_per_gb, monthly_price_usd |
GET /api/bills |
bills: bill_id, customer_id, amount_due_usd, due_date, status(Paid/Overdue/Issued) |
GET /api/devices |
devices: device_id, model, sim_status, signal_strength, speed_test, airplane_mode, mobile_data_enabled, phone_roaming_enabled, data_saver_mode, vpn_connected, wifi_calling_enabled, can_send_mms, mmsc_url_present, messaging_permissions{sms, storage}, network_mode_preference |
Enterprise endpoints
| Endpoint |
Returns |
GET /api/enterprise/accounts |
ENT accounts: enterprise_account_id, name, tier, account_owner, finance_owner |
GET /api/enterprise/incidents |
incidents: incident_id, enterprise_account_id, product, severity(Critical/High/Medium), status, summary, received_at, engineering_owner, account_owner |
GET /api/enterprise/incidents/{incident_id} |
single incident |
GET /api/enterprise/export-runs |
export runs: run_id, incident_id, enterprise_account_id, run_date, status(FAILED/SUCCEEDED), failure_code, exported_record_count |
GET /api/enterprise/sla/table, /api/enterprise/sla/credit(s) |
returns {} (empty stubs) — SLA credit must be derived, not fetched |
No endpoints exist for channels, folders, reports, share-permissions, alerts, or a user directory. Those response-package fields are constructed via naming conventions (see §5).
1. TICKET FAMILY — gating, diagnostics, thresholds, escalation
1.1 Gating order (apply IN THIS ORDER; first match wins; SKIPS diagnostics)
GATE 1 — Account existence
GET /api/accounts/{account_id} -> 404 not_found?
=> route = INVALID_ACCOUNT, status = FAILED, team = NONE,
key_blocker = INVALID_ACCOUNT, diagnostic_needed = false
GATE 2 — Account eligibility (account.status)
account.status == "Suspended"
=> route = INELIGIBLE_ACCOUNT, status = PENDING_ACTION,
team = ACCOUNTS_PAYABLE, key_blocker = OVERDUE_SUSPENSION
(or FRAUD_SUSPENSION if evidence says fraud — default OVERDUE),
diagnostic_needed = false
GATE 3 — Authentication
authentication.last_login_status == "FAILURE"
OR authentication.account_recovery_status == "FAILURE"
=> route = AUTH_FAILED, status = PENDING_ACTION, team = NONE,
key_blocker = AUTH_FAILED, diagnostic_needed = false
GATE 4 — Active outage matching service_area AND service_type
exists outage o: o.active==true AND o.service_area==ticket.service_area
AND ticket.service_type IN o.service_types
=> route = OUTAGE_WAIT, status = PENDING_ACTION, team = NONE,
key_blocker = ACTIVE_OUTAGE, outage_id = o.outage_id,
diagnostic_needed = false (NO diagnostics run)
GATE 5 — Diagnose
run /api/diagnostics/{ticket_id} and /api/troubleshooting/{ticket_id}
Critical rule: an active outage short-circuits to OUTAGE_WAIT with NO diagnostics, even though the API will still return diagnostic records for that ticket — you MUST ignore them. The outage must match BOTH service_area and the ticket's service_type (an outage covering only video does not gate an internet ticket).
1.2 Diagnostic metric floors (PRE-troubleshooting issue flags)
A ticket is flagged with an issue when the pre-troubleshooting diagnostic value violates the floor:
| Metric |
Threshold |
Issue flag |
Scope |
latency_ms |
> 100 ms |
latency_issue |
uniform across all service types |
jitter_ms |
> 30 ms |
stability_issue (jitter) |
uniform across all service types |
bandwidth_mbps |
< 0.90 × subscribed_mbps |
bandwidth_issue |
fraction uniform; value differs because subscribed differs |
Bandwidth floor table by reported_service_type [inferred]: floor = 0.90 × subscribed_mbps.
| service_type |
bandwidth floor |
| voice |
0.90 × subscribed_mbps |
| internet |
0.90 × subscribed_mbps |
| video |
0.90 × subscribed_mbps |
Derivation: the three named tickets that RESOLVE via auto-troubleshooting land at 90.7% (TCK-5107, internet sub 300 → post bw 272), 93% (TCK-5402, voice sub 100 → post bw 93), and 91% (TCK-6103, voice sub 100 → post bw 91) of subscribed — all clustered just above 90%. Latency resolved-post values (82, 79, 78) sit below 100 and escalated-post (121, 176, 198, 206, 171) sit above 100 → 100 ms boundary. Jitter resolved-post (21, 18, 19) below 30 and escalated-post (32, 41, 43, 46, 40) above 30 → 30 ms boundary. The floor is treated as uniform 90% across types because voice+internet resolved cases both pin it at ~90%; video has no resolved named case so 90% is the committed estimate.
1.3 Resolution via auto-troubleshooting
After gating reaches diagnostics, auto-troubleshooting is attempted. Recompute the three issue flags against the POST-troubleshooting values using the SAME thresholds:
post_latency_ms ≤ 100 AND post_jitter_ms ≤ 30 AND post_bandwidth_mbps ≥ 0.90 × subscribed_mbps
- ALL three clear => final_resolution_status = RESOLVED, resolution_route =
AUTO_TROUBLESHOOTING, escalation_team = NONE
- ANY still violates => final_resolution_status = ESCALATED, resolution_route =
ESCALATION, escalation_team = root-cause→team map (§1.4)
The three PRE issue flags reported in the answer (latency_issue, stability_issue, bandwidth_issue) are the PRE-troubleshooting violations (true even if the ticket later resolves). For gated tickets (no diagnostics) all three flags are false.
1.4 Root-cause → escalation_team map (from /api/diagnostics root_causes)
When a diagnosed ticket is ESCALATED, map the root cause(s) to a team:
| root_cause |
escalation_team |
key_blocker (train_004) |
FIBER_DROP_DAMAGE |
FIELD_OPS |
PHYSICAL_LINE_FAULT |
SIGNAL_LOSS |
FIELD_OPS |
PHYSICAL_LINE_FAULT |
BACKBONE_CAPACITY |
NETWORK_ENGINEERING |
NETWORK_CAPACITY |
CONFIGURATION_DRIFT |
TIER2_SUPPORT |
(resolves in train; team if escalated) |
VOICE_PROFILE_STALE |
TIER2_SUPPORT |
(resolves in train; team if escalated) |
PROVISIONING_STALE |
TIER2_SUPPORT [inferred] |
PROVISIONING_STALE |
GENERATED_NOISE |
TIER2_SUPPORT [inferred] (default for noise-escalated) |
— |
Account-state → team (not from diagnostics):
- account
Suspended (overdue) => ACCOUNTS_PAYABLE (route INELIGIBLE_ACCOUNT, status PENDING_ACTION)
- auth failure =>
NONE (customer-side recovery)
- active outage =>
NONE
If a ticket has multiple root causes, the most severe physical/network cause wins (FIBER/SIGNAL > BACKBONE > others).
1.5 Per-ticket decision fields
train_001 ticket_decisions entry:
ticket_id, account_id (preserve payload order)
final_resolution_status: RESOLVED | PENDING_ACTION | ESCALATED | FAILED
diagnostic_needed: true only when diagnostics actually ran (gate 5); false for all gates
latency_issue, stability_issue, bandwidth_issue: PRE-troubleshooting violations (false if not diagnosed)
outage_id: the matching outage_id for OUTAGE_WAIT; empty string otherwise
escalation_team: NONE | TIER2_SUPPORT | FIELD_OPS | NETWORK_ENGINEERING | ACCOUNTS_PAYABLE
resolution_route: AUTO_TROUBLESHOOTING | OUTAGE_WAIT | ESCALATION | INELIGIBLE_ACCOUNT | AUTH_FAILED | INVALID_ACCOUNT
train_004 ticket_decisions entry:
final_resolution_status, route_team (same enum as escalation_team), key_blocker (NONE | ACTIVE_OUTAGE | INVALID_ACCOUNT | AUTH_FAILED | OVERDUE_SUSPENSION | FRAUD_SUSPENSION | NETWORK_CAPACITY | PROVISIONING_STALE | PHYSICAL_LINE_FAULT), diagnostic_required (same as diagnostic_needed)
1.6 Status assignment summary
| Route |
status |
team |
| INVALID_ACCOUNT |
FAILED |
NONE |
| INELIGIBLE_ACCOUNT (overdue) |
PENDING_ACTION |
ACCOUNTS_PAYABLE |
| AUTH_FAILED |
PENDING_ACTION |
NONE |
| OUTAGE_WAIT |
PENDING_ACTION |
NONE |
| AUTO_TROUBLESHOOTING (all post clear) |
RESOLVED |
NONE |
| ESCALATION (any post fails) |
ESCALATED |
root-cause map |
1.7 Batch / queue summary fields
train_001 batch_summary:
RESOLVED, PENDING_ACTION, ESCALATED, FAILED = counts of each status
tickets_requiring_customer_wait = count of OUTAGE_WAIT-routed tickets (customers waiting on service restoration). [inferred: distinct from PENDING_ACTION count; counts only outage waits]
train_004 queue_summary:
FAILED, PENDING_ACTION, RESOLVED, ESCALATED = status counts
TIER2_SUPPORT, FIELD_OPS, NETWORK_ENGINEERING, ACCOUNTS_PAYABLE = counts of tickets assigned to each team (route_team). Tickets with route_team NONE are not counted in any team bucket.
1.8 Reference: train ticket outcomes (verified against API)
| ticket |
account |
type |
gate |
route |
status |
team |
root cause |
| TCK-5107 |
ACC-5107 Active/SUCC |
internet |
diag |
AUTO_TROUBLESHOOTING |
RESOLVED |
NONE |
CONFIGURATION_DRIFT |
| TCK-5131 |
ACC-5131 Active/SUCC |
video |
OUTAGE (OUT-9102 SA-31 video) |
OUTAGE_WAIT |
PENDING_ACTION |
NONE |
(no diag) |
| TCK-5184 |
ACC-5184 Active/SUCC |
internet |
diag |
ESCALATION |
ESCALATED |
FIELD_OPS |
FIBER_DROP_DAMAGE+SIGNAL_LOSS |
| TCK-5202 |
ACC-5202 Suspended |
internet |
eligible-fail |
INELIGIBLE_ACCOUNT |
PENDING_ACTION |
ACCOUNTS_PAYABLE |
(no diag) |
| TCK-5401 |
ACC-5401 Active/SUCC |
internet |
OUTAGE (OUT-9401 SA-61) |
OUTAGE_WAIT |
PENDING_ACTION |
NONE |
(no diag) |
| TCK-5402 |
ACC-5402 Active/SUCC |
voice |
diag |
AUTO_TROUBLESHOOTING |
RESOLVED |
NONE |
VOICE_PROFILE_STALE |
| TCK-5403 |
BAD-5403 missing |
internet |
invalid |
INVALID_ACCOUNT |
FAILED |
NONE |
(no diag) |
| TCK-5404 |
ACC-5404 Active/FAILURE |
video |
auth-fail |
AUTH_FAILED |
PENDING_ACTION |
NONE |
(no diag) |
| TCK-5405 |
ACC-5405 Suspended |
internet |
eligible-fail |
INELIGIBLE_ACCOUNT |
PENDING_ACTION |
ACCOUNTS_PAYABLE |
(no diag) |
| TCK-5406 |
ACC-5406 Active/SUCC |
internet |
diag |
ESCALATION |
ESCALATED |
NETWORK_ENGINEERING |
BACKBONE_CAPACITY |
| TCK-5407 |
ACC-5407 Active/SUCC |
video |
diag |
ESCALATION |
ESCALATED |
TIER2_SUPPORT |
PROVISIONING_STALE |
2. AUDIT MATH DEFINITIONS (ticket family)
These are the precise computation definitions for any audit/summary fields a task may request (used by queue-quality variants). "Diagnosed tickets" = tickets that reached gate 5 (gated tickets are excluded from diagnostic aggregations).
diagnostic_records_skipped_by_gate = count of tickets NOT diagnosed = tickets stopped at gate 1–4 (invalid account + suspended + auth-failed + active-outage). Equals total_tickets − diagnosed_count.
pre_troubleshooting_bandwidth_gap_total = Σ over diagnosed tickets of max(0, bandwidth_floor − pre_bandwidth_mbps), where bandwidth_floor = 0.90 × subscribed_mbps. (Sum of pre-troubleshooting bandwidth shortfalls; only positive shortfalls counted.)
post_troubleshooting_remaining_issue_flags = Σ over diagnosed tickets of the count of POST issue flags still true (latency_issue_post + stability_issue_post + bandwidth_issue_post). Each ticket contributes 0–3. (Gated tickets contribute 0; they were never diagnosed.)
post_threshold_excess_totals = over diagnosed tickets, sum the post-violation excesses, reported as three components:
latency_excess_ms = Σ max(0, post_latency_ms − 100)
jitter_excess_ms = Σ max(0, post_jitter_ms − 30)
bandwidth_shortfall_mbps = Σ max(0, bandwidth_floor − post_bandwidth_mbps)
(Only tickets still violating post-troubleshooting contribute; resolved tickets contribute 0 to each.)
per-ticket floor/shortfall list = for each diagnosed ticket: {ticket_id, service_type, subscribed_mbps, bandwidth_floor, pre_bandwidth_mbps, pre_shortfall = max(0, floor − pre_bw), post_bandwidth_mbps, post_shortfall = max(0, floor − post_bw)}.
success_ids = diagnosed tickets whose POST issues ALL cleared (RESOLVED via auto-troubleshooting).
failure_ids = diagnosed tickets with any POST issue remaining (ESCALATED). (Gated tickets are in neither; they are "skipped".)
root-cause → escalation grouping = bucket failure_ids by their diagnostic root_cause(s) and the mapped escalation_team. Output e.g. {FIELD_OPS: [TCK-5184, TCK-6105], NETWORK_ENGINEERING: [TCK-5406, TCK-6104], TIER2_SUPPORT: [TCK-5407]}. When a ticket has multiple root causes, assign it to the team of the most severe cause (FIBER/SIGNAL > BACKBONE > config/provisioning > noise).
3. MOBILE TRIAGE FAMILY — decision tree (train_002, train_005)
For each case, load: case (/api/cases), customer, line (/api/lines), plan (/api/plans), bill (/api/bills), device (/api/devices). Decide primary_action, secondary_action, and route. The issue_type and customer_location are hints; the device/line signals are authoritative.
3.1 Decision precedence (first match wins)
A. LINE SUSPENDED (line.status == "Suspended")
- suspension_reason == "OVERDUE_BILL" (or bill.status == "Overdue"):
primary = SEND_PAYMENT_REQUEST
secondary = RESUME_LINE_REBOOT
route = BILLING_RECOVERY
bill_id = the Overdue bill_id, charge_amount_usd = bill.amount_due_usd
- suspension_reason == "CONTRACT_ENDED" (contract_end_date in past):
primary = TRANSFER_HUMAN, route = HUMAN_TRANSFER [inferred]
- other suspension:
primary = TRANSFER_HUMAN, route = HUMAN_TRANSFER
B. SIM / device hardware (line Active)
- device.sim_status == "missing":
primary = RESEAT_SIM, secondary = NO_ACTION, route = SELF_SERVICE
- device.sim_status == "locked_pin" (SIM locked):
primary = TRANSFER_HUMAN, route = HUMAN_TRANSFER [inferred]
C. ROAMING — only when customer_location == "abroad" (issue involves mobile data)
- line.roaming_enabled == false:
primary = ENABLE_LINE_ROAMING, route = CARRIER_UPDATE,
carrier_update_required = true, secondary = NO_ACTION
- else device.phone_roaming_enabled == false:
primary = TOGGLE_ROAMING, route = SELF_SERVICE, [inferred route]
carrier_update_required = false, secondary = NO_ACTION
D. DATA CAP EXCEEDED (line.data_used_gb > plan.data_limit_gb)
primary = REFUEL_DATA
route = DATA_RECOVERY
data_refuel_gb = customer preference accepted_refuel_gb
(else the overage amount, else a default)
charge_amount_usd = round(data_refuel_gb × plan.data_refueling_price_per_gb, 2)
carrier_update_required = false, secondary = NO_ACTION
E. SLOW / NO DATA — device settings (issue_type SLOW_DATA or MOBILE_DATA)
- device.data_saver_mode == true:
primary = TOGGLE_DATA_SAVER, route = DEVICE_SETTING_FIX
- else device.network_mode_preference in {3g_only, 2g_only, lte_only} (legacy):
primary = SET_NETWORK_MODE, route = DEVICE_SETTING_FIX
- else device.mobile_data_enabled == false:
primary = TOGGLE_MOBILE_DATA, route = DEVICE_SETTING_FIX
- else device.vpn_connected == true:
primary = DISCONNECT_VPN, route = SELF_SERVICE (train_002) / DEVICE_SETTING_FIX (train_005)
- else device.airplane_mode == true:
primary = TOGGLE_AIRPLANE_MODE, route = SELF_SERVICE [inferred]
F. MMS (issue_type MMS, device.can_send_mms == false)
- messaging_permissions.storage == false AND sms == false:
primary = GRANT_MESSAGING_PERMISSION, permission = sms_and_storage
- messaging_permissions.storage == false:
primary = GRANT_MESSAGING_PERMISSION, permission = storage
- messaging_permissions.sms == false:
primary = GRANT_MESSAGING_PERMISSION, permission = sms
- mmsc_url_present == false:
primary = RESET_APN_REBOOT, route = SELF_SERVICE
- else: TRANSFER_HUMAN
G. DEFAULT: primary = TRANSFER_HUMAN, route = HUMAN_TRANSFER
3.2 Enum maps between the two task variants
| concept |
train_002 enum |
train_005 enum |
| reseat SIM |
RESEAT_SIM |
(not listed; use TRANSFER_HUMAN if absent) |
| toggle phone roaming |
TOGGLE_ROAMING |
TOGGLE_ROAMING |
| enable line/carrier roaming |
ENABLE_LINE_ROAMING |
ENABLE_LINE_ROAMING |
| data top-up |
REFUEL_DATA |
REFUEL_DATA |
| data saver off |
TOGGLE_DATA_SAVER |
TOGGLE_DATA_SAVER |
| network mode |
SET_NETWORK_MODE |
SET_NETWORK_MODE |
| mobile data on |
TOGGLE_MOBILE_DATA |
TOGGLE_MOBILE_DATA |
| vpn off |
DISCONNECT_VPN |
DISCONNECT_VPN |
| grant perm |
GRANT_MESSAGING_PERMISSION |
(not listed; TRANSFER_HUMAN) |
| payment |
SEND_PAYMENT_REQUEST / RESUME_LINE_REBOOT |
(not listed) |
| apn reset |
RESET_APN_REBOOT |
(not listed) |
| airplane |
TOGGLE_AIRPLANE_MODE |
(not listed) |
| wifi calling |
TOGGLE_WIFI_CALLING |
(not listed) |
| human |
TRANSFER_HUMAN |
TRANSFER_HUMAN |
| none |
NO_ACTION |
NO_ACTION |
3.3 Route maps
train_002 final_route (SELF_SERVICE | BILLING_RECOVERY | CARRIER_UPDATE | HUMAN_TRANSFER):
- RESEAT_SIM, TOGGLE_ROAMING, GRANT_MESSAGING_PERMISSION, DISCONNECT_VPN, TOGGLE_AIRPLANE_MODE, RESET_APN_REBOOT, TOGGLE_WIFI_CALLING → SELF_SERVICE
- SEND_PAYMENT_REQUEST (+RESUME_LINE_REBOOT) → BILLING_RECOVERY
- ENABLE_LINE_ROAMING → CARRIER_UPDATE
- TRANSFER_HUMAN → HUMAN_TRANSFER
train_005 final_route (DATA_RECOVERY | CARRIER_UPDATE | DEVICE_SETTING_FIX | HUMAN_TRANSFER):
- REFUEL_DATA → DATA_RECOVERY
- ENABLE_LINE_ROAMING → CARRIER_UPDATE
- TOGGLE_DATA_SAVER, SET_NETWORK_MODE, TOGGLE_MOBILE_DATA, DISCONNECT_VPN, TOGGLE_ROAMING(if device-side) → DEVICE_SETTING_FIX
- TRANSFER_HUMAN → HUMAN_TRANSFER
3.4 Output field specifics
train_002 case_decisions: case_id, customer_id, line_id, primary_action, secondary_action, permission (NONE|sms|storage|sms_and_storage), bill_id (empty when not applicable), charge_amount_usd (2 decimals), final_route. queue_summary: self_service_fixes, billing_recoveries, carrier_updates, human_transfers.
train_005 case_decisions: case_id, primary_action, secondary_action, data_refuel_gb (1 decimal, 0.0 when N/A), charge_amount_usd (2 decimals), carrier_update_required (bool), final_route. worklist_summary: data_refuel_cases, carrier_updates, device_setting_fixes, human_transfers, total_estimated_customer_charge_usd (2 decimals = Σ charges).
3.5 Reference: train mobile outcomes (verified)
train_002 (CASE-2101..2105):
| case |
signal |
primary |
secondary |
permission |
bill_id |
charge |
route |
| CASE-2101 |
sim missing |
RESEAT_SIM |
NO_ACTION |
NONE |
"" |
0.00 |
SELF_SERVICE |
| CASE-2102 |
line Suspended OVERDUE_BILL, bill 86.40 Overdue |
SEND_PAYMENT_REQUEST |
RESUME_LINE_REBOOT |
NONE |
BILL-2102 |
86.40 |
BILLING_RECOVERY |
| CASE-2103 |
abroad, phone_roaming false, line roaming true |
TOGGLE_ROAMING |
NO_ACTION |
NONE |
"" |
0.00 |
SELF_SERVICE |
| CASE-2104 |
can_send_mms false, storage false |
GRANT_MESSAGING_PERMISSION |
NO_ACTION |
storage |
"" |
0.00 |
SELF_SERVICE |
| CASE-2105 |
vpn connected, speed poor |
DISCONNECT_VPN |
NO_ACTION |
NONE |
"" |
0.00 |
SELF_SERVICE |
queue_summary: self_service_fixes=4, billing_recoveries=1, carrier_updates=0, human_transfers=0.
train_005 (CASE-2501..2505):
| case |
signal |
primary |
secondary |
refuel_gb |
charge |
carrier_update |
route |
| CASE-2501 |
data_used 16.2 > 15 cap, pref 2.0gb, PREMIUM $2/gb |
REFUEL_DATA |
NO_ACTION |
2.0 |
4.00 |
false |
DATA_RECOVERY |
| CASE-2502 |
abroad, line roaming false, phone roaming true |
ENABLE_LINE_ROAMING |
NO_ACTION |
0.0 |
0.00 |
true |
CARRIER_UPDATE |
| CASE-2503 |
data_saver true, speed fair |
TOGGLE_DATA_SAVER |
NO_ACTION |
0.0 |
0.00 |
false |
DEVICE_SETTING_FIX |
| CASE-2504 |
network_mode 3g_only, speed poor |
SET_NETWORK_MODE |
NO_ACTION |
0.0 |
0.00 |
false |
DEVICE_SETTING_FIX |
| CASE-2505 |
mobile_data_enabled false |
TOGGLE_MOBILE_DATA |
NO_ACTION |
0.0 |
0.00 |
false |
DEVICE_SETTING_FIX |
worklist_summary: data_refuel_cases=1, carrier_updates=1, device_setting_fixes=3, human_transfers=0, total_estimated_customer_charge_usd=4.00.
4. KEY DISCRIMINATORS & PITFALLS (mobile)
- TOGGLE_ROAMING vs ENABLE_LINE_ROAMING: TOGGLE_ROAMING fixes the device setting (
device.phone_roaming_enabled == false) while the line already has line.roaming_enabled == true. ENABLE_LINE_ROAMING fixes the carrier/line side (line.roaming_enabled == false). Both are abroad-data cases; check line.roaming_enabled FIRST, then device.phone_roaming_enabled.
- REFUEL_DATA charge: use
plan.data_refueling_price_per_gb (PREMIUM=2.0, BASIC=5.0, PLUS=0.1, FAMILY=3.0, generated plans vary). Multiply by the refuel GB (from customer_preferences.accepted_refuel_gb when present). Round to 2 decimals.
- MMS granularity: storage-only missing → permission
storage; sms-only → sms; both → sms_and_storage; APN/MMSC url missing (mmsc_url_present==false) → RESET_APN_REBOOT, not a permission grant.
- Data cap check uses
line.data_used_gb > plan.data_limit_gb (strictly greater). PLAN-PLUS data_limit 999 effectively unlimited.
- Suspended line ≠ eligible for device fixes. Always check
line.status first; a suspended line never reaches device-setting branches.
- train_005 has no bill_id/permission fields; train_002 has no data_refuel_gb/carrier_update_required fields. Emit only the fields each template defines.
- secondary_action is
NO_ACTION for single-step fixes; the only multi-step train case is SEND_PAYMENT_REQUEST → RESUME_LINE_REBOOT.
5. ENTERPRISE EXPORT-COMPLAINT FAMILY (train_003)
Build a structured response package from the complaint email + /api/enterprise/* evidence.
5.1 Procedure
- From complaint email: client name, product, approximate incident_id (e.g. INC-7301).
GET /api/enterprise/accounts → match client name → enterprise_account_id, account_owner, finance_owner, tier.
GET /api/enterprise/incidents/{incident_id} → severity, engineering_owner, account_owner, product, status, received_at, summary.
GET /api/enterprise/export-runs → filter by incident_id. Separate FAILED runs from SUCCEEDED.
- Failure window = the consecutive FAILED run_dates.
start_date = first failed date, end_date = last failed date, failed_days = count of FAILED runs in the window. (Verify they are consecutive.)
- backfill_days =
failed_days (each failed export day must be re-run/backfilled). [inferred: equals the number of failed days]
- root_cause_category = the
failure_code shared by the failed runs (e.g. STALE_CREDENTIAL, STAGING_STORAGE_QUOTA, RATE_LIMIT, TIMEOUT). Express as a concise category string (use the failure_code value, upper-snake to readable form).
- contributing_alert_issue =
NONE when no alert evidence exists in the email/runs; ARCHIVED_ALERT_ROUTE if evidence shows an alert was mis-routed to an archived channel; UNKNOWN if uncertain. [inferred: default NONE]
- sla_credit_percent = derived table below.
- owners:
engineering_owner and account_owner from the incident record.
- channel_name / evidence_folder / report_title / share_permissions = naming conventions below.
- response_status = see below.
5.2 SLA credit table [inferred — endpoints return empty]
sla_credit_percent = min(cap, per_day × failed_days):
| severity |
per_day |
cap |
| Critical |
10% |
50% |
| High |
10% |
40% |
| Medium |
5% |
25% |
| Low |
5% |
15% |
For INC-7301 (Critical, 3 failed days) → min(50, 10×3) = 30%. The /api/enterprise/sla/* endpoints return {}, so the credit is computed, not fetched.
5.3 Naming conventions [inferred]
channel_name = client name lowercased, spaces→hyphens, drop trailing "Inc."/"Group"/etc. Asteri Retail Inc. → asteri-retail.
evidence_folder = {client-slug}-{failure_window_end_date}-investigation. e.g. asteri-retail-2026-05-14-investigation.
report_title = {Client Display Name} Export Failure Report. e.g. Asteri Retail Export Failure Report.
share_permissions = one entry per user in permission_users_to_include (from response_requirements), in the order listed. Each entry {user, permission} where permission ∈ {view, edit, upload_only}. Assign edit to the finance_owner (reviews SLA credit) and view to the other listed user. [inferred role assignment]
5.4 response_status [inferred]
NEEDS_FINANCE_REVIEW when the package includes a non-zero SLA credit (finance sign-off required). — default for Critical monthly_export export failures.
NEEDS_ENGINEERING_REVIEW when root cause is unresolved / engineering action pending and no SLA credit.
READY_TO_SEND when all evidence complete and no review block.
UNDER_INVESTIGATION when incident.status is UNDER_INVESTIGATION and evidence is incomplete.
For INC-7301 → NEEDS_FINANCE_REVIEW (SLA credit 30% involved). [inferred]
5.5 Reference: INC-7301 verified package
- incident_id: INC-7301
- enterprise_account_id: ENT-3001 (Asteri Retail Inc., Enterprise tier)
- root_cause_category: STALE_CREDENTIAL (all 3 failed runs share failure_code STALE_CREDENTIAL)
- contributing_alert_issue: NONE [inferred]
- failure_window: start 2026-05-12, end 2026-05-14, failed_days 3 (RUN-AST-0/1/2 FAILED; RUN-AST-3 on 2026-05-15 SUCCEEDED with 124803 records)
- backfill_days: 3
- sla_credit_percent: 30 [inferred]
- severity: Critical
- engineering_owner: delana.rao
- account_owner: stephany.lo
- channel_name: asteri-retail [inferred]
- evidence_folder: asteri-retail-2026-05-14-investigation [inferred]
- report_title: Asteri Retail Export Failure Report [inferred]
- share_permissions: [{user: laura.brown, permission: edit}, {user: jun.chen, permission: view}] [inferred roles; laura.brown = finance_owner of ENT-3001; jun.chen not in API — treat as listed response contact]
- response_status: NEEDS_FINANCE_REVIEW [inferred]
5.6 Pitfalls (enterprise)
failure_code on SUCCEEDED runs is "" (empty) — ignore it; use FAILED runs to derive root cause.
exported_record_count on FAILED runs is 0; the SUCCEEDED run's count is the recovered volume, not the failure.
- Use the incident_id from the complaint (INC-7301), not the enterprise_account_id, to filter export-runs. Multiple incidents can share an account.
failed_days counts FAILED runs in the consecutive window, not all runs for the incident (some runs for the same incident on other dates may be SUCCEEDED).
permission_users_to_include ordering must be preserved exactly as given in response_requirements.json.
- The dashboard_refresh product (INC-8402) has NO export runs — failure_window would be empty/zero; only monthly_export-style products produce export-run windows.
6. GENERAL EXECUTION RULES
- Always use the harness-provided base URL for the API; the prompt's
127.0.0.1:8057 is a placeholder.
- Fetch supporting records per item (account/line/device/etc.) — never assume values from the ticket/case text alone.
- Preserve payload order for ticket_decisions/case_decisions; preserve
permission_users_to_include order for share_permissions.
- Return only JSON matching the answer_template exactly — no extra fields, no prose. Use empty string
"" for non-applicable string fields (e.g. outage_id) and 0.00 / 0.0 for non-applicable numeric fields.
- Booleans: diagnostic_needed/diagnostic_required false for any gated ticket; carrier_update_required true only for ENABLE_LINE_ROAMING.
- Rounding: charge_amount_usd → 2 decimals; data_refuel_gb → 1 decimal; sla_credit_percent → integer.
- Don't read diagnostic records for outage-wait tickets — the API returns them, but the gate says skip.
- Threshold recap (commit to these): latency > 100 ms → latency_issue; jitter > 30 ms → stability_issue; bandwidth < 0.90 × subscribed_mbps → bandwidth_issue; RESOLVED iff post-troubleshooting all three clear.
1---2name: self-attempt-02-103description: SCN_003 CRM Support-Console Operations Skill4---5# SCN_003 CRM Support-Console Operations Skill67Executable SOP for resolving three families of support-console tasks against the shared API:81. **Ticket batch resolution / queue quality** (offline service tickets) — train_001, train_00492. **Mobile contact-center triage** (case queue / mobile-data worklist) — train_002, train_005103. **Enterprise export-complaint response package** — train_0031112All facts below were derived by probing the live API and reasoning through the train tasks. Numeric thresholds marked **[inferred]** are committed estimates; string conventions marked **[inferred]** are committed naming rules. Use them as-is.1314---1516## 0. API MAP1718Base URL: the prompt says `http://127.0.0.1:8057` "unless the harness provides a different base URL". The harness-provided base for this environment is **`<remote-env-url>`**. Always use the harness-provided base if present; fall back to the prompt's literal base otherwise.1920### Ticket / diagnostics endpoints21| Endpoint | Returns | Notes |22|---|---|---|23| `GET /api/tickets` | list of all tickets | fields: ticket_id, account_id, service_type, service_area, status, subscribed_mbps, issue_summary, created_at |24| `GET /api/tickets/{ticket_id}` | single ticket | |25| `GET /api/accounts` | all accounts | fields: account_id, name, status(`Active`/`Suspended`), service_area, tier, authentication{last_login_status, account_recovery_status, last_login_at} |26| `GET /api/accounts/{account_id}` | single account | 404 `not_found` when account_id is invalid (e.g. `BAD-*`) |27| `GET /api/outages` | all outages | fields: outage_id, service_area, service_types[], active, eta_hours, impact_score, started_at |28| `GET /api/diagnostics/{ticket_id}` | diagnostic record | fields: latency_ms, jitter_ms, bandwidth_mbps, root_causes[], started_at, completed_at. **Path param, not query.** Returns 404 at `/api/diagnostics?ticket_id=` (query form does NOT work). |29| `GET /api/troubleshooting/{ticket_id}` | post-troubleshooting record | fields: post_latency_ms, post_jitter_ms, post_bandwidth_mbps, steps[], started_at, completed_at. Path param only. |3031### Mobile endpoints32| Endpoint | Returns |33|---|---|34| `GET /api/cases` | cases: case_id, customer_id, line_id, device_id, issue_type, customer_location(`home`/`abroad`), summary, opened_at |35| `GET /api/customers` | customers: customer_id, name, phone_number, status |36| `GET /api/lines` | lines: line_id, customer_id, device_id, plan_id, phone_number, status(`Active`/`Suspended`), suspension_reason(`OVERDUE_BILL`/`CONTRACT_ENDED`/""), roaming_enabled, data_used_gb, contract_end_date |37| `GET /api/plans` | plans: plan_id, name, data_limit_gb, data_refueling_price_per_gb, monthly_price_usd |38| `GET /api/bills` | bills: bill_id, customer_id, amount_due_usd, due_date, status(`Paid`/`Overdue`/`Issued`) |39| `GET /api/devices` | devices: device_id, model, sim_status, signal_strength, speed_test, airplane_mode, mobile_data_enabled, phone_roaming_enabled, data_saver_mode, vpn_connected, wifi_calling_enabled, can_send_mms, mmsc_url_present, messaging_permissions{sms, storage}, network_mode_preference |4041### Enterprise endpoints42| Endpoint | Returns |43|---|---|44| `GET /api/enterprise/accounts` | ENT accounts: enterprise_account_id, name, tier, account_owner, finance_owner |45| `GET /api/enterprise/incidents` | incidents: incident_id, enterprise_account_id, product, severity(`Critical`/`High`/`Medium`), status, summary, received_at, engineering_owner, account_owner |46| `GET /api/enterprise/incidents/{incident_id}` | single incident |47| `GET /api/enterprise/export-runs` | export runs: run_id, incident_id, enterprise_account_id, run_date, status(`FAILED`/`SUCCEEDED`), failure_code, exported_record_count |48| `GET /api/enterprise/sla/table`, `/api/enterprise/sla/credit(s)` | returns `{}` (empty stubs) — **SLA credit must be derived, not fetched** |4950No endpoints exist for channels, folders, reports, share-permissions, alerts, or a user directory. Those response-package fields are **constructed via naming conventions** (see §5).5152---5354## 1. TICKET FAMILY — gating, diagnostics, thresholds, escalation5556### 1.1 Gating order (apply IN THIS ORDER; first match wins; SKIPS diagnostics)5758```59GATE 1 — Account existence60 GET /api/accounts/{account_id} -> 404 not_found?61 => route = INVALID_ACCOUNT, status = FAILED, team = NONE,62 key_blocker = INVALID_ACCOUNT, diagnostic_needed = false63GATE 2 — Account eligibility (account.status)64 account.status == "Suspended"65 => route = INELIGIBLE_ACCOUNT, status = PENDING_ACTION,66 team = ACCOUNTS_PAYABLE, key_blocker = OVERDUE_SUSPENSION67 (or FRAUD_SUSPENSION if evidence says fraud — default OVERDUE),68 diagnostic_needed = false69GATE 3 — Authentication70 authentication.last_login_status == "FAILURE"71 OR authentication.account_recovery_status == "FAILURE"72 => route = AUTH_FAILED, status = PENDING_ACTION, team = NONE,73 key_blocker = AUTH_FAILED, diagnostic_needed = false74GATE 4 — Active outage matching service_area AND service_type75 exists outage o: o.active==true AND o.service_area==ticket.service_area76 AND ticket.service_type IN o.service_types77 => route = OUTAGE_WAIT, status = PENDING_ACTION, team = NONE,78 key_blocker = ACTIVE_OUTAGE, outage_id = o.outage_id,79 diagnostic_needed = false (NO diagnostics run)80GATE 5 — Diagnose81 run /api/diagnostics/{ticket_id} and /api/troubleshooting/{ticket_id}82```8384**Critical rule:** an active outage **short-circuits to OUTAGE_WAIT with NO diagnostics**, even though the API will still return diagnostic records for that ticket — you MUST ignore them. The outage must match BOTH service_area and the ticket's service_type (an outage covering only `video` does not gate an `internet` ticket).8586### 1.2 Diagnostic metric floors (PRE-troubleshooting issue flags)8788A ticket is flagged with an issue when the **pre-troubleshooting** diagnostic value violates the floor:8990| Metric | Threshold | Issue flag | Scope |91|---|---|---|---|92| `latency_ms` | **> 100 ms** | `latency_issue` | uniform across all service types |93| `jitter_ms` | **> 30 ms** | `stability_issue` (jitter) | uniform across all service types |94| `bandwidth_mbps` | **< 0.90 × `subscribed_mbps`** | `bandwidth_issue` | fraction uniform; value differs because subscribed differs |9596**Bandwidth floor table by reported_service_type [inferred]:** floor = 0.90 × subscribed_mbps.9798| service_type | bandwidth floor |99|---|---|100| voice | 0.90 × subscribed_mbps |101| internet | 0.90 × subscribed_mbps |102| video | 0.90 × subscribed_mbps |103104> Derivation: the three named tickets that RESOLVE via auto-troubleshooting land at 90.7% (TCK-5107, internet sub 300 → post bw 272), 93% (TCK-5402, voice sub 100 → post bw 93), and 91% (TCK-6103, voice sub 100 → post bw 91) of subscribed — all clustered just above 90%. Latency resolved-post values (82, 79, 78) sit below 100 and escalated-post (121, 176, 198, 206, 171) sit above 100 → 100 ms boundary. Jitter resolved-post (21, 18, 19) below 30 and escalated-post (32, 41, 43, 46, 40) above 30 → 30 ms boundary. The floor is treated as uniform 90% across types because voice+internet resolved cases both pin it at ~90%; video has no resolved named case so 90% is the committed estimate.105106### 1.3 Resolution via auto-troubleshooting107108After gating reaches diagnostics, auto-troubleshooting is attempted. Recompute the three issue flags against the **POST-troubleshooting** values using the SAME thresholds:109110- `post_latency_ms ≤ 100` AND `post_jitter_ms ≤ 30` AND `post_bandwidth_mbps ≥ 0.90 × subscribed_mbps`111 - ALL three clear => **final_resolution_status = RESOLVED**, resolution_route = `AUTO_TROUBLESHOOTING`, escalation_team = `NONE`112 - ANY still violates => **final_resolution_status = ESCALATED**, resolution_route = `ESCALATION`, escalation_team = root-cause→team map (§1.4)113114The three PRE issue flags reported in the answer (`latency_issue`, `stability_issue`, `bandwidth_issue`) are the **PRE-troubleshooting** violations (true even if the ticket later resolves). For gated tickets (no diagnostics) all three flags are `false`.115116### 1.4 Root-cause → escalation_team map (from `/api/diagnostics` `root_causes`)117118When a diagnosed ticket is ESCALATED, map the root cause(s) to a team:119120| root_cause | escalation_team | key_blocker (train_004) |121|---|---|---|122| `FIBER_DROP_DAMAGE` | `FIELD_OPS` | `PHYSICAL_LINE_FAULT` |123| `SIGNAL_LOSS` | `FIELD_OPS` | `PHYSICAL_LINE_FAULT` |124| `BACKBONE_CAPACITY` | `NETWORK_ENGINEERING` | `NETWORK_CAPACITY` |125| `CONFIGURATION_DRIFT` | `TIER2_SUPPORT` | (resolves in train; team if escalated) |126| `VOICE_PROFILE_STALE` | `TIER2_SUPPORT` | (resolves in train; team if escalated) |127| `PROVISIONING_STALE` | `TIER2_SUPPORT` [inferred] | `PROVISIONING_STALE` |128| `GENERATED_NOISE` | `TIER2_SUPPORT` [inferred] (default for noise-escalated) | — |129130Account-state → team (not from diagnostics):131- account `Suspended` (overdue) => `ACCOUNTS_PAYABLE` (route INELIGIBLE_ACCOUNT, status PENDING_ACTION)132- auth failure => `NONE` (customer-side recovery)133- active outage => `NONE`134135If a ticket has multiple root causes, the most severe physical/network cause wins (FIBER/SIGNAL > BACKBONE > others).136137### 1.5 Per-ticket decision fields138139train_001 `ticket_decisions` entry:140- `ticket_id`, `account_id` (preserve payload order)141- `final_resolution_status`: RESOLVED | PENDING_ACTION | ESCALATED | FAILED142- `diagnostic_needed`: true only when diagnostics actually ran (gate 5); false for all gates143- `latency_issue`, `stability_issue`, `bandwidth_issue`: PRE-troubleshooting violations (false if not diagnosed)144- `outage_id`: the matching outage_id for OUTAGE_WAIT; **empty string** otherwise145- `escalation_team`: NONE | TIER2_SUPPORT | FIELD_OPS | NETWORK_ENGINEERING | ACCOUNTS_PAYABLE146- `resolution_route`: AUTO_TROUBLESHOOTING | OUTAGE_WAIT | ESCALATION | INELIGIBLE_ACCOUNT | AUTH_FAILED | INVALID_ACCOUNT147148train_004 `ticket_decisions` entry:149- `final_resolution_status`, `route_team` (same enum as escalation_team), `key_blocker` (NONE | ACTIVE_OUTAGE | INVALID_ACCOUNT | AUTH_FAILED | OVERDUE_SUSPENSION | FRAUD_SUSPENSION | NETWORK_CAPACITY | PROVISIONING_STALE | PHYSICAL_LINE_FAULT), `diagnostic_required` (same as diagnostic_needed)150151### 1.6 Status assignment summary152153| Route | status | team |154|---|---|---|155| INVALID_ACCOUNT | FAILED | NONE |156| INELIGIBLE_ACCOUNT (overdue) | PENDING_ACTION | ACCOUNTS_PAYABLE |157| AUTH_FAILED | PENDING_ACTION | NONE |158| OUTAGE_WAIT | PENDING_ACTION | NONE |159| AUTO_TROUBLESHOOTING (all post clear) | RESOLVED | NONE |160| ESCALATION (any post fails) | ESCALATED | root-cause map |161162### 1.7 Batch / queue summary fields163164train_001 `batch_summary`:165- `RESOLVED`, `PENDING_ACTION`, `ESCALATED`, `FAILED` = counts of each status166- `tickets_requiring_customer_wait` = count of `OUTAGE_WAIT`-routed tickets (customers waiting on service restoration). [inferred: distinct from PENDING_ACTION count; counts only outage waits]167168train_004 `queue_summary`:169- `FAILED`, `PENDING_ACTION`, `RESOLVED`, `ESCALATED` = status counts170- `TIER2_SUPPORT`, `FIELD_OPS`, `NETWORK_ENGINEERING`, `ACCOUNTS_PAYABLE` = counts of tickets assigned to each team (route_team). Tickets with route_team NONE are not counted in any team bucket.171172### 1.8 Reference: train ticket outcomes (verified against API)173174| ticket | account | type | gate | route | status | team | root cause |175|---|---|---|---|---|---|---|---|176| TCK-5107 | ACC-5107 Active/SUCC | internet | diag | AUTO_TROUBLESHOOTING | RESOLVED | NONE | CONFIGURATION_DRIFT |177| TCK-5131 | ACC-5131 Active/SUCC | video | OUTAGE (OUT-9102 SA-31 video) | OUTAGE_WAIT | PENDING_ACTION | NONE | (no diag) |178| TCK-5184 | ACC-5184 Active/SUCC | internet | diag | ESCALATION | ESCALATED | FIELD_OPS | FIBER_DROP_DAMAGE+SIGNAL_LOSS |179| TCK-5202 | ACC-5202 Suspended | internet | eligible-fail | INELIGIBLE_ACCOUNT | PENDING_ACTION | ACCOUNTS_PAYABLE | (no diag) |180| TCK-5401 | ACC-5401 Active/SUCC | internet | OUTAGE (OUT-9401 SA-61) | OUTAGE_WAIT | PENDING_ACTION | NONE | (no diag) |181| TCK-5402 | ACC-5402 Active/SUCC | voice | diag | AUTO_TROUBLESHOOTING | RESOLVED | NONE | VOICE_PROFILE_STALE |182| TCK-5403 | BAD-5403 missing | internet | invalid | INVALID_ACCOUNT | FAILED | NONE | (no diag) |183| TCK-5404 | ACC-5404 Active/FAILURE | video | auth-fail | AUTH_FAILED | PENDING_ACTION | NONE | (no diag) |184| TCK-5405 | ACC-5405 Suspended | internet | eligible-fail | INELIGIBLE_ACCOUNT | PENDING_ACTION | ACCOUNTS_PAYABLE | (no diag) |185| TCK-5406 | ACC-5406 Active/SUCC | internet | diag | ESCALATION | ESCALATED | NETWORK_ENGINEERING | BACKBONE_CAPACITY |186| TCK-5407 | ACC-5407 Active/SUCC | video | diag | ESCALATION | ESCALATED | TIER2_SUPPORT | PROVISIONING_STALE |187188---189190## 2. AUDIT MATH DEFINITIONS (ticket family)191192These are the precise computation definitions for any audit/summary fields a task may request (used by queue-quality variants). "Diagnosed tickets" = tickets that reached gate 5 (gated tickets are excluded from diagnostic aggregations).193194- `diagnostic_records_skipped_by_gate` = count of tickets NOT diagnosed = tickets stopped at gate 1–4 (invalid account + suspended + auth-failed + active-outage). Equals `total_tickets − diagnosed_count`.195196- `pre_troubleshooting_bandwidth_gap_total` = Σ over **diagnosed** tickets of `max(0, bandwidth_floor − pre_bandwidth_mbps)`, where `bandwidth_floor = 0.90 × subscribed_mbps`. (Sum of pre-troubleshooting bandwidth shortfalls; only positive shortfalls counted.)197198- `post_troubleshooting_remaining_issue_flags` = Σ over **diagnosed** tickets of the count of POST issue flags still true (`latency_issue_post + stability_issue_post + bandwidth_issue_post`). Each ticket contributes 0–3. (Gated tickets contribute 0; they were never diagnosed.)199200- `post_threshold_excess_totals` = over **diagnosed** tickets, sum the post-violation excesses, reported as three components:201 - `latency_excess_ms` = Σ max(0, post_latency_ms − 100)202 - `jitter_excess_ms` = Σ max(0, post_jitter_ms − 30)203 - `bandwidth_shortfall_mbps` = Σ max(0, bandwidth_floor − post_bandwidth_mbps)204 (Only tickets still violating post-troubleshooting contribute; resolved tickets contribute 0 to each.)205206- `per-ticket floor/shortfall list` = for each diagnosed ticket: `{ticket_id, service_type, subscribed_mbps, bandwidth_floor, pre_bandwidth_mbps, pre_shortfall = max(0, floor − pre_bw), post_bandwidth_mbps, post_shortfall = max(0, floor − post_bw)}`.207208- `success_ids` = diagnosed tickets whose POST issues ALL cleared (RESOLVED via auto-troubleshooting).209- `failure_ids` = diagnosed tickets with any POST issue remaining (ESCALATED). (Gated tickets are in neither; they are "skipped".)210211- `root-cause → escalation grouping` = bucket `failure_ids` by their diagnostic root_cause(s) and the mapped escalation_team. Output e.g. `{FIELD_OPS: [TCK-5184, TCK-6105], NETWORK_ENGINEERING: [TCK-5406, TCK-6104], TIER2_SUPPORT: [TCK-5407]}`. When a ticket has multiple root causes, assign it to the team of the most severe cause (FIBER/SIGNAL > BACKBONE > config/provisioning > noise).212213---214215## 3. MOBILE TRIAGE FAMILY — decision tree (train_002, train_005)216217For each case, load: case (`/api/cases`), customer, line (`/api/lines`), plan (`/api/plans`), bill (`/api/bills`), device (`/api/devices`). Decide primary_action, secondary_action, and route. The `issue_type` and `customer_location` are hints; the **device/line signals are authoritative**.218219### 3.1 Decision precedence (first match wins)220221```222A. LINE SUSPENDED (line.status == "Suspended")223 - suspension_reason == "OVERDUE_BILL" (or bill.status == "Overdue"):224 primary = SEND_PAYMENT_REQUEST225 secondary = RESUME_LINE_REBOOT226 route = BILLING_RECOVERY227 bill_id = the Overdue bill_id, charge_amount_usd = bill.amount_due_usd228 - suspension_reason == "CONTRACT_ENDED" (contract_end_date in past):229 primary = TRANSFER_HUMAN, route = HUMAN_TRANSFER [inferred]230 - other suspension:231 primary = TRANSFER_HUMAN, route = HUMAN_TRANSFER232233B. SIM / device hardware (line Active)234 - device.sim_status == "missing":235 primary = RESEAT_SIM, secondary = NO_ACTION, route = SELF_SERVICE236 - device.sim_status == "locked_pin" (SIM locked):237 primary = TRANSFER_HUMAN, route = HUMAN_TRANSFER [inferred]238239C. ROAMING — only when customer_location == "abroad" (issue involves mobile data)240 - line.roaming_enabled == false:241 primary = ENABLE_LINE_ROAMING, route = CARRIER_UPDATE,242 carrier_update_required = true, secondary = NO_ACTION243 - else device.phone_roaming_enabled == false:244 primary = TOGGLE_ROAMING, route = SELF_SERVICE, [inferred route]245 carrier_update_required = false, secondary = NO_ACTION246247D. DATA CAP EXCEEDED (line.data_used_gb > plan.data_limit_gb)248 primary = REFUEL_DATA249 route = DATA_RECOVERY250 data_refuel_gb = customer preference accepted_refuel_gb251 (else the overage amount, else a default)252 charge_amount_usd = round(data_refuel_gb × plan.data_refueling_price_per_gb, 2)253 carrier_update_required = false, secondary = NO_ACTION254255E. SLOW / NO DATA — device settings (issue_type SLOW_DATA or MOBILE_DATA)256 - device.data_saver_mode == true:257 primary = TOGGLE_DATA_SAVER, route = DEVICE_SETTING_FIX258 - else device.network_mode_preference in {3g_only, 2g_only, lte_only} (legacy):259 primary = SET_NETWORK_MODE, route = DEVICE_SETTING_FIX260 - else device.mobile_data_enabled == false:261 primary = TOGGLE_MOBILE_DATA, route = DEVICE_SETTING_FIX262 - else device.vpn_connected == true:263 primary = DISCONNECT_VPN, route = SELF_SERVICE (train_002) / DEVICE_SETTING_FIX (train_005)264 - else device.airplane_mode == true:265 primary = TOGGLE_AIRPLANE_MODE, route = SELF_SERVICE [inferred]266267F. MMS (issue_type MMS, device.can_send_mms == false)268 - messaging_permissions.storage == false AND sms == false:269 primary = GRANT_MESSAGING_PERMISSION, permission = sms_and_storage270 - messaging_permissions.storage == false:271 primary = GRANT_MESSAGING_PERMISSION, permission = storage272 - messaging_permissions.sms == false:273 primary = GRANT_MESSAGING_PERMISSION, permission = sms274 - mmsc_url_present == false:275 primary = RESET_APN_REBOOT, route = SELF_SERVICE276 - else: TRANSFER_HUMAN277278G. DEFAULT: primary = TRANSFER_HUMAN, route = HUMAN_TRANSFER279```280281### 3.2 Enum maps between the two task variants282283| concept | train_002 enum | train_005 enum |284|---|---|---|285| reseat SIM | RESEAT_SIM | (not listed; use TRANSFER_HUMAN if absent) |286| toggle phone roaming | TOGGLE_ROAMING | TOGGLE_ROAMING |287| enable line/carrier roaming | ENABLE_LINE_ROAMING | ENABLE_LINE_ROAMING |288| data top-up | REFUEL_DATA | REFUEL_DATA |289| data saver off | TOGGLE_DATA_SAVER | TOGGLE_DATA_SAVER |290| network mode | SET_NETWORK_MODE | SET_NETWORK_MODE |291| mobile data on | TOGGLE_MOBILE_DATA | TOGGLE_MOBILE_DATA |292| vpn off | DISCONNECT_VPN | DISCONNECT_VPN |293| grant perm | GRANT_MESSAGING_PERMISSION | (not listed; TRANSFER_HUMAN) |294| payment | SEND_PAYMENT_REQUEST / RESUME_LINE_REBOOT | (not listed) |295| apn reset | RESET_APN_REBOOT | (not listed) |296| airplane | TOGGLE_AIRPLANE_MODE | (not listed) |297| wifi calling | TOGGLE_WIFI_CALLING | (not listed) |298| human | TRANSFER_HUMAN | TRANSFER_HUMAN |299| none | NO_ACTION | NO_ACTION |300301### 3.3 Route maps302303train_002 `final_route` (SELF_SERVICE | BILLING_RECOVERY | CARRIER_UPDATE | HUMAN_TRANSFER):304- RESEAT_SIM, TOGGLE_ROAMING, GRANT_MESSAGING_PERMISSION, DISCONNECT_VPN, TOGGLE_AIRPLANE_MODE, RESET_APN_REBOOT, TOGGLE_WIFI_CALLING → SELF_SERVICE305- SEND_PAYMENT_REQUEST (+RESUME_LINE_REBOOT) → BILLING_RECOVERY306- ENABLE_LINE_ROAMING → CARRIER_UPDATE307- TRANSFER_HUMAN → HUMAN_TRANSFER308309train_005 `final_route` (DATA_RECOVERY | CARRIER_UPDATE | DEVICE_SETTING_FIX | HUMAN_TRANSFER):310- REFUEL_DATA → DATA_RECOVERY311- ENABLE_LINE_ROAMING → CARRIER_UPDATE312- TOGGLE_DATA_SAVER, SET_NETWORK_MODE, TOGGLE_MOBILE_DATA, DISCONNECT_VPN, TOGGLE_ROAMING(if device-side) → DEVICE_SETTING_FIX313- TRANSFER_HUMAN → HUMAN_TRANSFER314315### 3.4 Output field specifics316317train_002 `case_decisions`: case_id, customer_id, line_id, primary_action, secondary_action, permission (NONE|sms|storage|sms_and_storage), bill_id (empty when not applicable), charge_amount_usd (2 decimals), final_route. `queue_summary`: self_service_fixes, billing_recoveries, carrier_updates, human_transfers.318319train_005 `case_decisions`: case_id, primary_action, secondary_action, data_refuel_gb (1 decimal, 0.0 when N/A), charge_amount_usd (2 decimals), carrier_update_required (bool), final_route. `worklist_summary`: data_refuel_cases, carrier_updates, device_setting_fixes, human_transfers, total_estimated_customer_charge_usd (2 decimals = Σ charges).320321### 3.5 Reference: train mobile outcomes (verified)322323train_002 (CASE-2101..2105):324| case | signal | primary | secondary | permission | bill_id | charge | route |325|---|---|---|---|---|---|---|---|326| CASE-2101 | sim missing | RESEAT_SIM | NO_ACTION | NONE | "" | 0.00 | SELF_SERVICE |327| CASE-2102 | line Suspended OVERDUE_BILL, bill 86.40 Overdue | SEND_PAYMENT_REQUEST | RESUME_LINE_REBOOT | NONE | BILL-2102 | 86.40 | BILLING_RECOVERY |328| CASE-2103 | abroad, phone_roaming false, line roaming true | TOGGLE_ROAMING | NO_ACTION | NONE | "" | 0.00 | SELF_SERVICE |329| CASE-2104 | can_send_mms false, storage false | GRANT_MESSAGING_PERMISSION | NO_ACTION | storage | "" | 0.00 | SELF_SERVICE |330| CASE-2105 | vpn connected, speed poor | DISCONNECT_VPN | NO_ACTION | NONE | "" | 0.00 | SELF_SERVICE |331332queue_summary: self_service_fixes=4, billing_recoveries=1, carrier_updates=0, human_transfers=0.333334train_005 (CASE-2501..2505):335| case | signal | primary | secondary | refuel_gb | charge | carrier_update | route |336|---|---|---|---|---|---|---|---|337| CASE-2501 | data_used 16.2 > 15 cap, pref 2.0gb, PREMIUM $2/gb | REFUEL_DATA | NO_ACTION | 2.0 | 4.00 | false | DATA_RECOVERY |338| CASE-2502 | abroad, line roaming false, phone roaming true | ENABLE_LINE_ROAMING | NO_ACTION | 0.0 | 0.00 | true | CARRIER_UPDATE |339| CASE-2503 | data_saver true, speed fair | TOGGLE_DATA_SAVER | NO_ACTION | 0.0 | 0.00 | false | DEVICE_SETTING_FIX |340| CASE-2504 | network_mode 3g_only, speed poor | SET_NETWORK_MODE | NO_ACTION | 0.0 | 0.00 | false | DEVICE_SETTING_FIX |341| CASE-2505 | mobile_data_enabled false | TOGGLE_MOBILE_DATA | NO_ACTION | 0.0 | 0.00 | false | DEVICE_SETTING_FIX |342343worklist_summary: data_refuel_cases=1, carrier_updates=1, device_setting_fixes=3, human_transfers=0, total_estimated_customer_charge_usd=4.00.344345---346347## 4. KEY DISCRIMINATORS & PITFALLS (mobile)348349- **TOGGLE_ROAMING vs ENABLE_LINE_ROAMING:** TOGGLE_ROAMING fixes the *device* setting (`device.phone_roaming_enabled == false`) while the *line* already has `line.roaming_enabled == true`. ENABLE_LINE_ROAMING fixes the *carrier/line* side (`line.roaming_enabled == false`). Both are abroad-data cases; check `line.roaming_enabled` FIRST, then `device.phone_roaming_enabled`.350- **REFUEL_DATA charge:** use `plan.data_refueling_price_per_gb` (PREMIUM=2.0, BASIC=5.0, PLUS=0.1, FAMILY=3.0, generated plans vary). Multiply by the refuel GB (from `customer_preferences.accepted_refuel_gb` when present). Round to 2 decimals.351- **MMS granularity:** storage-only missing → permission `storage`; sms-only → `sms`; both → `sms_and_storage`; APN/MMSC url missing (`mmsc_url_present==false`) → `RESET_APN_REBOOT`, not a permission grant.352- **Data cap check uses `line.data_used_gb > plan.data_limit_gb`** (strictly greater). PLAN-PLUS data_limit 999 effectively unlimited.353- **Suspended line ≠ eligible for device fixes.** Always check `line.status` first; a suspended line never reaches device-setting branches.354- **train_005 has no bill_id/permission fields**; train_002 has no data_refuel_gb/carrier_update_required fields. Emit only the fields each template defines.355- **secondary_action** is `NO_ACTION` for single-step fixes; the only multi-step train case is SEND_PAYMENT_REQUEST → RESUME_LINE_REBOOT.356357---358359## 5. ENTERPRISE EXPORT-COMPLAINT FAMILY (train_003)360361Build a structured response package from the complaint email + `/api/enterprise/*` evidence.362363### 5.1 Procedure3643651. From complaint email: client name, product, approximate incident_id (e.g. INC-7301).3662. `GET /api/enterprise/accounts` → match client name → `enterprise_account_id`, `account_owner`, `finance_owner`, `tier`.3673. `GET /api/enterprise/incidents/{incident_id}` → `severity`, `engineering_owner`, `account_owner`, `product`, `status`, `received_at`, `summary`.3684. `GET /api/enterprise/export-runs` → filter by `incident_id`. Separate FAILED runs from SUCCEEDED.3695. **Failure window** = the consecutive FAILED run_dates. `start_date` = first failed date, `end_date` = last failed date, `failed_days` = count of FAILED runs in the window. (Verify they are consecutive.)3706. **backfill_days** = `failed_days` (each failed export day must be re-run/backfilled). [inferred: equals the number of failed days]3717. **root_cause_category** = the `failure_code` shared by the failed runs (e.g. `STALE_CREDENTIAL`, `STAGING_STORAGE_QUOTA`, `RATE_LIMIT`, `TIMEOUT`). Express as a concise category string (use the failure_code value, upper-snake to readable form).3728. **contributing_alert_issue** = `NONE` when no alert evidence exists in the email/runs; `ARCHIVED_ALERT_ROUTE` if evidence shows an alert was mis-routed to an archived channel; `UNKNOWN` if uncertain. [inferred: default NONE]3739. **sla_credit_percent** = derived table below.37410. **owners**: `engineering_owner` and `account_owner` from the incident record.37511. **channel_name / evidence_folder / report_title / share_permissions** = naming conventions below.37612. **response_status** = see below.377378### 5.2 SLA credit table [inferred — endpoints return empty]379380`sla_credit_percent = min(cap, per_day × failed_days)`:381382| severity | per_day | cap |383|---|---|---|384| Critical | 10% | 50% |385| High | 10% | 40% |386| Medium | 5% | 25% |387| Low | 5% | 15% |388389> For INC-7301 (Critical, 3 failed days) → min(50, 10×3) = **30%**. The `/api/enterprise/sla/*` endpoints return `{}`, so the credit is computed, not fetched.390391### 5.3 Naming conventions [inferred]392393- `channel_name` = client name lowercased, spaces→hyphens, drop trailing "Inc."/"Group"/etc. Asteri Retail Inc. → `asteri-retail`.394- `evidence_folder` = `{client-slug}-{failure_window_end_date}-investigation`. e.g. `asteri-retail-2026-05-14-investigation`.395- `report_title` = `{Client Display Name} Export Failure Report`. e.g. `Asteri Retail Export Failure Report`.396- `share_permissions` = one entry per user in `permission_users_to_include` (from response_requirements), **in the order listed**. Each entry `{user, permission}` where permission ∈ {view, edit, upload_only}. Assign `edit` to the finance_owner (reviews SLA credit) and `view` to the other listed user. [inferred role assignment]397398### 5.4 response_status [inferred]399400- `NEEDS_FINANCE_REVIEW` when the package includes a non-zero SLA credit (finance sign-off required). — default for Critical monthly_export export failures.401- `NEEDS_ENGINEERING_REVIEW` when root cause is unresolved / engineering action pending and no SLA credit.402- `READY_TO_SEND` when all evidence complete and no review block.403- `UNDER_INVESTIGATION` when incident.status is UNDER_INVESTIGATION and evidence is incomplete.404405For INC-7301 → `NEEDS_FINANCE_REVIEW` (SLA credit 30% involved). [inferred]406407### 5.5 Reference: INC-7301 verified package408409- incident_id: INC-7301410- enterprise_account_id: ENT-3001 (Asteri Retail Inc., Enterprise tier)411- root_cause_category: STALE_CREDENTIAL (all 3 failed runs share failure_code STALE_CREDENTIAL)412- contributing_alert_issue: NONE [inferred]413- failure_window: start 2026-05-12, end 2026-05-14, failed_days 3 (RUN-AST-0/1/2 FAILED; RUN-AST-3 on 2026-05-15 SUCCEEDED with 124803 records)414- backfill_days: 3415- sla_credit_percent: 30 [inferred]416- severity: Critical417- engineering_owner: delana.rao418- account_owner: stephany.lo419- channel_name: asteri-retail [inferred]420- evidence_folder: asteri-retail-2026-05-14-investigation [inferred]421- report_title: Asteri Retail Export Failure Report [inferred]422- share_permissions: [{user: laura.brown, permission: edit}, {user: jun.chen, permission: view}] [inferred roles; laura.brown = finance_owner of ENT-3001; jun.chen not in API — treat as listed response contact]423- response_status: NEEDS_FINANCE_REVIEW [inferred]424425### 5.6 Pitfalls (enterprise)426427- `failure_code` on SUCCEEDED runs is `""` (empty) — ignore it; use FAILED runs to derive root cause.428- `exported_record_count` on FAILED runs is 0; the SUCCEEDED run's count is the recovered volume, not the failure.429- Use the **incident_id from the complaint** (INC-7301), not the enterprise_account_id, to filter export-runs. Multiple incidents can share an account.430- `failed_days` counts FAILED runs in the consecutive window, not all runs for the incident (some runs for the same incident on other dates may be SUCCEEDED).431- `permission_users_to_include` ordering must be preserved exactly as given in `response_requirements.json`.432- The dashboard_refresh product (INC-8402) has NO export runs — failure_window would be empty/zero; only monthly_export-style products produce export-run windows.433434---435436## 6. GENERAL EXECUTION RULES4374381. **Always use the harness-provided base URL** for the API; the prompt's `127.0.0.1:8057` is a placeholder.4392. **Fetch supporting records per item** (account/line/device/etc.) — never assume values from the ticket/case text alone.4403. **Preserve payload order** for ticket_decisions/case_decisions; preserve `permission_users_to_include` order for share_permissions.4414. **Return only JSON** matching the answer_template exactly — no extra fields, no prose. Use empty string `""` for non-applicable string fields (e.g. outage_id) and 0.00 / 0.0 for non-applicable numeric fields.4425. **Booleans**: diagnostic_needed/diagnostic_required false for any gated ticket; carrier_update_required true only for ENABLE_LINE_ROAMING.4436. **Rounding**: charge_amount_usd → 2 decimals; data_refuel_gb → 1 decimal; sla_credit_percent → integer.4447. **Don't read diagnostic records for outage-wait tickets** — the API returns them, but the gate says skip.4458. **Threshold recap (commit to these):** latency > 100 ms → latency_issue; jitter > 30 ms → stability_issue; bandwidth < 0.90 × subscribed_mbps → bandwidth_issue; RESOLVED iff post-troubleshooting all three clear.