# ResQ-X staff TV: operational feed handoff

## Current state and audience

The staff display and its read-only API adapter are implemented. **No production operational feed is configured.** Actual figures remain unavailable until that connection exists. Example mode is explicitly illustrative and is never returned by the API as a live fallback.

The office TV and private CEO dashboard are separate applications with separate data contracts and runtime credentials. This application accepts only the **staff** contract below. It has no management endpoint, financial metrics, financial trend, billing counts or management-view switch. Its production Site remains owner-private until an approved display session is set up. Connecting a TV does not require granting every member of staff management access.

The staff payload is an allowlist of operational values. Revenue, receipts, margins, bank balances, subscription amounts, marketing spend, customer prices, payroll and paying-account counts are excluded. Customer names, contact details, exact addresses and arbitrary notes are also excluded. Do not connect the management endpoint with its more powerful credential to this Site.

## Smallest integration needed

Noel/Alfred should provide a dedicated read-only HTTPS endpoint over the actual Refuel orders, Rescue jobs, Fleet OS usage and operational CRM counts. Generate the staff snapshot at that boundary. Use stable source record IDs to reconcile offline and in-app transactions before aggregation; the display identifiers must be opaque references without personal or commercial details.

Configure these runtime environment variables in the private staff Site settings:

| Variable | Purpose |
| --- | --- |
| `RESQX_STAFF_FEED_URL` | Dedicated staff endpoint on a fixed public HTTPS hostname. No URL credentials, query string, fragment, custom port or redirect. |
| `RESQX_STAFF_FEED_TOKEN` | Optional bearer credential scoped to read the staff endpoint only. Store it as a runtime secret. |

The former combined-feed variable names are deliberately unsupported. There is no fallback to a management feed. The browser cannot select another endpoint, add a management audience, or obtain the credential.

Use a hostname controlled by ResQ-X or its contracted data provider. The adapter blocks literal IP addresses and common internal hostnames. The selected provider remains responsible for its hostname and DNS.

The configured endpoint receives a GET request with `Accept: application/json`, the optional bearer header, and these query parameters:

| Query | Meaning |
| --- | --- |
| `audience` | Always `staff` |
| `period` | `today`, `week` or `month` |
| `start` | Inclusive ISO timestamp at the Africa/Lagos calendar boundary |
| `end` | Exclusive ISO timestamp at the request instant |
| `timezone` | Always `Africa/Lagos` |

Use Sunday–Saturday reporting weeks. Lagos is UTC+1. Business output belongs to its actual completion time. Monthly progress and dated targets are independent of the selected Today/Week/Month counters.

## Strict response contract

Return a **bare JSON snapshot**, not a wrapper. `lib/dashboard-schema.ts` is the exact contract. The server validates it before returning anything to the browser. An unknown financial field, legacy combined snapshot, arbitrary description or wrong audience rejects the **entire** snapshot; it is not quietly forwarded or hidden in the UI.

| Top-level field | Definition |
| --- | --- |
| `schemaVersion` | Exactly `2` |
| `audience` | Exactly `staff` |
| `timezone` | Exactly `Africa/Lagos` |
| `period`, `periodStart`, `periodEnd` | Requested reporting window; end tolerance is at most five seconds and cannot be future-dated. |
| `generatedAt` | Time the aggregate snapshot was assembled, separate from individual source freshness. |
| `sources` | Up to five sources: `refuel`, `rescue`, `fleet`, `growth`, `product`. |
| `metrics` | All defined scalar keys are required. Missing or unverified measures use `null`; zero requires source evidence. |
| `monthlyTargets` | `{month:"YYYY-MM",pmsLitres,agoLitres,rescueJobs}` for the reporting month. |
| `monthlyProgress` | `{pmsLitres,agoLitres,rescueJobs}` for that month regardless of selected period. |
| `trend` | Up to 31 unique chronological points: `date`, `pmsLitres`, `agoLitres`, `rescueJobs`. No label or monetary series. |
| `jobs` | Up to 12 rows: `id`, `service`, `originArea`, `destinationArea`, `product`, `quantity`, `status`, `updatedAt`. |
| `activity` | Up to eight typed events, as defined below. |
| `alerts` | Up to five typed operational exceptions, as defined below. |

