# Live MyGeotab validation guide

**Status:** MyGeotab host integration is **implemented in source** (mock mode + unit tests also in place).  
**Live integration inside an actual MyGeotab database has NOT been verified yet.**  
**Production `/addin/*` hosting has NOT been verified yet.**

Do not treat this Add-in as production-ready or live-validated until the checklist in this document has been executed against a real MyGeotab database with onboard-scale diagnostics, and production (or staging) `/addin/*` URLs respond correctly.

---

## Verification categories

### A. Code verified by unit tests

Covered by `npm run test -w @cleral/addin` (mocked `api.call` / `multiCall` and pure processing):

- `weight = StatusData.data / Diagnostic.conversion`
- `"Onboard scale"` filtering
- Invalid conversion / missing timestamp / non-finite data handling
- Diagnostic id extraction and multiCall flatten
- Device → Vehicle normalization (mocked API)
- DeviceStatusInfo → location (mocked API)
- Discovery → StatusData → WeightEvent path (mocked API)
- Cycle windowing, open/completed cycle, previous-cycle merge
- Channel / Total error precedence
- Total-first ordering
- Empty / no-data placeholders (channel A / B)

### B. Behavior verified only by source inspection

Present in source and previous code review; **not** proven inside a MyGeotab host:

- `geotab.addin.cleral` registration (`registerCleralAddin`)
- `initialize` / `focus` / `blur` wiring
- Exact Device / StatusData / Diagnostic / DeviceStatusInfo request shapes vs legacy
- 30-minute discovery + `< 8` → 120-minute fallback
- Provider switch mock → MyGeotab on `initialize`
- 5-second refresh + in-flight overlap guard
- Vite `base: "/addin/"` and dist asset prefixes
- `manifest.json` contents and relative `index.html` / `icon.svg` resolution

### C. Behavior requiring an actual MyGeotab database

Must be confirmed in a live (or sandbox) MyGeotab environment:

- Host loads the Add-in page and invokes `geotab.addin.cleral(api, state)`
- Host calls `initialize` / `focus` / `blur` at the expected times
- Real Device list for the signed-in user
- Real DeviceStatusInfo lat/lng for selected devices
- Real onboard-scale Diagnostic names and conversion factors
- Real StatusData volume/timing for cycle windowing
- Embedded iframe security / CORS / cookie context
- Menu path `CLERAL/` appearance and icon
- End-to-end weight UI matching fleet scales

---

## Exact production URLs

| Purpose | URL |
|---------|-----|
| Add-in app (entry) | `https://app.cleraladdins.com/addin/` |
| Add-in index (explicit) | `https://app.cleraladdins.com/addin/index.html` |
| Manifest / Add-in config | `https://app.cleraladdins.com/addin/manifest.json` |
| Icon (resolved from manifest) | `https://app.cleraladdins.com/addin/icon.svg` |

Vite build `base` is `/addin/`. Dashboard Next.js remains at `https://app.cleraladdins.com/` (not under `/addin/`).

**Prerequisite:** Edge routing must serve `apps/addin/dist` at `/addin/*`. If that host path is not deployed yet, use a temporary HTTPS URL that serves the same `dist` contents and substitute that origin in the steps below.

Local mock-only (not MyGeotab host): `http://localhost:5173/addin/`

---

## Exact manifest contents

Source: `apps/addin/public/manifest.json` (copied to build output as `dist/manifest.json`).

```json
{
  "name": "CLERAL Weight Monitoring",
  "supportEmail": "support@cleraladdins.com",
  "version": "0.1.0",
  "items": [
    {
      "url": "index.html",
      "path": "CLERAL/",
      "menuName": {
        "en": "CLERAL Weight Monitoring"
      },
      "icon": "icon.svg"
    }
  ],
  "isSigned": false
}
```

Notes:

- Relative `url` / `icon` resolve against the manifest URL base → `/addin/index.html` and `/addin/icon.svg`.
- Add-in factory key registered in code: **`geotab.addin.cleral`** (legacy-compatible name).
- `isSigned: false` — unsigned custom Add-in; follow your MyGeotab org policy for unsigned add-ins.

---

## Prerequisites

1. Built Add-in artifacts available at the production (or staging) `/addin/` URL:
   ```bash
   npm run build -w @cleral/addin
   # Deploy apps/addin/dist → https://app.cleraladdins.com/addin/
   ```
2. Public HTTPS access to:
   - `…/addin/`
   - `…/addin/manifest.json`
   - `…/addin/index.html`
   - `…/addin/assets/*` (hashed JS/CSS with `/addin/` prefix)
3. MyGeotab database with:
   - At least one **Device** visible to the test user
   - Onboard scale **Diagnostic** names containing `"Onboard scale"`
   - Recent **StatusData** for those diagnostics (ideally within last 30–120 minutes)
   - **DeviceStatusInfo** with valid latitude/longitude for map checks
