# 06 · Data

`starter/data.js` holds a realistic fake dataset for **Bright Spark Electric**, a small electrical business run by Mike Doyle, with one teammate, Jess. Every person, number, address and message is invented. Phone numbers use the fictional **555-01xx** range.

"Now" in the dataset is **Thursday 22 Oct 2026, 10:20 AM, New York time** (`REMLY_DATA.NOW`). The calendar week is Mon 19 – Sun 25 Oct.

## Rule: field names follow the API contract

Every object in `data.js` has exactly the shape the real API returns (`docs/okf/remly-native/contracts/openapi.yaml`). That way, screens built on it map 1:1 onto the native apps.

- **Do not** rename, add or remove fields on contract objects.
- Things the design needs that the API does not have go under **`proposed`**. Each needs a contract change before the native build. Screens must look right when `proposed` is empty, because that is the live app today.
- Show server-written strings verbatim (`Error.error`, `reply-channels.unavailable`).

## What's in it

| Key in `REMLY_DATA` | API route | Shape | Count |
|---|---|---|---|
| `session` | `GET /me` (and `POST /auth/login`) | `{ user: SessionUser, client: SessionClient }` | 1 owner, all feature flags on |
| `conversations` | `GET /conversations` | `[Conversation]` | **12**: SMS ×5, WhatsApp ×3, web chat ×2, voicemail ×2 |
| `threads[grouping_key]` | `GET /conversations/{channel}/{identifier}` | `{ meta: ConversationMeta, messages: [Message], next_before }` | 12 (messages oldest first here; the API pages newest first) |
| `leads` | `GET /leads` | `[Lead]` | **20** across all statuses (10 open: urgent 2, new 3, contacted 2, quoted 3) |
| `leadHistory[leadId]` | `GET /leads/{id}/history` | `[{ old_status, new_status, at }]` | 3 |
| `calls` | `GET /calls` | `[Call]` | **15**: missed ×4, voicemail ×3, answered ×5, outgoing ×3 |
| `voicemails` | `GET /voicemails` | `[Voicemail]` | 3 |
| `appointments` | `GET /appointments?from&to` | `[Appointment]` | **12** across the week: 4 completed, 5 confirmed, 2 pending (Remy ghost slots), 1 cancelled |
| `accounts[leadId]` | `GET /customers/{id}/account` | `Account` (cents) | 4 |
| `customerNotes[phone]` | `GET /customer-history` (notes) | `[{ id, note, created_at }]` | 2 |
| `notificationPrefs` | `GET /me/notifications` | `NotificationPrefs` | 1 |
| `businessDetails` | `GET /business-details` | `BusinessDetails` | 1 |
| `unanswered` | `GET /unanswered` | `[UnansweredQuestion]` | 2 |
| `windowState[key]` | `GET /whatsapp/window-state` | `{ ok, window_open, last_user_message, hours_since }` | 3 (Paul's window is closed) |
| `replyChannels[leadId]` | `GET /leads/{id}/reply-channels` | `{ available, unavailable, reasons, default, source, media }` | 2 (opted out; no phone) |

**Customers** (20): there is no customers-list route. The Customers screen is derived from `leads`, one per person, which is what the shipping apps do.

## The people (and the state each one demonstrates)

| Customer | Channel | Lead status | Shows off |
|---|---|---|---|
| Dana Whitfield | SMS | urgent | Missed call → Remy text-back, 2 unread, private note, Remy suggestion, pencilled-in 12:30 visit |
| Greg Hollis | Voicemail | new | Voicemail with audio + transcript, `is_partial`, suggestion, pending tomorrow 8–10am |
| Raj Okafor | Web chat | scheduled | Remy booked it alone; web composer = "Continue by text" |
| Leah Brooks | WhatsApp | contacted | **Remy paused** (owner replied), reaction, read ticks |
| Marta Gomez | Voicemail | new | Voicemail with **no transcript yet** |
| Nina Patel | SMS | quoted | **Held by teammate** ("Jess replying"), stuck quote |
| Paul Adeyemi | WhatsApp | quoted | **24-hour window closed** → template only |
| Owen Fry | Web chat | new | No phone, `sms_consent: false`, unanswered question for Teach Remy |
| Sara Lim | SMS | scheduled | **Muted**, confirmed job today 4 PM |
| Ivy Chen | SMS | contacted | Remy-only conversation |
| Elena Park | WhatsApp | collections | Photo message, $3,200 outstanding, job today 9 AM |
| Victor Reyes | SMS | quoted | **Opted out** (texted STOP); composer shows the server's words |
| Tom Ruiz, Kim Diaz, Grace Kim | — | closed | Done jobs, paid accounts |
| Ben Ito | WhatsApp | collections | Part-paid account |
| Priya Shah, Hannah Cole | — | scheduled | Jobs Fri and Sat |
| Marcus Webb | SMS | urgent | Missed call this morning, no conversation yet |
| Leo Novak | SMS | lost | The Lost toggle on the board |

## Contract details that change how screens draw

- `Conversation.grouping_key` is the navigation key (`sms:5550184471`, `whatsapp:…`, `voicemail:…`, `web:<session>`). Split it at the first colon for the thread route. `Lead.session_id` is **not** a navigation key; use `Lead.conversation_id`.
- `unread_count` is a number, per user.
- `paused`, `muted`, `opted_out` and `takeover_by_name` appear on both the row and the thread meta, with the same values.
- `Message.role` is open: `user`, `bot`, `owner`, `system` (missed call), `outbound_call`. Anything else renders as an event line.
- `message_status` is open: `accepted`, `queued`, `sending`, `sent`, `delivered`, `read`, `undelivered`, `failed`. Anything else is "unknown".
- `is_first_in_group` and `is_last_in_group` come from the server. Never recompute them.
- `sent_by_user_name` is recorded but **not shown** per bubble. Outbound shows "You".
- `ThreadVoicemail.audio_url` null → no play button. `transcript` null → the "no transcript" copy, which is normal.
- `Lead.status` is one of nine. `urgent` is a flag, `garbage` is never on the board, and `closed` reads "Done". Write back the raw value.
- `Lead.qualifying_data` keys vary by business; render them as humanised key/value pairs, never with a fixed layout.
- `Appointment.date` + `time_slot` are **two strings**. `time_slot` is free text ("9:00 AM", "2-4pm"). Parse it only to place the event on the calendar.
- `Call.duration_seconds` is often null; show no length then. Call history starts when recording began.
- `Account` values are **cents**. Show whole dollars.
- `SessionClient.phone` can be null → hide "Your number". Never invent one.

## Proposed (not in the contract yet)

All of these sit under `REMLY_DATA.proposed`. Each needs a contract change and Sruly's OK before the native build.

| Key | What for | Without it |
|---|---|---|
| `suggestions[grouping_key]` | Remy reply suggestions (`{ id, text, reason }`) | No suggestion card (live today: the list is empty) |
| `suggestionMode` | off / suggest / autoOpening / auto per business | Default "suggest"; the setting is stored only on the phone |
| `leadEstimatesCents[leadId]` | Money on pipeline cards, column sums, hero "~$2,400" | No amounts shown |
| `remyDigest` | "While you were out" on Today and in the Remy sheet | Block hidden |
| `billedThisWeekCents` | Today chart card | Card hidden |
| `driveMinutes[appointmentId]` | Drive chips between jobs | No drive chips |
| `onboarding` | New business setup + texting approval state | Setup flow is a design proposal only |

When you add a proposed field, add a row here.
