135 lines
5.3 KiB
Markdown
135 lines
5.3 KiB
Markdown
# 05 — Ticket detail enrichment (tags, categories, activity, auto flags)
|
||||
|
|
|
|||
|
|
**Agent:** 5/10 · **Series:** `support-auto`
|
|||
|
|
**Migration:** `apps/api/sql/schema/031_support_ticket_detail.sql`
|
|||
|
|
**Contract:** [02-contract.md](./02-contract.md) §§2, 4.4, 6–7
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## Goal
|
|||
|
|
|
|||
|
|
Enrich support tickets for “more detailed support” without breaking existing create/list/get contracts:
|
|||
|
|
|
|||
|
|
| Capability | Storage / API |
|
|||
|
|
|------------|---------------|
|
|||
|
|
| Tags | `support_tickets.tags TEXT[]` — create + staff PATCH (`set_tags`) |
|
|||
|
|
| Better categories | `support_categories` taxonomy; seeds expand beyond `billing\|bug\|account\|other` |
|
|||
|
|
| Related resource refs | `related_product_id` → `processed_products` (tenant-checked), `related_sku` |
|
|||
|
|
| Customer context snapshot | `customer_context JSONB` — server-filled on create; staff GET full; customer GET redacted |
|
|||
|
|
| Disable auto-reply | `auto_reply_disabled` — staff PATCH; auto-set on human reply / customer follow-up after auto |
|
|||
|
|
| Match / AI metadata | `auto_reply_status`, `auto_reply_meta`, `auto_reply_*` message columns |
|
|||
|
|
| Activity timeline | `support_ticket_activity` — auto / AI / human events on **staff GET** |
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## Migration coordination (agents 3 / 4)
|
|||
|
|
|
|||
|
|
**One additive series — do not fork ticket columns.**
|
|||
|
|
|
|||
|
|
| File | Owner | Contents |
|
|||
|
|
|------|-------|----------|
|
|||
|
|
| `031_support_ticket_detail.sql` | **agent 5** | Ticket rich fields, message `auto_*` columns, `support_categories`, `support_ticket_activity`, notification kind `auto_reply` |
|
|||
|
|
| `032_*` (KB / templates / jobs) | **agent 3** | `support_kb_articles`, `support_reply_templates`, matcher tables/jobs — **do not** re-`ALTER` ticket/message columns from 031 |
|
|||
|
|
| AI fallback | **agent 4** | Reuse `RecordAutoReplyOutcome`, message `auto_source='ai'`, statuses `ai_draft` / `ai_sent` / `failed` / `handed_off` |
|
|||
|
|
|
|||
|
|
Shared helpers for agents 3–4:
|
|||
|
|
|
|||
|
|
- `support.RecordAutoReplyOutcome(ctx, ticketID, status, disabled, messageID, meta, activityKind)`
|
|||
|
|
- Message columns: `is_auto_reply`, `auto_source` ∈ `kb|template|ai`, `auto_confidence`, `auto_ref_type`, `auto_ref_id`
|
|||
|
|
- Ticket: `auto_reply_status` ∈ `none|matched|ai_draft|ai_sent|skipped|failed|handed_off`
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## API changes (additive)
|
|||
|
|
|
|||
|
|
### Customer create — `POST /api/support/tickets`
|
|||
|
|
|
|||
|
|
Existing: `subject`, `category`, `priority`, `body`.
|
|||
|
|
|
|||
|
|
Additive optional:
|
|||
|
|
|
|||
|
|
```json
|
|||
|
|
{
|
|||
|
|
"tags": ["woo", "export"],
|
|||
|
|
"related_product_id": "uuid",
|
|||
|
|
"related_sku": "SKU-123"
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
`related_product_id` must belong to the caller’s `company_id` or the API returns `invalid related_product_id`.
|
|||
|
|
|
|||
|
|
### List — customer + staff
|
|||
|
|
|
|||
|
|
Returns (in addition to prior fields): `tags`, `related_product_id`, `related_sku`, `auto_reply_disabled`, `auto_reply_status`, `resolved_by_user_id`.
|
|||
|
|
|
|||
|
|
**Does not** embed full `customer_context`, `auto_reply_meta`, or `activity` (detail-only).
|
|||
|
|
|
|||
|
|
### GET customer — `GET /api/support/tickets/{id}`
|
|||
|
|
|
|||
|
|
Detail fields + messages (with `is_auto_reply` / `auto_source` when present).
|
|||
|
|
`customer_context` redacts `signals` and `user_email`.
|
|||
|
|
`auto_reply_meta` and `activity` omitted.
|
|||
|
|
|
|||
|
|
### GET staff — `GET /api/admin/support/tickets/{id}`
|
|||
|
|
|
|||
|
|
Full snapshot + `auto_reply_meta` + `activity[]` timeline + messages including internal notes and auto metadata.
|
|||
|
|
|
|||
|
|
### Staff PATCH — `PATCH /api/admin/support/tickets/{id}`
|
|||
|
|
|
|||
|
|
Additive fields on `AdminUpdateInput`:
|
|||
|
|
|
|||
|
|
| Field | Notes |
|
|||
|
|
|-------|-------|
|
|||
|
|
| `category` | Active taxonomy slug |
|
|||
|
|
| `tags` + `set_tags: true` | Replace tag set (omit `set_tags` to leave unchanged) |
|
|||
|
|
| `related_product_id` / `clear_related_product` | Tenant-scoped |
|
|||
|
|
| `related_sku` | Optional string |
|
|||
|
|
| `auto_reply_disabled` | Staff kill-switch for auto pipeline |
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## Activity timeline kinds
|
|||
|
|
|
|||
|
|
`created`, `customer_message`, `agent_message`, `system_message`, `auto_reply`, `ai_draft`, `ai_sent`, `ai_failed`, `handed_off`, `claimed`, `released`, `status_changed`, `auto_disabled`, `auto_enabled`, `note`
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## Behavior hooks
|
|||
|
|
|
|||
|
|
1. **Create** — writes `customer_context`, activity `created`; leaves `auto_reply_status=none` for agent 3 matcher.
|
|||
|
|
2. **Customer reply after matched/ai_sent** — sets `auto_reply_disabled=true`, `auto_reply_status=handed_off`.
|
|||
|
|
3. **Staff public reply** — sets `auto_reply_disabled=true`.
|
|||
|
|
4. **Staff toggles `auto_reply_disabled`** — activity `auto_disabled` / `auto_enabled`.
|
|||
|
|
|
|||
|
|
Create / Reply\* still **must not** call LLM completers directly (agent 4 owns `TryAutoReplyLLM`).
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## Tests
|
|||
|
|
|
|||
|
|
| Test | File |
|
|||
|
|
|------|------|
|
|||
|
|
| Tag / SKU / category validation | `ticket_detail_test.go` |
|
|||
|
|
| Context redaction | `ticket_detail_test.go` |
|
|||
|
|
| Create rejects bad tags (ClientError) | `ticket_detail_test.go` |
|
|||
|
|
| Existing CRUD auth / claim / CSAT | unchanged; pre-031 column fallbacks keep them green until goose up |
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## Verify locally
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
# from apps/api
|
|||
|
|
goose -dir sql/schema postgres "$DATABASE_URL" up
|
|||
|
|
go test ./internal/support/ -count=1
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## ASSUMPTIONS
|
|||
|
|
|
|||
|
|
- ASSUMPTION: Agent 3 lands KB in `032_*` without duplicating 031 ticket ALTERs. (**Confirmed:** `032_support_kb_auto_reply.sql` references 031.)
|
|||
|
|
- ASSUMPTION: `plans.name` is stored as `customer_context.plan_slug` (plans have no slug column).
|
|||
|
|
- ASSUMPTION: List payloads stay lean; UI agents read detail GET for timeline / context.
|
|||
|
|
- NOTE for agent 10: two files share version `033_*` (`033_support_auto_ai_config.sql` and `033_support_auto_security_perf.sql`) — rename one before goose up.
|