# ResQ-X CEO Dashboard: private management feed handoff

## Current state

The dashboard shell and read-only API adapter are implemented. **No production management feed is configured.** The API requires an authenticated visitor and returns `not_connected` until that separate connection exists. Example mode is a visual preview and is never a substitute for current company records.

The CEO website is a separate owner-private Site. Sites dispatch enforces access to this application; its API additionally requires a trusted signed-in visitor before reading any source. There is no TV mode or automatic screen rotation. The staff-TV Site has its own restricted schema, endpoint and credential; it does not host a management route. Do not share the CEO Site or sign its account into an unattended office display.

This management contract includes aggregate recognised sales, cash receipts, subscriptions and marketing costs. Profit, cash-flow forecasts, receivables ageing, bank balances and payroll are not implemented. Do not insert personal data, raw accounting exports or unsupported fields into this feed.

## Smallest integration needed

Noel/Alfred should provide a separately authorised read-only management HTTPS endpoint, implemented over the actual ResQ-X order/admin and finance data. That endpoint should aggregate confirmed Refuel deliveries, Rescue movements, Fleet OS activity and the reconciled sales ledger. Use the same order/job IDs for offline and in-app records so a sheet import cannot double-count an app transaction.

Configure these runtime environment variables in the private Site settings, then deploy the saved version:

| Variable | Purpose |
| --- | --- |
| `RESQX_CEO_FEED_URL` | Fixed public HTTPS hostname and endpoint path; no URL credentials, query string, fragment, nonstandard port or redirect. A browser cannot change this address. |
| `RESQX_CEO_FEED_TOKEN` | Bearer credential scoped to the management read endpoint, when bearer authentication is used. Use a separate credential from the staff feed. Store it as a runtime secret; never put it in source, the browser, a spreadsheet or a handoff message. |

There is no fallback to `RESQX_STAFF_FEED_URL` or the previous generic dashboard configuration. The management URL and token must be configured on this Site only. The upstream endpoint must enforce management access independently; `audience=management` is a declaration, not an authorisation mechanism.

The feed URL is an administrator-selected allowlist entry. Choose a hostname controlled by ResQ-X or its contracted data provider. The adapter blocks literal IPs and common internal hostnames; the selected provider remains responsible for its hostname and DNS.

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

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

Use Sunday–Saturday reporting weeks, matching the company's KPI scorecard. Lagos is UTC+1. A delivery at `2026-11-07T23:30:00Z` belongs to Sunday, 8 November locally. Monthly progress and targets are current-month fields independent of the selected Today/Week/Month totals.

## Response contract

The feed must return a **bare JSON snapshot**, not an `{ok,data}` wrapper. `lib/dashboard-schema.ts` is the exact, versioned contract. Unknown fields are rejected at every object level. This endpoint accepts only version 2 management snapshots; staff snapshots and the previous unlabelled version 1 payload fail validation. The separate staff contract also rejects management fields, rather than merely hiding them in its UI.

| Top-level field | Definition |
| --- | --- |
| `schemaVersion` | `2` |
| `audience` | `management` |
| `timezone` | `Africa/Lagos` |
| `period`, `periodStart`, `periodEnd` | Echo the requested window. End tolerance is at most five seconds and must not be future-dated. |
| `generatedAt` | Actual time the aggregate snapshot was assembled. Do not replace source sync timestamps with this value. |
| `sources` | Up to five independently identified sources: `refuel`, `rescue`, `fleet`, `finance`, `growth`, `product`. |
| `metrics` | All defined scalar keys are required. Send `null` for missing, unverified or unsupported measures. A true zero must be a verified zero. |
| `monthlyTargets` | `{month:"YYYY-MM",pmsLitres,agoLitres,rescueJobs}` for the current month. |
| `monthlyProgress` | `{pmsLitres,agoLitres,rescueJobs}` for the current month, regardless of selected period. |
| `trend` | Up to 31 chronological, unique daily points: `label`, `date`, `pmsLitres`, `agoLitres`, `rescueJobs`, `salesRevenue`. The display must clearly identify its chart window; points may provide a trailing comparison independently of the selected-period counters. |
| `jobs` | Up to 12 operational rows: `id`, `service`, `route`, `product`, `quantity`, `status`, `updatedAt`. Use city/zone routes, not addresses. Rescue rows must have null product and quantity. |
| `activity` | Up to eight events: `id`, `service`, `label`, `detail`, `occurredAt`. Use operational descriptions without personal information. |
| `alerts` | Up to five exceptions: `id`, `severity`, `title`, `detail`, `sourceId`. |