All objects are strict, including nested rows. Every object must contain only its documented keys. The application generates source names, chart day labels, route wording, event descriptions and alert copy locally in `lib/staff-display.ts`.

### Sources and timestamps

Each source has `id`, `status` (`ok`, `delayed`, `unavailable`), `lastSyncAt`, `lastEventAt` and `staleAfterSeconds`. **Do not send a `label`.** `lastSyncAt` is the most recent successful source ingestion/query. `lastEventAt` is its newest known business event; a quiet day may have an old event and a fresh sync. An unverified source has null timestamps and `unavailable` status.

Each populated metric, monthly progress value and trend point needs a non-null successful-sync timestamp for every contributing source. Even zero needs evidence. Combined `activeJobs` requires both Refuel and Rescue. A currently unavailable source may retain last-good values only when its prior successful sync exists. Targets are independently dated and can exist before operational connection. Events require an observed source; alerts must name a declared source.

### Operational metric definitions

Volumes and stock use litres. Counts are nonnegative integers. Durations are nonnegative, and percentages range from 0 to 100. Use `null` where no valid denominator or source exists.

| Metric keys | Counting rule and source |
| --- | --- |
| `pmsLitres`, `agoLitres`, `completedDeliveries` | Customer-accepted physical deliveries under paid or approved-credit orders. Deduplicate delivery IDs; exclude quotes, cancellations and pending work. A financial adjustment alone does not reverse physical litres. Refuel source. |
| `completedRescueJobs` | Unique completed job/vehicle movement with customer acceptance and proof of delivery. Exclude accepted-only, ongoing and cancelled jobs. Rescue source. |
| `completedTruckTrips`, `vehiclesMoved` | Truck journeys and customer vehicle movements remain separate: one double-loader journey may move two vehicles. Rescue source. |
| `activeJobs` | Current open dispatch work across Refuel and Rescue, independent of the selected completion period. Both sources. |
| `activeFleets30d`, `activeUsers30d` | Distinct customer organisations/users with meaningful workflow activity in the trailing 30 days; exclude staff, tests and sign-in-only activity. Fleet source. |
| `trackedAssets` | Current in-scope registered/managed assets. Do not imply live GPS transmission without telemetry evidence. Fleet source. |
| `newFleetAccounts` | New qualifying Fleet OS accounts in the selected period, without billing status or subscription amounts. Fleet source. |
| `qualifiedLeads`, `attributedBookings`, `newCustomers` | Deduplicated operational CRM counts in the selected period. Define qualification and customer conversion. Attributed bookings require a campaign-to-job relationship; use null when unavailable. Growth source. |
| `pmsStockLitres`, `agoStockLitres` | Current reconciled on-hand stock, not an estimate from a partial order history. Refuel source. |
| `availableVehicles`, `totalVehicles` | Current dispatchable Rescue capacity and fleet in scope. Exclude maintenance/unavailable vehicles from capacity. Rescue source. |
| `avgResponseMinutes`, `onTimePercent` | Defined Rescue response interval and on-time completion percentage over qualifying records. Rescue source. |

An edited source record replaces its prior state; it must not be counted again. Handle cancellations and corrections through the same stable record. Keep physical output, attributed bookings and current state counts distinct.

### Safe job rows

`id` must match `^(JOB|EVT|ALT)-[A-F0-9]{8,24}$`, for example `JOB-00000241`. These are opaque display IDs, never customer names, phone numbers, invoice descriptions or payment references. Prefer the corresponding prefix for jobs, events and alerts. Maintain any source-to-display-ID mapping only in the adapter.

`service` is `refuel` or `rescue`. `status` is `queued`, `dispatched`, `in_progress`, `completed` or `delayed`. Fuel `product` is `PMS`, `AGO` or null; quantities are litres or null. Rescue rows require null product and quantity.

`originArea` and `destinationArea` must be one of:

`yard`, `lagos`, `lekki`, `ikoyi`, `victoria_island`, `ikeja`, `surulere`, `abuja`, `service_area`.

