# 13 — Support ticket ratings / CSAT **Owner:** agent 13 **Depends on:** agent 12 schema (`030_support_desk.sql` → `support_csat_ratings`) **Status:** implemented (API + tests) ## Goal Customers rate **resolved** or **closed** tickets once (score 1–5 + optional comment). Platform admins see aggregate CSAT (no PII). ## Schema (agent 12) Table `support_csat_ratings`: | Column | Notes | |--------|--------| | `ticket_id` | `UNIQUE` — one rating per ticket | | `company_id`, `user_id` | owner + tenant | | `score` | `SMALLINT` 1–5 | | `comment` | optional, max 2000 runes (truncated) | | `created_at` | UTC | Ticket columns `csat_token_hash` / `csat_invite_sent_at` exist for optional public token flow (not wired in this agent). ## Rules 1. Only `created_by_user_id` may rate (others → `404`). 2. Status must be `resolved` or `closed` else `400` (`ticket not eligible for rating`). 3. Duplicate insert → `409` (`already rated`). 4. Logs: `ticket_id` + `score` only — **never** comment, email, or names. ## APIs | Method | Path | Auth | Response | |--------|------|------|----------| | `POST` | `/api/support/tickets/{id}/csat` | session + company | `201` `SupportCsat` | | `GET` | `/api/support/tickets/{id}` | customer | additive `csat` / `csat_eligible` | | `GET` | `/api/admin/support/tickets/{id}` | support desk | includes `csat` when present | | `GET` | `/api/admin/support/csat?from=&to=` | platform admin | aggregate | ### Customer submit body ```json { "score": 4, "comment": "optional" } ``` ### Admin aggregate ```json { "total": 12, "average": 4.25, "distribution": { "1": 0, "2": 1, "3": 2, "4": 4, "5": 5 }, "from": null, "to": null } ``` `from` / `to` are optional RFC3339 bounds on `created_at` (`to` exclusive). ## Code map | Piece | Path | |-------|------| | Service | `apps/api/internal/support/ratings.go` | | Types | `CSATInput`, `CSATRating`, `CSATAggregate` in `types.go` | | Handlers | `apps/api/internal/httpapi/support_csat_handlers.go` | | Routes | customer POST csat; admin GET `/api/admin/support/csat` | | Unit tests | `ratings_test.go`, `support_csat_auth_test.go` | | Integration | `ratings_integration_test.go` (needs `DATABASE_URL` + goose) | ## Verification ```bash cd apps/api go test ./internal/support/ -count=1 -run 'CSAT|NormalizeCSAT|ClientErrorCSAT' go test ./internal/httpapi/ -count=1 -run 'SupportCSAT|SupportTicketCRUDAuth' ``` With DB migrated: ```bash go test ./internal/support/ -count=1 -run TestSubmitCSATOwnershipAndOnce ``` ## Out of scope (this agent) - Public token CSAT (`POST /api/public/support/csat`) - User/staff UI panels (agents 14–15) - Email CSAT invite stubs