Each source has `id`, `label`, `status` (`ok`, `delayed`, `unavailable`), `lastSyncAt`, `lastEventAt` and `staleAfterSeconds`. `lastSyncAt` is the most recent successful ingestion or query of that source. `lastEventAt` is the timestamp of its newest known business event; a quiet day can legitimately have an old event timestamp and a fresh sync. If no source has been verified, use null timestamps and `unavailable`. Every populated metric must identify its contributing source(s). The combined `activeJobs` metric requires both Refuel and Rescue source entries.

Actual scalar metrics, monthly progress and daily trend values—including zero—also require a non-null successful-sync timestamp for every contributing source. A currently unavailable source may retain last-good figures only if that prior timestamp exists; show its unavailable status alongside them. Dated targets remain independent of operational connection status. Every alert must name a source declared in the same snapshot.

### Metric definitions

All money is in **naira**, not kobo, with taxes excluded from recognised sales. Volumes are litres. Operational counts are nonnegative integers; stock, volumes and durations are nonnegative. Net money fields can be negative after valid credit notes or refunds.

| Metric keys | Counting rule and source |
| --- | --- |
| `pmsLitres`, `agoLitres`, `completedDeliveries` | Customer-accepted physical delivery, under paid or approved-credit orders. Deduplicate stable delivery/order IDs; exclude quotes, pending orders and cancelled deliveries. Do not deduct a financial refund from physical litres unless a recorded physical return actually occurred. Refuel source. |
| `completedRescueJobs` | Unique completed job/vehicle movement with customer acceptance and proof of delivery. This is the scorecard's trips KPI. Exclude cancellations and accepted-only/ongoing jobs. Rescue source. |
| `completedTruckTrips`, `vehiclesMoved` | Keep truck journeys and customer vehicle movements separate: a double loader can move two vehicles on one journey. Rescue source. |
| `salesRevenue` | Reconciled net recognised sales across service lines. Never add Fleet OS spend for a fuel order to the same fuel sale again. Include service/delivery fees once according to Finance's policy; exclude VAT and transfers/deposits that are not earned sales. Finance source. |
| `cashCollected` | Matched receipts less cash refunds within the selected period; it is not recognised revenue. Finance source. |
| `activeJobs` | Current open dispatch work across Refuel and Rescue; a current state measure, independent of selected-period completions. |
| `activeFleets30d`, `activeUsers30d` | Distinct customer organisations/users with defined meaningful product activity in the trailing 30 days; exclude staff/test accounts and sign-in-only activity. Fleet source. |
| `payingFleets`, `trackedAssets` | Current paying accounts and in-scope registered/managed assets. Do not label tracked assets as transmitting GPS unless telemetry freshness confirms it. Fleet source. |
| `subscriptionRevenue`, `newFleetAccounts` | Net recognised subscription revenue and new qualifying Fleet OS accounts in the selected period. Subscriptions are already a component of total sales revenue, not an amount to add to it again. Fleet and Finance sources must both have a successful sync; reconcile the subscription component to Finance. |
| `adSpend`, `qualifiedLeads`, `attributedBookings`, `newCustomers` | Selected-period marketing/CRM values. Use a defined qualification rule and deduplicated customer IDs. Bookings require explicit attribution and remain distinct from completed jobs. Report null when attribution is absent. Growth source. |
| `pmsStockLitres`, `agoStockLitres` | Current reconciled usable on-hand stock; do not infer stock simply from purchase totals minus a partial sales feed. Refuel source. |
| `availableVehicles`, `totalVehicles` | Current dispatchable Rescue capacity and fleet in scope. Exclude unavailable/maintenance vehicles from available capacity. Rescue source. |
| `avgResponseMinutes`, `onTimePercent` | Rescue response interval and on-time completion percentage over qualifying records in the selected period. Document start/end event definitions; use null for no valid denominator. Rescue source. |