Use `service_area` when a location is outside the approved coarse labels or should not be shown. Never send an address or a custom `route` string. Adding a new office-approved area requires a reviewed enum and label update.

### Typed activity

Every event has `id`, `kind`, `service` and `occurredAt`. Its extra fields depend on its kind:

| Kind | Service | Additional fields |
| --- | --- | --- |
| `delivery_completed` | `refuel` | `product` (`PMS` or `AGO`), `quantity` (litres or null), `area` (approved area code). |
| `rescue_completed` | `rescue` | `originArea`, `destinationArea`, `vehiclesMoved` (count or null). |
| `fleet_activated` | `fleet` | `assetsAdded` (count or null). |

No `label`, `detail`, note, organisation name or free-text field is accepted. Unknown event kinds are rejected until intentionally added to the staff contract.

### Typed alerts

Every alert has only `id`, `severity` (`warning`, `critical`, `info`), `kind` and `sourceId`.

| Kind | Allowed source | Locally generated meaning |
| --- | --- | --- |
| `low_pms_stock`, `low_ago_stock` | `refuel` | Review replenishment against scheduled deliveries. |
| `job_delayed` | `refuel` or `rescue` | Review dispatch and expected completion. |
| `vehicle_unavailable` | `rescue` | Review dispatchable capacity. |
| `source_delayed`, `source_unavailable` | Any declared staff source | Review connection freshness before using retained figures. |

There are no free-text titles/details, financial exception codes or customer account alerts in the staff feed.

### Dated targets

Approved November 2026 operational targets are **51,700 PMS litres**, **36,300 AGO litres** and **44 completed Rescue movements**. December targets are **56,870 PMS litres**, **39,930 AGO litres** and **49 completed movements**. Do not silently carry a previous month's target forward. The production adapter should own a dated register; unsupported months return null targets.

## Freshness, access and failure behaviour

The display checks every 30 seconds while open. This interval does not make a delayed underlying source current. Use thresholds that match actual source ingestion; initial examples are 120 seconds for Refuel/dispatch, five minutes for Fleet OS and one hour for CRM.

The server preserves each source's sync/event timestamps and marks expired sources delayed. Old snapshots with the wrong reporting window, future timestamps and impossible values are rejected. Retained browser values must show the failed/delayed state. Unknown values never become zero.

The adapter has a 10-second timeout, a one-mebibyte body limit, no redirects and private no-store responses. The API requires a signed-in visitor before requesting configured operational data. Site access policy is an additional boundary. Upstream bodies, connection details and credentials are never returned in errors.

The local browser API is `GET /api/dashboard?period=today|week|month`; no other client query parameter is accepted. Its replies are:

- `{status:"ok", snapshot, receivedAt}` for a validated staff snapshot. Retrieval time does not replace source freshness.
- `{status:"not_connected", message}` while the staff endpoint is unconfigured.
- `{status:"unauthorized", message, signInPath}` when authentication is missing.
- `{status:"unavailable", message, retryAfterSeconds?}` for connection, upstream or validation failures.

## Acceptance before office use

1. Configure the staff-scoped endpoint and credential. Confirm anonymous requests cannot cause an upstream read.
2. Deliberately return a management field, legacy snapshot or arbitrary note. Confirm the staff API rejects it without returning the sensitive value.
3. Reconcile one known day's litres, completed Rescue movements and trip/vehicle counts against operational registers. Verify app and offline copies of a job count once.
4. Observe a real completion in the correct Lagos day; test a correction, a cancellation and a double-loader movement.
5. Interrupt a source and confirm retained values are visibly delayed. Confirm missing metrics remain unavailable.
6. Set up an approved read-only office display session and test actual TV resolution, rotation, reconnection and Lagos midnight. Neither production feed nor office-device authentication is verified until these are connected.

## Automated validation

Tests cover the audience boundary, rejection of monetary/private fields and free text, structured display copy, anonymous access, destination/query isolation, safe errors, source freshness, missing versus zero, duplicate records, reporting windows and impossible values. They do not establish that a production feed is connected.

