# Ticket Brief

**Live:** https://cw-ticket-brief-portal.pages.dev/
**Repo:** `mellis-dev/cw-ticket-brief-portal` (private)

## What This Tool Does

Takes a ConnectWise ticket number and produces a manager-ready brief that includes:

- **Ticket snapshot** — status, board, owner, priority, actual hours, contact info, merged/bundled ticket links
- **Timeline summary** — chronological note log with elapsed-time deltas
- **What likely went wrong** — AI-grounded root-cause analysis (3 points)
- **What still needs done** — remaining action items (3 points)
- **30-minute action plan** — prioritized next steps (3 items)
- **Draft messages** — manager-to-client, manager-to-resource coaching, and resource-to-client

Optional: email a copy of the report to any `@coremanaged.com` address. The same output renders in the on-screen preview.

URL prefill is supported: `/?ticket=12345` auto-populates the field and triggers generation immediately.

## Architecture

```
Browser (index.html)             API (Cloudflare Pages Function)         OpenClaw Webhook
─────────────────────           ────────────────────────────────         ────────────────
POST /api/report              → report.js                             →  OPENCLAW_WEBHOOK_URL
  { ticketId, sendEmail,           validates input                        (POST, mode: "full")
    emailTo }                      reads cf-access header                    ↓
                                   forwards to webhook              webhook-ticket-brief.js
                              ←  returns { ok, report,                  fetchTicketContext()
                                  subject, email, run }                  buildReport()
                                                                         buildOpusAnalysis()
                                                                         maybeSendEmail()
                                                                         persistRunLog()
```

### Files

| File | Purpose |
|------|---------|
| `public/index.html` | Frontend — all HTML/CSS/JS in one file |
| `functions/api/report.js` | Edge function — validates, extracts CF Access user email, proxies to webhook |
| `functions/api/webhook-ticket-brief.js` | Webhook handler — CWM data fetch, AI analysis, email, run log (all modes) |

> `webhook-ticket-brief.js` is the shared backend for **all** ticket-processing tools (Ticket Brief, Suggest Resolution, Survey Response). The `mode` field routes to the appropriate builder function.

### Environment / Secrets

| Variable | Purpose |
|----------|---------|
| `OPENCLAW_WEBHOOK_URL` | URL of the OpenClaw webhook endpoint |
| `OPENCLAW_WEBHOOK_TOKEN` | Bearer token for webhook auth |
| `CW_SITE` | ConnectWise site URL |
| `CW_COMPANY_ID` | CW company ID (used in Basic auth and codebase lookup) |
| `CW_CLIENT_ID` | CW API client ID header |
| `CW_PUBLIC_KEY` | CW API public key |
| `CW_PRIVATE_KEY` | CW API private key |
| `OPENROUTER_API_KEY` | OpenRouter key for AI analysis |
| `FULL_REPORT_MODEL` | AI model override for full report (default: `anthropic/claude-opus-4-6`) |
| `DRAFT_COMMS_MODEL` | AI model override for draft comms (fallback for `FULL_REPORT_MODEL`) |
| `OPENROUTER_INPUT_PER_MILLION_USD` | Cost estimate — input tokens per $1M (default: 15) |
| `OPENROUTER_OUTPUT_PER_MILLION_USD` | Cost estimate — output tokens per $1M (default: 75) |
| `RESEND_API_KEY` / `RESEND_TOKEN` | Resend API key for email delivery |
| `REPORT_EMAIL_FROM` | Sender address for emailed reports |
| `REPORT_EMAIL_TO` | Default recipient if no override is provided |
| `REPORT_EMAIL_ALLOWED_DOMAINS` | Comma-separated allowed recipient domains (default: `coremanaged.com`) |
| `RUN_LOG_DB` | Cloudflare D1 binding for run log persistence |
| `OPS_ALERT_WEBHOOK_URL` | Ops alert destination when AI falls back to stub content |

### D1 Tables

- **`run_logs`** — one row per generation run. Columns: `created_at`, `ticket_id`, `mode`, `requester_email`, `send_email_requested`, `email_sent`, `duration_ms`, `analysis_mode`, `analysis_model`, `analysis_error`, `estimated_cost_usd`.

## API Endpoints

### `POST /api/report`

| Field | Type | Required | Notes |
|-------|------|----------|-------|
| `ticketId` | string | ✅ | Numeric, ≥ 4 digits |
| `sendEmail` | boolean | — | Triggers Resend delivery |
| `emailTo` | string | — | Must be `@coremanaged.com`; falls back to CF Access user email |

**Response:**
```json
{
  "ok": true,
  "report": "### Ticket Snapshot\n...",
  "subject": "Ticket Brief: 12345 - Summary | Client",
  "email": { "attempted": true, "sent": true, "detail": "Email sent via Resend." },
  "analysis": { "mode": "opus", "model": "anthropic/claude-opus-4-6", "estimatedCostUsd": 0.012 },
  "run": { "event": "ticket_brief_portal_run", "durationMs": 18400, ... }
}
```

## Webhook: `webhook-ticket-brief.js`

All POST requests from the portal land here. Auth is `Authorization: Bearer <OPENCLAW_WEBHOOK_TOKEN>`.

### `mode: "full"` — Full Ticket Brief

1. **`fetchTicketContext()`** — Fetches ticket, notes, allNotes, merged/bundled child tickets and their notes from CWM REST API.
2. **`buildReport()`** — Constructs ticket snapshot, timeline, and calls AI.
3. **`buildOpusAnalysis()`** — Sends structured prompt to OpenRouter; expects JSON back with `whatLikelyWentWrong`, `whatStillNeedsDone`, `actionPlan`, `drafts` (3 keys). Single attempt; throws on failure.
4. **`maybeSendEmail()`** — Posts rendered HTML to Resend if configured and recipient passes domain allowlist.
5. **`persistRunLog()`** — Inserts one row to D1 `run_logs`.
6. **`notifyFallback()`** — Fires `OPS_ALERT_WEBHOOK_URL` if analysis mode is `fallback`.

## Key Implementation Notes

- **CF Access email header** — `report.js` reads `cf-access-authenticated-user-email` so the requester's identity is captured without any login UI.
- **Codebase detection** — CWM API base URL is resolved dynamically via `/login/companyinfo/{companyId}`, which returns `Codebase` and `SiteUrl`. This avoids hardcoding the API path.
- **Merged/bundled tickets** — The webhook fetches `mergedParentTicket/id=<ticketId>` children and scans `allNotes` for cross-ticket bundle markers, then pulls full note history for each. This ensures the timeline covers tickets that were consolidated into one.
- **Timeline deltas** — Each note entry shows elapsed time since the previous entry (e.g., `+2h 14m`), formatted in minutes, hours, or days as appropriate.
- **`allNotes` vs `notes`** — The `/allNotes` endpoint returns both internal and customer-visible notes in a single call; the handler prefers it and falls back to `/notes`.
- **Cost estimation** — Rough token count is derived from text length (÷4) and multiplied by the per-million-token rates from env vars.
- **URL auto-run** — `?ticket=12345` in the URL populates the input field and fires `run()` immediately after the CF Access email prefill resolves.

## Accent Color

Green `#297843` — the default portal accent. Each tool uses a unique color (see dev guide).

## Deployment

```bash
git pull
npx wrangler pages deploy public --project-name cw-ticket-brief-portal --branch deploy-delay --commit-dirty=true
git add -A && git commit -m "description" && git push origin main
```

⚠️ **Deploy to `deploy-delay` branch only.** Main bypasses Zero Trust.