Use event times for business periods and `updatedAt` for ingestion. An edited record replaces its prior state instead of being counted again. Handle reversals and cancellations by stable ID. Keep physical work, bookings, recognised revenue and cash collection as separate measures.

### Monthly target source

The current KPI scorecard specifies November 2026 targets of **51,700 PMS litres**, **36,300 AGO litres** and **44 completed Rescue movements**. December specifies **56,870 PMS litres**, **39,930 AGO litres** and **49 completed movements**. Do not carry those targets into another month silently. The runtime feed should own the dated target register; unsupported months must return null targets.

Reference: [ResQX Q4 2026 Individual KPI Scorecards](https://chatgpt.com/library?file_id=libfile_6260d0cf88a08191a093cfb92be2f293&account_id=personal).

## Freshness and failure behaviour

The frontend is intended to poll every 30 seconds while open. That refresh interval does not promise 30-second source latency: a daily sales sheet remains a daily source. Each source has its own freshness threshold. Typical initial thresholds are 120 seconds for dispatch/Refuel, five minutes for Fleet OS, 15 minutes for reconciled sales, and one hour for marketing; set them to the actual ingestion agreement.

The server validates source timestamps and marks an expired source `delayed` without altering its original sync/event time. A successful HTTP response does not itself prove all sources are current. Old cached snapshots that do not cover the requested window, future timestamps, malformed JSON and impossible values are rejected. The browser should retain the last verified values only with a clear delayed/unavailable warning; it must never substitute zero.

The feed request has a 10-second timeout, a one-mebibyte body limit, no redirects and no shared caching. Upstream response bodies and credentials are never exposed in errors. The API replies are:

- `{status:"ok", snapshot, receivedAt}` after validation; `receivedAt` is retrieval time, not source freshness.
- `{status:"not_connected", message}` for an authenticated visitor while no management endpoint is configured.
- `{status:"unauthorized", message, signInPath}` when an authenticated visitor is required.
- `{status:"unavailable", message, retryAfterSeconds?}` for upstream, validation or connection failures.

## Acceptance before showing real management figures

1. Configure the management endpoint and its own runtime secret on the CEO Site only. Confirm the Site remains owner-only, an unauthenticated request receives no financial data, and no secret appears in browser responses or assets.
2. Reconcile one known day's litres, completed Rescue movements, recognised revenue and receipts against the source registers. Confirm off-app and app versions of the same job count once.
3. Observe one real transaction progress from dispatched to completed and verify it appears in the appropriate Lagos day. Test one correction/cancellation and one double-loader movement without inflating truck-trip counts.
4. Interrupt a source and verify its last good values become visibly delayed. Confirm missing figures remain unavailable and do not become zero.
5. Confirm the separate office-TV Site receives only the staff snapshot and rejects a management snapshot, including any revenue, receipts, subscriptions, advertising costs or billing counts. A link to the TV must not grant access to the CEO Site.
6. Confirm the CEO Site reconnects after an interruption and that its live labels reflect each source's actual freshness. End-to-end financial reconciliation remains pending until the production source is connected.

## Validation tests

The meaningful schema tests cover the required management audience/version and strict financial source checks as well as Lagos midnight/month boundaries, Sunday reporting weeks, malformed or extra fields, missing versus zero, negative volumes, fractional counts, signed financial corrections, duplicate records, stale sources, future timestamps and mismatched windows. They do not prove a real production source is connected.

To run without adding dependencies, compile only these test files to an ignored temporary folder and use Node's built-in test runner:

```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 lib/dashboard-schema.ts
node --test .sites-runtime/schema-tests/tests/dashboard-schema.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. These aggregate analytics blocks do not accept user IDs, personal information or free-text campaign/customer labels.

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.

### Private Marketing spend

Only the management contract permits an optional nullable `spend` object on a platform row: `{amount, currency}`. `amount` is finite and nonnegative, and `currency` is the provider's three-letter uppercase ISO currency code. Preserve native currencies: a Google Ads USD amount cannot be silently added to Meta/TikTok NGN spend. There is deliberately no mixed-currency `totalSpend` field. The legacy NGN `metrics.adSpend` stays null unless the source has a complete and documented reporting-currency conversion basis; the illustrative combined total is also blank. Any displayed cost per result must use a same-platform, same-period supported denominator and retain that platform's currency. Provider conversions remain distinct from confirmed CRM bookings.