Run the focused tests without adding dependencies:

```sh
node node_modules/typescript/bin/tsc --outDir .sites-runtime/schema-tests --module nodenext --moduleResolution nodenext --target es2022 --strict --esModuleInterop --skipLibCheck tests/dashboard-schema.test.ts tests/staff-feed.test.ts tests/analytics.test.ts
node --test .sites-runtime/schema-tests/tests/dashboard-schema.test.js .sites-runtime/schema-tests/tests/staff-feed.test.js .sites-runtime/schema-tests/tests/analytics.test.js
```


## Product usage and Marketing extensions

The version 2 snapshot now accepts optional nullable `product` and `marketing` blocks. Omitting a block or returning `null` means **that connection is unavailable**, not zero activity. An older v2 feed still validates; the new screens remain blank until their actual data is supplied. The exact strict types are in `lib/analytics-schema.ts`.

`product` is an additional source ID. A populated Product block requires a successful sync for that source. Marketing platform and CRM blocks use the `growth` source, with separate freshness and coverage for every platform and for the CRM. A successful ads query never certifies CRM bookings.

### Freshness and reporting coverage

The Product block, each Marketing platform row and the Marketing CRM object all include:

| Field | Contract |
| --- | --- |
| `status` | `ok`, `delayed` or `unavailable`. Failed refreshes preserve their last-good timestamps and are never marked current. |
| `coverage` | `complete`, `partial` or `unavailable` for the requested reporting interval. |
| `lastSyncAt` | Successful source-query/ingestion timestamp, or null. |
| `dataThrough` | Exclusive timestamp through which the requested aggregate has coverage, or null. A newly fetched report can still cover only an earlier day. |
| `staleAfterSeconds` | A source-specific threshold from 15 to 86,400 seconds. |

Even a verified zero needs both timestamps. `dataThrough` cannot be later than the sync or the requested period end. An older reporting cutoff must declare `partial` coverage. Analytics timestamps cannot exceed the snapshot generation or their contributing source's sync. A prior-period cutoff at or before `periodStart` cannot populate current-period analytics; return null values or an unavailable block instead. Preserve provider lag instead of filling omitted days with zero. The screen distinguishes partial coverage from missing data; querying every 30 seconds does not remove upstream latency.

### Product block

| Object | Required fields inside a supplied block |
| --- | --- |
| `users` | `registeredTotal`, `newUsers`, `activeUsers`, `orderingUsers`, `returningUsers` |
| `orders` | `placed`, `completed`, `cancelled` |
| `channels` | Up to five unique rows: `id`, `ordersPlaced`, `activeUsers` |
| `services` | Up to three unique rows: `id`, `activeUsers`, `completedActions` |
| `whatsapp` | `conversations`, `ordersPlaced`, `handoffs` |

All measures are nonnegative integer counts or null. All nested objects are strict.

Channel IDs are `app`, `dashboard`, `whatsapp_ai`, `assisted` and `unknown`. These describe **where an order was originally created**. `assisted` covers staff/telephone/partner-assisted orders. Retain `unknown` when origin attribution is missing. An order initiated by WhatsApp AI and later visible in the admin dashboard counts once, under `whatsapp_ai`. Do not classify it again when dispatched, edited or viewed. Complete channel order partitions must reconcile to the total unique placed-order count; incomplete partitions keep missing counts null.

Service IDs are `refuel`, `rescue` and `fleet_os`. They describe the ResQ-X service used, independently of channel. Refuel and Rescue `completedActions` represent completed orders/jobs; Fleet OS actions represent completed meaningful workflows. These are different action types, so they are not added into a shared order/conversion denominator. The "most used" comparison uses unique active users per service, reports ties, and has no winner when any service is missing, coverage is incomplete or all counts are zero.

User counting rules:

