SCN_003 CRM Support-Operations Skill
Reusable SOP for resolving CRM support-console batches across three task
families: (1) ticket-batch resolution, (2) mobile triage, (3) enterprise
export-complaint response packages. All facts below are distilled from gold
answers + live API probes and are intended for a downstream solver.
0. Environment & API base
- Prompts reference
http://127.0.0.1:8057 — ALWAYS replace with the real
base URL: <remote-env-url>.
- Health:
GET /health -> {"ok": true}.
- Catalog:
GET /api/catalog (endpoints + record counts only).
- Records are the source of truth; never assume a field exists. Probe to confirm.
API endpoint map (substitute real ids)
| Resource |
Endpoint |
| Accounts |
GET /api/accounts , GET /api/accounts/<account_id> |
| Tickets |
GET /api/tickets , GET /api/tickets/<ticket_id> |
| Outages |
GET /api/outages , GET /api/outages?service_area=<area> |
| Diagnostics (pre) |
GET /api/diagnostics/<ticket_id> |
| Troubleshooting (post) |
GET /api/troubleshooting/<ticket_id> |
| Mobile customers |
GET /api/customers , GET /api/customers/<customer_id> |
| Mobile lines |
GET /api/lines , GET /api/lines/<line_id> |
| Mobile devices |
GET /api/devices/<device_id> |
| Mobile plans |
GET /api/plans , GET /api/plans/<plan_id> |
| Mobile cases / bills |
GET /api/bills , GET /api/cases , GET /api/cases/<case_id> , GET /api/bills/<bill_id> |
| Enterprise accounts |
GET /api/enterprise/accounts |
| Enterprise incidents |
GET /api/enterprise/incidents |
| Enterprise export-runs |
GET /api/enterprise/export-runs?incident_id=<incident_id> |
| Enterprise messages |
GET /api/enterprise/messages?query=<text> |
| Enterprise SLA |
GET /api/enterprise/sla/<enterprise_account_id> |
Key record shapes (probe to confirm; do not hard-code):
GET /api/tickets/<id> -> {ticket_id, account_id, service_area, service_type, subscribed_mbps, status, issue_summary, created_at}. subscribed_mbps and service_type live here and drive the bandwidth floor.
GET /api/accounts/<id> -> {account_id, name, service_area, status, tier, authentication:{last_login_status, account_recovery_status, last_login_at}}. 404 body {"error":"not_found"} means invalid account.
GET /api/diagnostics/<id> -> {ticket_id, bandwidth_mbps, latency_ms, jitter_ms, root_causes:[...], started_at, completed_at}.
GET /api/troubleshooting/<id> -> {ticket_id, post_bandwidth_mbps, post_latency_ms, post_jitter_ms, steps:[...], started_at, completed_at}.
GET /api/outages?service_area=<area> -> list of {outage_id, service_area, service_types:[...], active, eta_hours, impact_score, started_at}.
1. Family A — Ticket-batch resolution (train_001 / train_004)
Input: a CSV (ticket_batch.csv / queue_snapshot.csv) listing ticket_id, account_id, reported_service_type, customer_report/queue_note. Resolve every
ticket from console records, not from the note text alone. Output JSON per the
provided answer_template.json (two schema variants exist — see §1.6).
1.1 Gating order (apply in this exact sequence per ticket)
- Account gate (FAILED route — skip diagnostics):
a.
GET /api/accounts/<account_id> returns 404 -> INVALID_ACCOUNT.
final_resolution_status=FAILED, diagnostic_needed=false.
b. authentication.last_login_status == "FAILURE" (or
account_recovery_status == "FAILURE") -> AUTH_FAILED.
FAILED, diagnostic_needed=false.
c. status == "Suspended":
- If the ticket note/report text contains "overdue" (overdue notice) ->
OVERDUE_SUSPENSION, route team ACCOUNTS_PAYABLE (FAILED,
diagnostic_needed=false). The line-level analogue is
suspension_reason == "OVERDUE_BILL".
- Else (e.g. "account hold") -> INELIGIBLE_ACCOUNT (FAILED,
diagnostic_needed=false, no team).
- Outage gate (only after account gate passes):
GET /api/outages?service_area=<ticket.service_area>. If any outage has
active == true AND ticket.service_type ∈ outage.service_types ->
OUTAGE_WAIT: final_resolution_status=PENDING_ACTION,
diagnostic_needed=false, outage_id=<matching outage_id>. This is the
only branch that sets a non-empty outage_id.
- Diagnostics (account OK, no active outage):
diagnostic_needed=true. GET /api/diagnostics/<id> (pre) and
GET /api/troubleshooting/<id> (post). Compute pre-flags (§1.3),
then post-flags, then decide RESOLVED vs ESCALATED (§1.4).
Order matters: account/auth gates are evaluated before the outage gate, and the
outage gate before diagnostics. A gated ticket never runs diagnostics, so its
latency_issue/stability_issue/bandwidth_issue are all false and
outage_id is empty (except the OUTAGE_WAIT branch).
1.2 Flag semantics — flags are PRE-troubleshooting floor violations
latency_issue, stability_issue, bandwidth_issue are computed from the
pre-troubleshooting diagnostic record against absolute/ratio floors. They
are false for every gated ticket (no diagnostic was run).
1.3 Diagnostic floors (INFERRED — see §1.5 evidence)
| Metric |
Floor |
Issue rule (true when) |
Source field |
| latency |
100 ms |
latency_ms > 100 |
diagnostics.latency_ms |
| jitter (stability) |
30 ms |
jitter_ms > 30 |
diagnostics.jitter_ms |
| bandwidth |
0.70 × subscribed_mbps |
bandwidth_mbps < 0.70 × subscribed_mbps |
diagnostics.bandwidth_mbps vs tickets.subscribed_mbps |
Uniform across service_type (internet / video / voice all use the same
ratio + absolute floors). subscribed_mbps comes from the ticket record.
1.4 Post-troubleshooting re-check (RESOLVED vs ESCALATED)
For every diagnostic-eligible ticket, also GET /api/troubleshooting/<id> and
recompute the three flags against the SAME floors using the post_ fields
(post_bandwidth_mbps, post_latency_ms, post_jitter_ms).
- If all three post-flags are false -> RESOLVED,
resolution_route=AUTO_TROUBLESHOOTING, escalation_team=NONE.
- If any post-flag is still true -> ESCALATED,
resolution_route=ESCALATION,
escalation_team derived from diagnostics.root_causes (§1.5).
outage_id is empty string for both RESOLVED and ESCALATED.
1.5 Root-cause -> escalation-team map
Determine the team by scanning root_causes[] in priority order; first match
wins:
| Root-cause keyword (substring, case-insensitive) |
escalation_team |
(train_004 key_blocker label) |
FIBER or SIGNAL |
FIELD_OPS |
(fiber/signal label) |
BACKBONE or CAPACITY |
NETWORK_ENGINEERING |
NETWORK_CAPACITY |
PROVISIONING |
TIER2_SUPPORT |
PROVISIONING_STALE |
BILLING |
ACCOUNTS_PAYABLE |
(billing label) |
(none of the above, e.g. CONFIGURATION_DRIFT, GENERATED_NOISE, VOICE_PROFILE_STALE) |
resolves via auto-troubleshoot -> usually RESOLVED; if still failing, see note |
— |
Observed: CONFIGURATION_DRIFT and VOICE_PROFILE_STALE tickets cleared all
post-flags so they were RESOLVED (no escalation). Only fiber/signal,
backbone/capacity, and provisioning root causes survived troubleshooting and
escalated. If a non-mapped root cause still has post-flags true, default the
team by the closest category; with no match, leave FIELD_OPS as a fallback
only when a physical-layer cause is evident (weakly inferred).
1.6 Output schema variants — follow the provided answer_template.json
Variant 1 (train_001 style) — richer per-ticket decision:
ticket_decisions[]: {ticket_id, account_id, final_resolution_status,
diagnostic_needed, latency_issue, stability_issue, bandwidth_issue,
outage_id ("" when none), escalation_team, resolution_route}
batch_summary: {RESOLVED, PENDING_ACTION, ESCALATED, FAILED,
tickets_requiring_customer_wait}
resolution_route enum: AUTO_TROUBLESHOOTING | OUTAGE_WAIT | ESCALATION | INELIGIBLE_ACCOUNT | AUTH_FAILED | INVALID_ACCOUNT.
escalation_team enum: NONE | TIER2_SUPPORT | FIELD_OPS | NETWORK_ENGINEERING | ACCOUNTS_PAYABLE.
tickets_requiring_customer_wait = count of PENDING_ACTION (outage-wait)
tickets (the customer must wait for the outage to clear).
Variant 2 (train_004 style) — key_blocker + route_team:
ticket_decisions[]: {ticket_id, final_resolution_status, route_team,
key_blocker, diagnostic_required}
queue_summary: {FAILED, PENDING_ACTION, RESOLVED, ESCALATED,
TIER2_SUPPORT, FIELD_OPS, NETWORK_ENGINEERING, ACCOUNTS_PAYABLE}
key_blocker labels: ACTIVE_OUTAGE | INVALID_ACCOUNT | AUTH_FAILED | OVERDUE_SUSPENSION | NETWORK_CAPACITY | PROVISIONING_STALE | NONE
(and fiber/signal label for that branch).
route_team: NONE for resolved / outage / invalid / auth; the mapped team
for overdue + escalations.
diagnostic_required == diagnostic_needed (same boolean).
queue_summary counts: first four are status counts; the four team keys
count tickets routed to each team (FIELD_OPS=0 in train_004).
Preserve payload (CSV) order in the ticket_decisions array.
1.7 Audit math formulas (state exactly)
Let D = set of diagnostic-eligible tickets (diagnostic_needed=true), G =
gated/skipped tickets (diagnostic_needed=false), E = ESCALATED tickets
(post-flags not all clear), R = RESOLVED-via-AUTO_TROUBLESHOOTING tickets.
Floors: BW_RATIO=0.70, LAT=100, JIT=30. For ticket t: sub(t) =
subscribed_mbps, bw(t)/lat(t)/jit(t) = pre diagnostic values,
pbw(t)/plat(t)/pjit(t) = post values.
- pre_troubleshooting_bandwidth_gap_total_mbps =
Σ_{t∈D, bw(t) < 0.70·sub(t)} (0.70·sub(t) − bw(t)).
- diagnostic_records_skipped_by_gate =
|G| (tickets with
diagnostic_needed=false).
- post_troubleshooting_remaining_issue_flags =
Σ_{t∈E} [ (plat(t)>100) + (pjit(t)>30) + (pbw(t)<0.70·sub(t)) ]
(count of post-flags still true, summed over escalated tickets).
- post_threshold_excess_totals (over
E only):
- latency_excess_total =
Σ_{t∈E} max(0, plat(t) − 100)
- jitter_excess_total =
Σ_{t∈E} max(0, pjit(t) − 30)
- bandwidth_shortfall_total =
Σ_{t∈E} max(0, 0.70·sub(t) − pbw(t))
- tickets_using_post_troubleshooting_records =
|D| (every
diagnostic-eligible ticket has a troubleshooting record consumed).
- tickets_with_active_outage_match = count of tickets where an outage with
active=true, matching service_area, and service_type ∈ outage.service_types exists (i.e. the OUTAGE_WAIT branch).
- unique_escalation_teams = sorted unique
escalation_team values among
E, excluding NONE.
- root_cause_escalation_ticket_ids by team =
{team: sorted[ticket_id]}
for each team in E's escalation teams.
- post success/failure id lists:
- success (RESOLVED via AUTO_TROUBLESHOOTING) =
sorted[ticket_id for t∈R]
- failure (ESCALATED) =
sorted[ticket_id for t∈E]
- per-ticket floor + shortfall list (sorted by
ticket_id), one entry
per t∈D:
{ticket_id, subscribed_mbps, bandwidth_floor_mbps=0.70·sub, latency_floor_ms=100, jitter_floor_ms=30, pre_bandwidth_shortfall_mbps=max(0, 0.70·sub−bw), pre_latency_excess_ms=max(0, lat−100), pre_jitter_excess_ms=max(0, jit−30)}.
1.8 Worked evidence for the thresholds (INFERRED)
- Bandwidth ratio 0.70 (strong): TCK-5107
sub=300, pre bw=209 flagged
issue; 0.70·300 = 210, and 209 < 210 (off by exactly 1 — boundary test).
Post bw=272 ≥ 210 -> cleared -> RESOLVED. TCK-5184 sub=500, pre bw=318
flagged; 0.70·500=350, 318<350. Both consistent. (0.75 also fits the
two bandwidth points but cannot explain the deliberate 209-vs-210
boundary; commit to 0.70.)
- Latency floor 100 ms: RESOLVED posts 82, 79 (≤100, no issue); ESCALATED
posts 176, 198, 121 (>100, issue); pre-flagged gold 142.8, 188.4 (>100).
All consistent with
>100 = issue. Boundary untested; 100 is the canonical
degraded-latency threshold.
- Jitter floor 30 ms: RESOLVED posts 21, 18 (≤30); ESCALATED posts 41, 43,
32 (>30); pre-flagged gold 33.5, 44.2 (>30). All consistent with
>30.
(25 ms also fits all points; 30 is the canonical jitter threshold and matches
the ESCALATED post value 32 being just over — commit to 30.)
2. Family B — Mobile triage (train_002 / train_005)
Input: a JSON queue (case_queue.json / mobile_data_worklist.json) of cases
with case_id + reported_issue/summary. For each case, fetch
GET /api/cases/<case_id> to get customer_id, line_id, device_id,
issue_type, customer_location; then GET /api/lines/<line_id>,
GET /api/devices/<device_id>, GET /api/plans/<plan_id> (via line.plan_id),
and GET /api/bills/<bill_id> when a bill is referenced.
2.1 Decision tree (apply in priority order; first match wins)
- Line suspended for overdue bill —
line.status=="Suspended" and
(line.suspension_reason=="OVERDUE_BILL" or note contains "overdue"):
- primary
SEND_PAYMENT_REQUEST, secondary RESUME_LINE_REBOOT,
final_route=BILLING_RECOVERY.
bill_id = the line/customer's overdue bill; charge_amount_usd =
bill.amount_due_usd. (Found via GET /api/bills/<bill_id>; the bill id
pattern is BILL-<case_num>.)
- Data cap exceeded —
line.data_used_gb > plan.data_limit_gb:
- primary
REFUEL_DATA, final_route=DATA_RECOVERY.
data_refuel_gb = ceil(data_used_gb − data_limit_gb) (min 1.0; INFERRED
from one point: overage 1.2 -> 2.0 GB).
charge_amount_usd = data_refuel_gb × plan.data_refueling_price_per_gb.
- SIM missing —
device.sim_status=="missing":
- primary
RESEAT_SIM, final_route=SELF_SERVICE. (NO_SERVICE case after
commute.)
- Abroad + roaming gap —
case.customer_location=="abroad" and data issue:
a. line.roaming_enabled==false -> primary ENABLE_LINE_ROAMING,
carrier_update_required=true, final_route=CARRIER_UPDATE
(carrier-side; needs a carrier update).
b. else device.phone_roaming_enabled==false -> primary TOGGLE_ROAMING,
final_route=SELF_SERVICE (phone-side toggle).
- MMS / messaging permission —
device.can_send_mms==false and a
messaging_permissions key is false:
- primary
GRANT_MESSAGING_PERMISSION, permission=<the false key> (e.g.
storage), final_route=SELF_SERVICE. (mmsc_url_present should be true;
the missing permission is the blocker.)
- VPN connected —
device.vpn_connected==true (slow data):
- primary
DISCONNECT_VPN, final_route=SELF_SERVICE.
- Data-saver on —
device.data_saver_mode==true (slow data):
- primary
TOGGLE_DATA_SAVER, final_route=DEVICE_SETTING_FIX.
- Stale network mode —
device.network_mode_preference != "4g_5g_preferred"
(e.g. "3g_only", slow data):
- primary
SET_NETWORK_MODE, final_route=DEVICE_SETTING_FIX.
- Mobile data disabled —
device.mobile_data_enabled==false:
- primary
TOGGLE_MOBILE_DATA, final_route=DEVICE_SETTING_FIX.
Default secondary_action=NO_ACTION for all except the billing branch
(RESUME_LINE_REBOOT). permission="NONE" and bill_id="" unless the
branch sets them. data_refuel_gb=0.0, charge_amount_usd=0.0,
carrier_update_required=false unless the branch sets them.
issue_type reinforces branch selection but the device/line fields are
authoritative: NO_SERVICE -> sim/billing; MOBILE_DATA -> cap/roaming/data-
toggle; MMS -> permission; SLOW_DATA -> vpn/data-saver/network-mode.
2.2 Output schema variants — follow the provided answer_template.json
train_002 style (case_queue):
case_decisions[]: {case_id, customer_id, line_id, primary_action,
secondary_action, permission, bill_id, charge_amount_usd, final_route}
queue_summary: {self_service_fixes, billing_recoveries, carrier_updates,
human_transfers}
final_route values seen: SELF_SERVICE, BILLING_RECOVERY.
- summary:
self_service_fixes = count of SELF_SERVICE routes;
billing_recoveries = count of BILLING_RECOVERY; carrier_updates =
count of CARRIER_UPDATE; human_transfers = 0 (no human-transfer branch
observed).
train_005 style (mobile_data_worklist):
case_decisions[]: {case_id, primary_action, secondary_action,
data_refuel_gb, charge_amount_usd, carrier_update_required, final_route}
worklist_summary: {data_refuel_cases, carrier_updates, device_setting_fixes,
human_transfers, total_estimated_customer_charge_usd}
final_route values seen: DATA_RECOVERY, CARRIER_UPDATE,
DEVICE_SETTING_FIX.
total_estimated_customer_charge_usd = Σ charge_amount_usd across cases.
Preserve payload order. Action enums differ slightly between the two variants
(e.g. TOGGLE_ROAMING vs ENABLE_LINE_ROAMING; REFUEL_DATA) — emit the
action token the decision tree produces and that the template expects.
2.3 Plan reference (from GET /api/plans)
| plan_id |
data_limit_gb |
data_refueling_price_per_gb |
monthly_price_usd |
| PLAN-BASIC |
5.0 |
5.0 |
40.0 |
| PLAN-PREMIUM |
15.0 |
2.0 |
65.0 |
| PLAN-PLUS |
999.0 |
0.1 |
85.0 |
| (PLAN-25 / others) |
25.0 |
3.0 |
120.0 |
Always read the plan from the API; do not hard-code these.
3. Family C — Enterprise export-complaint response package (train_003)
Input: client_complaint_email.txt (client name, product, approximate
incident reference) + response_requirements.json (required_fields,
permission_users_to_include, naming_style). Identify the incident from the
email's approximate reference (e.g. INC-7301), then assemble the package.
Output JSON per answer_template.json.
3.1 SOP
- Identify incident.
GET /api/enterprise/incidents, match on
incident_id from the email (the "Approximate incident reference"). Confirm
enterprise_account_id, severity, engineering_owner, account_owner,
product, status.
- Confirm account.
GET /api/enterprise/accounts, match on
enterprise_account_id to get name, tier, finance_owner.
- Failed export-run window.
GET /api/enterprise/export-runs?incident_id=<incident_id>.
failure_window.start_date = run_date of the FIRST status=="FAILED"
run; end_date = run_date of the LAST FAILED run;
failed_days = count of FAILED runs.
backfill_days = count of FAILED runs (= failed_days).
root_cause_category = concise phrase inferred from the FAILED runs'
failure_code + the engineering message body
(GET /api/enterprise/messages?query=<client or failure keyword>).
Examples: STALE_CREDENTIAL + "credential rotation completed; scheduler
pod still references old secret" -> "stale credential after rotation";
STAGING_STORAGE_QUOTA + "staging bucket reached quota" -> quota-related
phrase. Lowercase descriptive phrase.
- SLA credit.
GET /api/enterprise/sla/<enterprise_account_id>.
sla_credit_percent = monthly_export_credit_percent WHEN the
credit_trigger condition is met by the failure window (e.g. "3
consecutive failed export runs" -> failed_days≥3; "critical export outage
longer than 72 hours" -> failed_days≥3 days). Else 0.
- If credit warranted ->
response_status = "NEEDS_FINANCE_REVIEW" (finance
must approve the credit). Other statuses: NEEDS_ENGINEERING_REVIEW if an
unresolved engineering root cause needs sign-off and no credit;
READY_TO_SEND if resolved with no credit; UNDER_INVESTIGATION if the
incident is still open and root cause unconfirmed. (Only the finance-review
branch is directly attested by gold.)
- Contributing alert issue. From the engineering root-cause message: if its
channel contains "archive" (e.g. export-alerts-archive) ->
ARCHIVED_ALERT_ROUTE; else NONE; UNKNOWN only if no message found.
- Owners.
engineering_owner and account_owner come from the incident
record. (Finance owner is used for share permissions.)
- Naming conventions (from
response_requirements.naming_style =
"lowercase hyphen channel; client-date investigation folder; client export
failure report title"), using the account name:
channel_name = client name lowercased, spaces -> hyphens
(e.g. "Asteri Retail Inc." -> asteri-retail-inc).
evidence_folder = "<Client Name> <Month Year> Investigation" where
Month Year comes from the incident received_at / failure window month
(e.g. "Asteri Retail Inc. May 2026 Investigation").
report_title = "<Client Name> Export Failure - Resolution Report".
- Share permissions. Use
permission_users_to_include from the
requirements, in the listed order. Assign "view" to the user that is the
account's finance_owner; assign "edit" to the other listed user(s).
(INFERRED from one example: laura.brown=finance_owner->view,
jun.chen->edit. Order preserved as listed in requirements.)
3.2 Output fields
incident_id, enterprise_account_id, root_cause_category, contributing_alert_issue,
failure_window {start_date, end_date, failed_days}, backfill_days,
sla_credit_percent, severity, engineering_owner, account_owner,
channel_name, evidence_folder, report_title,
share_permissions [{user, permission}], response_status
Enumerations: contributing_alert_issue ∈
ARCHIVED_ALERT_ROUTE | NONE | UNKNOWN; severity ∈
Critical | High | Medium | Low; permission ∈ view | edit | upload_only;
response_status ∈
READY_TO_SEND | NEEDS_FINANCE_REVIEW | NEEDS_ENGINEERING_REVIEW | UNDER_INVESTIGATION.
4. Pitfalls & conventions (all families)
- Base URL: never use
127.0.0.1:8057; always <remote-env-url>.
- Bandwidth floor is a RATIO, not absolute. It is
0.70 × subscribed_mbps
per ticket; subscribed_mbps must be read from GET /api/tickets/<id>, not
assumed. Do not apply a single global bandwidth number.
- Flags are PRE-troubleshooting. Gated tickets (account/auth/outage) get
all three issue flags
false and empty outage_id (except OUTAGE_WAIT).
- Outage match requires
active==true AND service_type membership, not
just service_area. Inactive outages in the same area do NOT gate.
- Account 404 = INVALID_ACCOUNT (different from Suspended). Auth failure is
read from
authentication.last_login_status/account_recovery_status, not
from status.
- Suspended accounts split by cause: "overdue" -> OVERDUE_SUSPENSION +
ACCOUNTS_PAYABLE; "hold"/other -> INELIGIBLE_ACCOUNT + no team. The line
analogue (
suspension_reason=="OVERDUE_BILL") is the mobile billing branch.
- POST flags decide RESOLVED vs ESCALATED, not the root cause. A root cause
only sets the escalation TEAM once escalation is already determined by
remaining post-flags.
- Escalation team keyword scan is case-insensitive substring on
root_causes[]; first matching category in the priority order
(fiber/signal -> backbone/capacity -> provisioning -> billing) wins.
- Preserve payload order in all decision arrays; sort by ticket_id /
case_id only for the audit id-lists.
- Empty strings, not null:
outage_id, bill_id are "" when not
applicable. escalation_team / route_team use NONE, not null.
- Mobile action vocabularies differ between the two variants (e.g.
TOGGLE_ROAMING phone-side vs ENABLE_LINE_ROAMING line-side; REFUEL_DATA
vs DATA_REFUEL). Match the provided template's enum expectations.
- Enterprise naming strings must follow the requirements
naming_style
exactly (lowercase-hyphen channel; " Investigation"
folder; " Export Failure - Resolution Report" title).
- SLA credit triggers the finance review response_status; the percent comes
straight from the SLA endpoint's
monthly_export_credit_percent.
- Probe the API; do not memorize record values. Ticket ids, account ids,
plan params, owners, etc. will change at test time — only the thresholds,
ratios, maps, and formulas above are stable.
1---2name: fewshot-attempt-02-33description: SCN_003 CRM Support-Operations Skill4---5# SCN_003 CRM Support-Operations Skill67Reusable SOP for resolving CRM support-console batches across three task8families: (1) ticket-batch resolution, (2) mobile triage, (3) enterprise9export-complaint response packages. All facts below are distilled from gold10answers + live API probes and are intended for a downstream solver.1112## 0. Environment & API base1314- Prompts reference `http://127.0.0.1:8057` — ALWAYS replace with the real15 base URL: **`<remote-env-url>`**.16- Health: `GET /health` -> `{"ok": true}`.17- Catalog: `GET /api/catalog` (endpoints + record counts only).18- Records are the source of truth; never assume a field exists. Probe to confirm.1920### API endpoint map (substitute real ids)2122| Resource | Endpoint |23|---|---|24| Accounts | `GET /api/accounts` , `GET /api/accounts/<account_id>` |25| Tickets | `GET /api/tickets` , `GET /api/tickets/<ticket_id>` |26| Outages | `GET /api/outages` , `GET /api/outages?service_area=<area>` |27| Diagnostics (pre) | `GET /api/diagnostics/<ticket_id>` |28| Troubleshooting (post) | `GET /api/troubleshooting/<ticket_id>` |29| Mobile customers | `GET /api/customers` , `GET /api/customers/<customer_id>` |30| Mobile lines | `GET /api/lines` , `GET /api/lines/<line_id>` |31| Mobile devices | `GET /api/devices/<device_id>` |32| Mobile plans | `GET /api/plans` , `GET /api/plans/<plan_id>` |33| Mobile cases / bills | `GET /api/bills` , `GET /api/cases` , `GET /api/cases/<case_id>` , `GET /api/bills/<bill_id>` |34| Enterprise accounts | `GET /api/enterprise/accounts` |35| Enterprise incidents | `GET /api/enterprise/incidents` |36| Enterprise export-runs | `GET /api/enterprise/export-runs?incident_id=<incident_id>` |37| Enterprise messages | `GET /api/enterprise/messages?query=<text>` |38| Enterprise SLA | `GET /api/enterprise/sla/<enterprise_account_id>` |3940Key record shapes (probe to confirm; do not hard-code):41- `GET /api/tickets/<id>` -> `{ticket_id, account_id, service_area, service_type, subscribed_mbps, status, issue_summary, created_at}`. **`subscribed_mbps` and `service_type` live here** and drive the bandwidth floor.42- `GET /api/accounts/<id>` -> `{account_id, name, service_area, status, tier, authentication:{last_login_status, account_recovery_status, last_login_at}}`. 404 body `{"error":"not_found"}` means invalid account.43- `GET /api/diagnostics/<id>` -> `{ticket_id, bandwidth_mbps, latency_ms, jitter_ms, root_causes:[...], started_at, completed_at}`.44- `GET /api/troubleshooting/<id>` -> `{ticket_id, post_bandwidth_mbps, post_latency_ms, post_jitter_ms, steps:[...], started_at, completed_at}`.45- `GET /api/outages?service_area=<area>` -> list of `{outage_id, service_area, service_types:[...], active, eta_hours, impact_score, started_at}`.4647---4849## 1. Family A — Ticket-batch resolution (train_001 / train_004)5051Input: a CSV (`ticket_batch.csv` / `queue_snapshot.csv`) listing `ticket_id,52account_id, reported_service_type, customer_report/queue_note`. Resolve every53ticket from console records, not from the note text alone. Output JSON per the54provided `answer_template.json` (two schema variants exist — see §1.6).5556### 1.1 Gating order (apply in this exact sequence per ticket)57581. **Account gate** (FAILED route — skip diagnostics):59 a. `GET /api/accounts/<account_id>` returns 404 -> **INVALID_ACCOUNT**.60 `final_resolution_status=FAILED`, `diagnostic_needed=false`.61 b. `authentication.last_login_status == "FAILURE"` (or62 `account_recovery_status == "FAILURE"`) -> **AUTH_FAILED**.63 `FAILED`, `diagnostic_needed=false`.64 c. `status == "Suspended"`:65 - If the ticket note/report text contains "overdue" (overdue notice) ->66 **OVERDUE_SUSPENSION**, route team **ACCOUNTS_PAYABLE** (FAILED,67 `diagnostic_needed=false`). The line-level analogue is68 `suspension_reason == "OVERDUE_BILL"`.69 - Else (e.g. "account hold") -> **INELIGIBLE_ACCOUNT** (FAILED,70 `diagnostic_needed=false`, no team).712. **Outage gate** (only after account gate passes):72 - `GET /api/outages?service_area=<ticket.service_area>`. If any outage has73 `active == true` AND `ticket.service_type ∈ outage.service_types` ->74 **OUTAGE_WAIT**: `final_resolution_status=PENDING_ACTION`,75 `diagnostic_needed=false`, `outage_id=<matching outage_id>`. This is the76 only branch that sets a non-empty `outage_id`.773. **Diagnostics** (account OK, no active outage):78 - `diagnostic_needed=true`. `GET /api/diagnostics/<id>` (pre) and79 `GET /api/troubleshooting/<id>` (post). Compute pre-flags (§1.3),80 then post-flags, then decide RESOLVED vs ESCALATED (§1.4).8182Order matters: account/auth gates are evaluated before the outage gate, and the83outage gate before diagnostics. A gated ticket never runs diagnostics, so its84`latency_issue/stability_issue/bandwidth_issue` are all `false` and85`outage_id` is empty (except the OUTAGE_WAIT branch).8687### 1.2 Flag semantics — flags are PRE-troubleshooting floor violations8889`latency_issue`, `stability_issue`, `bandwidth_issue` are computed from the90**pre-troubleshooting** diagnostic record against absolute/ratio floors. They91are `false` for every gated ticket (no diagnostic was run).9293### 1.3 Diagnostic floors (INFERRED — see §1.5 evidence)9495| Metric | Floor | Issue rule (true when) | Source field |96|---|---|---|---|97| latency | **100 ms** | `latency_ms > 100` | `diagnostics.latency_ms` |98| jitter (stability) | **30 ms** | `jitter_ms > 30` | `diagnostics.jitter_ms` |99| bandwidth | **0.70 × subscribed_mbps** | `bandwidth_mbps < 0.70 × subscribed_mbps` | `diagnostics.bandwidth_mbps` vs `tickets.subscribed_mbps` |100101Uniform across `service_type` (internet / video / voice all use the same102ratio + absolute floors). `subscribed_mbps` comes from the ticket record.103104### 1.4 Post-troubleshooting re-check (RESOLVED vs ESCALATED)105106For every diagnostic-eligible ticket, also `GET /api/troubleshooting/<id>` and107recompute the three flags against the SAME floors using the **post_** fields108(`post_bandwidth_mbps`, `post_latency_ms`, `post_jitter_ms`).109110- If **all three post-flags are false** -> **RESOLVED**,111 `resolution_route=AUTO_TROUBLESHOOTING`, `escalation_team=NONE`.112- If **any post-flag is still true** -> **ESCALATED**, `resolution_route=ESCALATION`,113 `escalation_team` derived from `diagnostics.root_causes` (§1.5).114115`outage_id` is empty string for both RESOLVED and ESCALATED.116117### 1.5 Root-cause -> escalation-team map118119Determine the team by scanning `root_causes[]` in priority order; first match120wins:121122| Root-cause keyword (substring, case-insensitive) | escalation_team | (train_004 key_blocker label) |123|---|---|---|124| `FIBER` or `SIGNAL` | `FIELD_OPS` | (fiber/signal label) |125| `BACKBONE` or `CAPACITY` | `NETWORK_ENGINEERING` | `NETWORK_CAPACITY` |126| `PROVISIONING` | `TIER2_SUPPORT` | `PROVISIONING_STALE` |127| `BILLING` | `ACCOUNTS_PAYABLE` | (billing label) |128| (none of the above, e.g. `CONFIGURATION_DRIFT`, `GENERATED_NOISE`, `VOICE_PROFILE_STALE`) | resolves via auto-troubleshoot -> usually RESOLVED; if still failing, see note | — |129130Observed: `CONFIGURATION_DRIFT` and `VOICE_PROFILE_STALE` tickets cleared all131post-flags so they were RESOLVED (no escalation). Only fiber/signal,132backbone/capacity, and provisioning root causes survived troubleshooting and133escalated. If a non-mapped root cause still has post-flags true, default the134team by the closest category; with no match, leave `FIELD_OPS` as a fallback135only when a physical-layer cause is evident (weakly inferred).136137### 1.6 Output schema variants — follow the provided answer_template.json138139**Variant 1 (train_001 style)** — richer per-ticket decision:140```141ticket_decisions[]: {ticket_id, account_id, final_resolution_status,142 diagnostic_needed, latency_issue, stability_issue, bandwidth_issue,143 outage_id ("" when none), escalation_team, resolution_route}144batch_summary: {RESOLVED, PENDING_ACTION, ESCALATED, FAILED,145 tickets_requiring_customer_wait}146```147- `resolution_route` enum: `AUTO_TROUBLESHOOTING | OUTAGE_WAIT | ESCALATION |148 INELIGIBLE_ACCOUNT | AUTH_FAILED | INVALID_ACCOUNT`.149- `escalation_team` enum: `NONE | TIER2_SUPPORT | FIELD_OPS |150 NETWORK_ENGINEERING | ACCOUNTS_PAYABLE`.151- `tickets_requiring_customer_wait` = count of PENDING_ACTION (outage-wait)152 tickets (the customer must wait for the outage to clear).153154**Variant 2 (train_004 style)** — key_blocker + route_team:155```156ticket_decisions[]: {ticket_id, final_resolution_status, route_team,157 key_blocker, diagnostic_required}158queue_summary: {FAILED, PENDING_ACTION, RESOLVED, ESCALATED,159 TIER2_SUPPORT, FIELD_OPS, NETWORK_ENGINEERING, ACCOUNTS_PAYABLE}160```161- `key_blocker` labels: `ACTIVE_OUTAGE | INVALID_ACCOUNT | AUTH_FAILED |162 OVERDUE_SUSPENSION | NETWORK_CAPACITY | PROVISIONING_STALE | NONE`163 (and fiber/signal label for that branch).164- `route_team`: `NONE` for resolved / outage / invalid / auth; the mapped team165 for overdue + escalations.166- `diagnostic_required` == `diagnostic_needed` (same boolean).167- `queue_summary` counts: first four are status counts; the four team keys168 count tickets routed to each team (FIELD_OPS=0 in train_004).169170Preserve payload (CSV) order in the `ticket_decisions` array.171172### 1.7 Audit math formulas (state exactly)173174Let `D` = set of diagnostic-eligible tickets (`diagnostic_needed=true`), `G` =175gated/skipped tickets (`diagnostic_needed=false`), `E` = ESCALATED tickets176(post-flags not all clear), `R` = RESOLVED-via-AUTO_TROUBLESHOOTING tickets.177Floors: `BW_RATIO=0.70`, `LAT=100`, `JIT=30`. For ticket `t`: `sub(t)` =178`subscribed_mbps`, `bw(t)`/`lat(t)`/`jit(t)` = pre diagnostic values,179`pbw(t)`/`plat(t)`/`pjit(t)` = post values.1801811. **pre_troubleshooting_bandwidth_gap_total_mbps** =182 `Σ_{t∈D, bw(t) < 0.70·sub(t)} (0.70·sub(t) − bw(t))`.1832. **diagnostic_records_skipped_by_gate** = `|G|` (tickets with184 `diagnostic_needed=false`).1853. **post_troubleshooting_remaining_issue_flags** =186 `Σ_{t∈E} [ (plat(t)>100) + (pjit(t)>30) + (pbw(t)<0.70·sub(t)) ]`187 (count of post-flags still true, summed over escalated tickets).1884. **post_threshold_excess_totals** (over `E` only):189 - latency_excess_total = `Σ_{t∈E} max(0, plat(t) − 100)`190 - jitter_excess_total = `Σ_{t∈E} max(0, pjit(t) − 30)`191 - bandwidth_shortfall_total = `Σ_{t∈E} max(0, 0.70·sub(t) − pbw(t))`1925. **tickets_using_post_troubleshooting_records** = `|D|` (every193 diagnostic-eligible ticket has a troubleshooting record consumed).1946. **tickets_with_active_outage_match** = count of tickets where an outage with195 `active=true`, matching `service_area`, and `service_type ∈196 outage.service_types` exists (i.e. the OUTAGE_WAIT branch).1977. **unique_escalation_teams** = sorted unique `escalation_team` values among198 `E`, excluding `NONE`.1998. **root_cause_escalation_ticket_ids by team** = `{team: sorted[ticket_id]}`200 for each team in `E`'s escalation teams.2019. **post success/failure id lists**:202 - success (RESOLVED via AUTO_TROUBLESHOOTING) = `sorted[ticket_id for t∈R]`203 - failure (ESCALATED) = `sorted[ticket_id for t∈E]`20410. **per-ticket floor + shortfall list** (sorted by `ticket_id`), one entry205 per `t∈D`:206 `{ticket_id, subscribed_mbps, bandwidth_floor_mbps=0.70·sub,207 latency_floor_ms=100, jitter_floor_ms=30,208 pre_bandwidth_shortfall_mbps=max(0, 0.70·sub−bw),209 pre_latency_excess_ms=max(0, lat−100),210 pre_jitter_excess_ms=max(0, jit−30)}`.211212### 1.8 Worked evidence for the thresholds (INFERRED)213214- **Bandwidth ratio 0.70 (strong):** TCK-5107 `sub=300`, pre `bw=209` flagged215 issue; `0.70·300 = 210`, and `209 < 210` (off by exactly 1 — boundary test).216 Post `bw=272 ≥ 210` -> cleared -> RESOLVED. TCK-5184 `sub=500`, pre `bw=318`217 flagged; `0.70·500=350`, `318<350`. Both consistent. (`0.75` also fits the218 two bandwidth points but cannot explain the deliberate `209`-vs-`210`219 boundary; commit to **0.70**.)220- **Latency floor 100 ms:** RESOLVED posts 82, 79 (≤100, no issue); ESCALATED221 posts 176, 198, 121 (>100, issue); pre-flagged gold 142.8, 188.4 (>100).222 All consistent with `>100 = issue`. Boundary untested; 100 is the canonical223 degraded-latency threshold.224- **Jitter floor 30 ms:** RESOLVED posts 21, 18 (≤30); ESCALATED posts 41, 43,225 32 (>30); pre-flagged gold 33.5, 44.2 (>30). All consistent with `>30`.226 (25 ms also fits all points; 30 is the canonical jitter threshold and matches227 the ESCALATED post value 32 being just over — commit to **30**.)228229---230231## 2. Family B — Mobile triage (train_002 / train_005)232233Input: a JSON queue (`case_queue.json` / `mobile_data_worklist.json`) of cases234with `case_id` + `reported_issue`/`summary`. For each case, fetch235`GET /api/cases/<case_id>` to get `customer_id`, `line_id`, `device_id`,236`issue_type`, `customer_location`; then `GET /api/lines/<line_id>`,237`GET /api/devices/<device_id>`, `GET /api/plans/<plan_id>` (via line.plan_id),238and `GET /api/bills/<bill_id>` when a bill is referenced.239240### 2.1 Decision tree (apply in priority order; first match wins)2412421. **Line suspended for overdue bill** — `line.status=="Suspended"` and243 (`line.suspension_reason=="OVERDUE_BILL"` or note contains "overdue"):244 - primary `SEND_PAYMENT_REQUEST`, secondary `RESUME_LINE_REBOOT`,245 `final_route=BILLING_RECOVERY`.246 - `bill_id` = the line/customer's overdue bill; `charge_amount_usd` =247 `bill.amount_due_usd`. (Found via `GET /api/bills/<bill_id>`; the bill id248 pattern is `BILL-<case_num>`.)2492. **Data cap exceeded** — `line.data_used_gb > plan.data_limit_gb`:250 - primary `REFUEL_DATA`, `final_route=DATA_RECOVERY`.251 - `data_refuel_gb = ceil(data_used_gb − data_limit_gb)` (min 1.0; INFERRED252 from one point: overage 1.2 -> 2.0 GB).253 - `charge_amount_usd = data_refuel_gb × plan.data_refueling_price_per_gb`.2543. **SIM missing** — `device.sim_status=="missing"`:255 - primary `RESEAT_SIM`, `final_route=SELF_SERVICE`. (NO_SERVICE case after256 commute.)2574. **Abroad + roaming gap** — `case.customer_location=="abroad"` and data issue:258 a. `line.roaming_enabled==false` -> primary `ENABLE_LINE_ROAMING`,259 `carrier_update_required=true`, `final_route=CARRIER_UPDATE`260 (carrier-side; needs a carrier update).261 b. else `device.phone_roaming_enabled==false` -> primary `TOGGLE_ROAMING`,262 `final_route=SELF_SERVICE` (phone-side toggle).2635. **MMS / messaging permission** — `device.can_send_mms==false` and a264 `messaging_permissions` key is `false`:265 - primary `GRANT_MESSAGING_PERMISSION`, `permission=<the false key>` (e.g.266 `storage`), `final_route=SELF_SERVICE`. (`mmsc_url_present` should be true;267 the missing permission is the blocker.)2686. **VPN connected** — `device.vpn_connected==true` (slow data):269 - primary `DISCONNECT_VPN`, `final_route=SELF_SERVICE`.2707. **Data-saver on** — `device.data_saver_mode==true` (slow data):271 - primary `TOGGLE_DATA_SAVER`, `final_route=DEVICE_SETTING_FIX`.2728. **Stale network mode** — `device.network_mode_preference != "4g_5g_preferred"`273 (e.g. `"3g_only"`, slow data):274 - primary `SET_NETWORK_MODE`, `final_route=DEVICE_SETTING_FIX`.2759. **Mobile data disabled** — `device.mobile_data_enabled==false`:276 - primary `TOGGLE_MOBILE_DATA`, `final_route=DEVICE_SETTING_FIX`.277278Default `secondary_action=NO_ACTION` for all except the billing branch279(`RESUME_LINE_REBOOT`). `permission="NONE"` and `bill_id=""` unless the280branch sets them. `data_refuel_gb=0.0`, `charge_amount_usd=0.0`,281`carrier_update_required=false` unless the branch sets them.282283`issue_type` reinforces branch selection but the device/line fields are284authoritative: `NO_SERVICE` -> sim/billing; `MOBILE_DATA` -> cap/roaming/data-285toggle; `MMS` -> permission; `SLOW_DATA` -> vpn/data-saver/network-mode.286287### 2.2 Output schema variants — follow the provided answer_template.json288289**train_002 style** (case_queue):290```291case_decisions[]: {case_id, customer_id, line_id, primary_action,292 secondary_action, permission, bill_id, charge_amount_usd, final_route}293queue_summary: {self_service_fixes, billing_recoveries, carrier_updates,294 human_transfers}295```296- `final_route` values seen: `SELF_SERVICE`, `BILLING_RECOVERY`.297- summary: `self_service_fixes` = count of SELF_SERVICE routes;298 `billing_recoveries` = count of BILLING_RECOVERY; `carrier_updates` =299 count of CARRIER_UPDATE; `human_transfers` = 0 (no human-transfer branch300 observed).301302**train_005 style** (mobile_data_worklist):303```304case_decisions[]: {case_id, primary_action, secondary_action,305 data_refuel_gb, charge_amount_usd, carrier_update_required, final_route}306worklist_summary: {data_refuel_cases, carrier_updates, device_setting_fixes,307 human_transfers, total_estimated_customer_charge_usd}308```309- `final_route` values seen: `DATA_RECOVERY`, `CARRIER_UPDATE`,310 `DEVICE_SETTING_FIX`.311- `total_estimated_customer_charge_usd` = Σ `charge_amount_usd` across cases.312313Preserve payload order. Action enums differ slightly between the two variants314(e.g. `TOGGLE_ROAMING` vs `ENABLE_LINE_ROAMING`; `REFUEL_DATA`) — emit the315action token the decision tree produces and that the template expects.316317### 2.3 Plan reference (from `GET /api/plans`)318319| plan_id | data_limit_gb | data_refueling_price_per_gb | monthly_price_usd |320|---|---|---|---|321| PLAN-BASIC | 5.0 | 5.0 | 40.0 |322| PLAN-PREMIUM | 15.0 | 2.0 | 65.0 |323| PLAN-PLUS | 999.0 | 0.1 | 85.0 |324| (PLAN-25 / others) | 25.0 | 3.0 | 120.0 |325326Always read the plan from the API; do not hard-code these.327328---329330## 3. Family C — Enterprise export-complaint response package (train_003)331332Input: `client_complaint_email.txt` (client name, product, approximate333incident reference) + `response_requirements.json` (required_fields,334`permission_users_to_include`, `naming_style`). Identify the incident from the335email's approximate reference (e.g. `INC-7301`), then assemble the package.336Output JSON per `answer_template.json`.337338### 3.1 SOP3393401. **Identify incident.** `GET /api/enterprise/incidents`, match on341 `incident_id` from the email (the "Approximate incident reference"). Confirm342 `enterprise_account_id`, `severity`, `engineering_owner`, `account_owner`,343 `product`, `status`.3442. **Confirm account.** `GET /api/enterprise/accounts`, match on345 `enterprise_account_id` to get `name`, `tier`, `finance_owner`.3463. **Failed export-run window.**347 `GET /api/enterprise/export-runs?incident_id=<incident_id>`.348 - `failure_window.start_date` = `run_date` of the FIRST `status=="FAILED"`349 run; `end_date` = `run_date` of the LAST `FAILED` run;350 `failed_days` = count of FAILED runs.351 - `backfill_days` = count of FAILED runs (= `failed_days`).352 - `root_cause_category` = concise phrase inferred from the FAILED runs'353 `failure_code` + the engineering message body354 (`GET /api/enterprise/messages?query=<client or failure keyword>`).355 Examples: `STALE_CREDENTIAL` + "credential rotation completed; scheduler356 pod still references old secret" -> `"stale credential after rotation"`;357 `STAGING_STORAGE_QUOTA` + "staging bucket reached quota" -> quota-related358 phrase. Lowercase descriptive phrase.3594. **SLA credit.** `GET /api/enterprise/sla/<enterprise_account_id>`.360 - `sla_credit_percent` = `monthly_export_credit_percent` WHEN the361 `credit_trigger` condition is met by the failure window (e.g. "3362 consecutive failed export runs" -> failed_days≥3; "critical export outage363 longer than 72 hours" -> failed_days≥3 days). Else 0.364 - If credit warranted -> `response_status = "NEEDS_FINANCE_REVIEW"` (finance365 must approve the credit). Other statuses: `NEEDS_ENGINEERING_REVIEW` if an366 unresolved engineering root cause needs sign-off and no credit;367 `READY_TO_SEND` if resolved with no credit; `UNDER_INVESTIGATION` if the368 incident is still open and root cause unconfirmed. (Only the finance-review369 branch is directly attested by gold.)3705. **Contributing alert issue.** From the engineering root-cause message: if its371 `channel` contains "archive" (e.g. `export-alerts-archive`) ->372 `ARCHIVED_ALERT_ROUTE`; else `NONE`; `UNKNOWN` only if no message found.3736. **Owners.** `engineering_owner` and `account_owner` come from the incident374 record. (Finance owner is used for share permissions.)3757. **Naming conventions** (from `response_requirements.naming_style` =376 "lowercase hyphen channel; client-date investigation folder; client export377 failure report title"), using the account `name`:378 - `channel_name` = client name lowercased, spaces -> hyphens379 (e.g. "Asteri Retail Inc." -> `asteri-retail-inc`).380 - `evidence_folder` = `"<Client Name> <Month Year> Investigation"` where381 Month Year comes from the incident `received_at` / failure window month382 (e.g. "Asteri Retail Inc. May 2026 Investigation").383 - `report_title` = `"<Client Name> Export Failure - Resolution Report"`.3848. **Share permissions.** Use `permission_users_to_include` from the385 requirements, in the listed order. Assign `"view"` to the user that is the386 account's `finance_owner`; assign `"edit"` to the other listed user(s).387 (INFERRED from one example: laura.brown=finance_owner->view,388 jun.chen->edit. Order preserved as listed in requirements.)389390### 3.2 Output fields391392```393incident_id, enterprise_account_id, root_cause_category, contributing_alert_issue,394failure_window {start_date, end_date, failed_days}, backfill_days,395sla_credit_percent, severity, engineering_owner, account_owner,396channel_name, evidence_folder, report_title,397share_permissions [{user, permission}], response_status398```399Enumerations: `contributing_alert_issue` ∈400`ARCHIVED_ALERT_ROUTE | NONE | UNKNOWN`; `severity` ∈401`Critical | High | Medium | Low`; `permission` ∈ `view | edit | upload_only`;402`response_status` ∈403`READY_TO_SEND | NEEDS_FINANCE_REVIEW | NEEDS_ENGINEERING_REVIEW | UNDER_INVESTIGATION`.404405---406407## 4. Pitfalls & conventions (all families)408409- **Base URL:** never use `127.0.0.1:8057`; always `<remote-env-url>`.410- **Bandwidth floor is a RATIO, not absolute.** It is `0.70 × subscribed_mbps`411 per ticket; `subscribed_mbps` must be read from `GET /api/tickets/<id>`, not412 assumed. Do not apply a single global bandwidth number.413- **Flags are PRE-troubleshooting.** Gated tickets (account/auth/outage) get414 all three issue flags `false` and empty `outage_id` (except OUTAGE_WAIT).415- **Outage match requires `active==true` AND service_type membership**, not416 just service_area. Inactive outages in the same area do NOT gate.417- **Account 404 = INVALID_ACCOUNT** (different from Suspended). Auth failure is418 read from `authentication.last_login_status`/`account_recovery_status`, not419 from `status`.420- **Suspended accounts split by cause:** "overdue" -> OVERDUE_SUSPENSION +421 ACCOUNTS_PAYABLE; "hold"/other -> INELIGIBLE_ACCOUNT + no team. The line422 analogue (`suspension_reason=="OVERDUE_BILL"`) is the mobile billing branch.423- **POST flags decide RESOLVED vs ESCALATED**, not the root cause. A root cause424 only sets the escalation TEAM once escalation is already determined by425 remaining post-flags.426- **Escalation team keyword scan is case-insensitive substring** on427 `root_causes[]`; first matching category in the priority order428 (fiber/signal -> backbone/capacity -> provisioning -> billing) wins.429- **Preserve payload order** in all decision arrays; **sort by ticket_id /430 case_id** only for the audit id-lists.431- **Empty strings, not null:** `outage_id`, `bill_id` are `""` when not432 applicable. `escalation_team` / `route_team` use `NONE`, not null.433- **Mobile action vocabularies differ between the two variants** (e.g.434 `TOGGLE_ROAMING` phone-side vs `ENABLE_LINE_ROAMING` line-side; `REFUEL_DATA`435 vs `DATA_REFUEL`). Match the provided template's enum expectations.436- **Enterprise naming strings must follow the requirements `naming_style`437 exactly** (lowercase-hyphen channel; "<Client> <Mon Year> Investigation"438 folder; "<Client> Export Failure - Resolution Report" title).439- **SLA credit triggers the finance review** response_status; the percent comes440 straight from the SLA endpoint's `monthly_export_credit_percent`.441- **Probe the API; do not memorize record values.** Ticket ids, account ids,442 plan params, owners, etc. will change at test time — only the thresholds,443 ratios, maps, and formulas above are stable.