4. User permission to install custom Add-ins in that database
5. Browser DevTools access while the Add-in is open

---

## Required MyGeotab Add-in configuration

### Setup steps

1. Sign in to the target MyGeotab database.
2. Open **Administration → System Settings → Add-Ins** (label may vary by MyGeotab version).
3. Add a custom Add-in using the configuration URL:
   - **`https://app.cleraladdins.com/addin/manifest.json`**
4. Save / enable the Add-in for the database (or test group).
5. Confirm the menu entry appears under path **`CLERAL/`** as **CLERAL Weight Monitoring**.
6. Open the Add-in page from the MyGeotab UI (embedded host).

If the installer rejects relative URLs, confirm the browser can open the resolved page URL directly:

`https://app.cleraladdins.com/addin/index.html`

Do **not** put MyGeotab credentials in the Add-in frontend. The host injects `api`.

---

## Expected runtime behavior (from source)

### 1. `initialize(api, state, callback)`

When the host calls initialize:

1. Store `api` and call `activateMyGeotabProvider(api)` (switches off mock).
2. Set add-in focused = true.
3. Call `Get` **Device** with `search.fromDate: new Date()`.
4. Always invoke `callback()` when the vehicle load finishes or fails (errors are logged).

### 2. `focus()`

1. Set focused = true.
2. Re-bind MyGeotab provider if `api` was stored.
3. Reload vehicles via Device `Get`.

### 3. `blur()`

1. Set focused = false.
2. Refresh work becomes a no-op (interval may still tick; requests are gated).
3. Provider stays on MyGeotab (does not flip back to mock).

### 4. Device request

```text
api.call("Get", {
  typeName: "Device",
  search: { fromDate: <Date now> }
})
```

Normalized to UI `Vehicle`: `id`, `name`, `deviceId` (= device id).

### 5. DeviceStatusInfo request

On vehicle select / refresh:

```text
api.call("Get", {
  typeName: "DeviceStatusInfo",
  search: { deviceSearch: { id: <deviceId> } }
})
```

Uses `result[0].latitude`, `longitude`, optional `dateTime`.

### 6. StatusData (discovery)

```text
api.call("Get", {
  typeName: "StatusData",
  search: {
    deviceSearch: { id: <deviceId> },
    fromDate: <now - N minutes ISO>,
    toDate: <now ISO>
  },
  resultsLimit: 10000
})
```

- First pass: **N = 30**
- If fewer than **8** onboard-scale diagnostics after resolution/filter: second pass **N = 120**

### 7. Diagnostic resolution

For each unique diagnostic id from StatusData:

```text
api.multiCall([
  ["Get", { typeName: "Diagnostic", search: { id: <diagnosticId> } }],
  ...
])
```

Keep diagnostics whose `name` includes **`Onboard scale`**. Use returned `conversion`.

### 8. StatusData (per diagnostic, weights)

For each onboard-scale diagnostic, last **30 minutes**:

```text
api.call("Get", {
  typeName: "StatusData",
  search: {
    deviceSearch: { id: <deviceId> },
    diagnosticSearch: { id: <diagnosticId> },
    fromDate: <now - 30m ISO>,
    toDate: <now ISO>
  }
})
```

### 9. Weight calculation

```text
weight = StatusData.data / Diagnostic.conversion
```

Skip invalid conversion (`0`, non-finite), non-finite `data`, or missing `dateTime`.  
Build `WeightEvent { name, weight, timestamp, diagnosticId? }`.

### 10. Cycle processing

`WeightEvent[]` → `processWeightEvents()` (unchanged legacy rules):

- Total gross cycle markers, open vs completed cycle
- Previous-cycle merge when open/incomplete
- Channel A/B/C/D, E (A+B), Total
- Error precedence → UI “Error”
- Total row first

### 11. 5-second refresh

After a vehicle is selected in the React UI:

- Initial load of weight + vehicle (location)
- Interval **5000 ms**
- Skip starting a new refresh while one is in flight
- Skip while blurred / on unmount (interval cleared)

---

## What to verify after installation

### Vehicle selector

- [ ] Dropdown populates with Devices from the database (not mock names like `CLERAL-G9` unless those are real devices).
- [ ] Empty selection shows “Select a vehicle”.
- [ ] Changing vehicle clears previous cycle and loads the new device.

### Weight behavior

- [ ] Total appears first when data exists.
- [ ] Channels are data-driven (A/B/C/D/E as present in diagnostics).
- [ ] Net / gross show kg values or `—` / `Error` appropriately.
- [ ] Values can change across refreshes when StatusData updates.
- [ ] Cycle logic behaves sanely vs known scale cycles (compare to legacy Add-in if available).

### Map behavior

- [ ] Marker moves to DeviceStatusInfo lat/lng for the selected vehicle.
- [ ] Popup shows vehicle name and coordinates.
- [ ] If location missing, map does not crash (may keep prior/default center).