- `registeredTotal` is the current number of in-scope registered customer identities, independent of the selected period.
- `newUsers` is newly registered customer identities in the selected period.
- `activeUsers` is distinct registered customer identities with meaningful product activity in that period. A verified quote request, order creation or completed Fleet OS workflow qualifies; a login, page heartbeat or background sync alone does not.
- `orderingUsers` is the distinct registered customers who placed at least one qualifying order during that period.
- `returningUsers` is active customers who also had meaningful activity before the period began.
- Exclude staff, demo/test accounts, automated bot/system events and duplicate account records. Resolve cross-device/channel identities to a canonical customer ID before aggregation. Anonymous and guest traffic is excluded from these registered/new/active-user totals until it resolves to a registered canonical identity. WhatsApp conversation totals may include guests, but those conversations must not inflate registered-user counts.
- Channel and service user counts overlap. **Never sum them to produce total unique users.** One customer can use multiple channels and services.

Placed orders use creation time; completed orders use completion time; cancellations use cancellation time. These event populations can include different order cohorts. In particular, completed orders can exceed newly placed orders on a day with backlog clearance. Do not display `completed / placed` as a conversion rate without a matching cohort definition.

WhatsApp conversations and handoffs are operational events, not confirmed orders. `whatsapp.ordersPlaced` must reconcile to the same `whatsapp_ai` channel order partition. A conversation can produce multiple orders, and a handoff can concern a conversation started earlier.

### Marketing block

`marketing.platforms` contains up to five unique rows. IDs are:

`meta_ads`, `google_ads`, `tiktok_ads`, `instagram`, `search_console`.

Every platform row contains its freshness/coverage fields plus nullable `impressions`, `reach`, `contentViews`, `clicks`, `engagements`, `conversations`, `newFollowers` and `reportedConversions`.

Use the actual provider's definition for each field. Unsupported metrics stay null. `reportedConversions` may be fractional because of attribution; CRM booking counts remain integers. `reach` is provider-reported unique reach for the selected period—never a sum of daily reach. Platform audiences overlap and cannot be summed into a unique business audience. Instagram content metrics may include boosted content; do not label them organic-only. `newFollowers` means gross follower additions, not net follower growth or total current followers.

`marketing.crm` contains its own freshness/coverage fields and nullable `enquiries`, `qualifiedLeads`, `confirmedBookings`, `newCustomers` and `unattributedBookings`.

- CRM events need stable lead, customer and booking IDs with a written qualification rule.
- `confirmedBookings` is all unique confirmed CRM bookings in the selected booking-time period, including bookings lacking marketing attribution.
- `unattributedBookings` is the subset without a valid primary campaign/source match. It cannot exceed confirmed bookings.
- A provider's conversion, message conversation or click must not be copied into qualified leads, bookings, new customers or fulfilled jobs.
- Leads qualified and bookings confirmed during a period can arise from earlier enquiries. Do not infer a funnel conversion rate from these period totals.
- Marketing source/campaign attribution and Product order-creation channel are different dimensions. For example, a customer may discover ResQ-X through an ad and place the order in the app. Preserve both attributes without duplicating the order.

### Source implementation checklist

The internal adapter needs a stable `orderId` / `leadId` / `eventId`, canonical customer ID, service, immutable `originChannel`, event type, event time, and production/test/staff flags. Preserve original creation time and completion/cancellation timestamps. Keep marketing source, campaign ID and attribution rule separately from the order's creation channel. Upsert corrections by stable ID; do not add a second event when processing retries.

Keep all row-level identity and event data inside the authorised source system or server-side adapter. The office payload contains only the aggregate counts and allowlisted enum values above. No user IDs, personal information or free-text campaign/customer labels belong in these blocks.

Before calling the new screens live, reconcile one known app order, one web-dashboard order and one WhatsApp AI order to the underlying records; ensure the WhatsApp order's admin copy is not counted twice. Verify one cross-channel returning user, a cancellation, a backlog completion and an incomplete platform-report day. Disconnect the Product source while leaving operations connected: Product must still show unavailable data. Disconnect CRM while ads remain readable: bookings and qualified leads must remain unavailable.

### Office privacy boundary

The staff Marketing contract accepts no `spend`, budget, price, revenue, cost-per-result or other monetary field, including nested platform and CRM rows. Returning a management platform object with `spend` makes the whole staff response invalid. The staff screen therefore cannot receive a hidden financial dataset.