### Refresh behavior

- [ ] Approximately every 5 seconds, weight and/or location update while Add-in is focused.
- [ ] Leaving the Add-in (`blur`) stops meaningful refresh work.
- [ ] Returning (`focus`) reloads vehicles and resumes refresh when a vehicle is selected.

### Error / no-data behavior

- [ ] Device with no onboard-scale diagnostics → no-data / placeholder channels (A & B) without crashing.
- [ ] API failures log to console; UI shows error/no-data states rather than a blank white failure.
- [ ] Channel or Total scale errors display **Error** when error timestamps are newer than weights (when such diagnostics exist).

---

## Browser console checks

With the Add-in open inside MyGeotab (DevTools on the Add-in frame if sandboxed):

1. Confirm no uncaught `geotab is undefined` (host should provide `geotab`; registration also soft-creates a shell).
2. Confirm `typeof geotab.addin.cleral === "function"` after load (or after host registration).
3. Watch for logged errors:
   - `MyGeotab Device request failed`
   - `MyGeotab DeviceStatusInfo request failed`
   - `MyGeotab onboard-scale discovery failed`
   - `MyGeotab StatusData failed for diagnostic …`
   - `MyGeotab Diagnostic multiCall failed`
4. On success you should **not** see a long stream of those errors every 5s.

Optional temporary logging (do not leave in production): verify `initialize` / `focus` / `blur` fire when navigating away and back.

---

## Network / API checks

MyGeotab API traffic is usually host-mediated (not always visible as plain REST from the Add-in origin). Prefer:

1. MyGeotab / browser tooling that surfaces `api.call` / `multiCall` if available.
2. Or temporary debug counters in a **local branch only** (do not commit for this doc’s baseline).

Expect conceptually:

| When | Calls |
|------|--------|
| initialize / focus | Device Get |
| select vehicle + each refresh | DeviceStatusInfo + StatusData discovery (+ maybe 120m) + Diagnostic multiCall + StatusData per diagnostic |

Asset checks (normal HTTP):

- [ ] `GET https://app.cleraladdins.com/addin/manifest.json` → 200, JSON as above
- [ ] `GET https://app.cleraladdins.com/addin/index.html` → 200
- [ ] JS/CSS under `/addin/assets/…` → 200 (not 404 to dashboard HTML)

---

## Troubleshooting checklist

| Symptom | Likely cause | What to check |
|---------|--------------|---------------|
| Menu item missing | Manifest not installed / wrong URL | Manifest URL 200; Add-Ins list enabled |
| Blank Add-in frame | `/addin/` not routed to Vite dist | `index.html` and `/addin/assets/*` 200; not dashboard HTML |
| JS 404 | Wrong `base` or incomplete deploy | Asset paths must start with `/addin/` |
| Mock vehicles in host | `initialize` never ran / still mock provider | Confirm host calls `geotab.addin.cleral` → `initialize` |
| Empty vehicle list | Device Get failed or no devices | User permissions; console Device errors |
| No weights | No `"Onboard scale"` diagnostics or no StatusData in window | Diagnostic names; 30/120m data presence |
| Weights look wrong | conversion factor | Compare `data/conversion` to legacy Add-in |
| Map stuck / wrong | DeviceStatusInfo empty | Device has live status; console location errors |
| Refresh stops | blur / tab hidden / overlap stuck | Refocus Add-in; reload page |
| Constant errors every 5s | API failing for selected device | Pick another device; inspect console |

---

## Known limitations

- **Live MyGeotab validation has not been completed** in this repository’s development environment.
- Production `/addin/*` hosting must be deployed before the production URLs above work.
- 120-minute fallback runs whenever onboard-scale count `< 8` after the 30-minute pass (including empty); legacy returned early on empty StatusData without retry — behavior may differ slightly.
- On `blur`, the 5s timer is gated rather than cleared (requests no-op until focus).
- Each refresh may perform **two** DeviceStatusInfo reads (`getVehicleWeight` + `getVehicle` in parallel).
- `IoxAddOn` / Trip / LogRecord from the legacy file are **not** used for the modern weight UI.
- Unsigned Add-in (`isSigned: false`) may be restricted by some organizations.
- Unit tests **do not** prove host lifecycle timing, iframe security, or real diagnostic catalogs.

---

## Sign-off template (fill after live test)

| Field | Value |
|-------|--------|
| Tester | |
| Date | |
| MyGeotab database | |
| Add-in URL used | |
| Manifest URL used | |
| Devices tested | |
| Onboard-scale diagnostics observed | |
| initialize/focus/blur OK? | ☐ |
| Weights match expectation? | ☐ |
| Map OK? | ☐ |
| 5s refresh OK? | ☐ |
| Errors/no-data OK? | ☐ |
| Result | ☐ Pass / ☐ Fail |
| Notes | |

**Only after this sign-off is completed may live MyGeotab integration be claimed as verified.**
