# Legacy MyGeotab Add-in Analysis

**Source:** `mygeotab-cleral-main.zip` → preserved at `legacy/mygeotab-cleral/`  
**Files:** `cleral.html` (~1,100 lines, HTML + inline JS), `style.css`  
**Date of analysis:** September 2026

This document captures the current business behavior of the CLERAL MyGeotab Add-in. The new SaaS / modern Add-in must preserve these functional concepts.

---

## 1. Current architecture

| Layer | Implementation |
|--------|----------------|
| Entry | MyGeotab Add-in factory: `geotab.addin.cleral = function(api, state)` |
| Lifecycle | `initialize` → `focus` → `blur` |
| UI | Single-page HTML: vehicle select overlay, weight table, Leaflet map, time dashboard |
| Logic | All business logic inline in `cleral.html` (no modules/bundler) |
| Styling | `style.css` + Pure CSS CDN + Leaflet CSS CDN |
| Libraries (CDN) | Leaflet 0.7.3, Leaflet.MovingMarker, Moment.js 2.10.3, Pure CSS 0.6.0 |

Layout: `#cleral-container` is a **2-column grid** — left: weight info, right: full-height map. Overlays: vehicle select (top-right), time dashboard (bottom-right).

---

## 2. Current dependencies

- **MyGeotab Add-in API** (`api.call`, `api.multiCall`) — no npm package; injected by MyGeotab host
- **Leaflet** — map tiles via OpenStreetMap (`http://{s}.tile.osm.org/...`)
- **Moment.js** — time display (`lll` format)
- **Pure CSS** — form/grid helpers
- **Leaflet.MovingMarker** — trip replay (mostly unused / commented in current UX)

No build step, TypeScript, or package manager.

---

## 3. MyGeotab integration

### Lifecycle

1. **`initialize(api, state, callback)`** — creates map; loads vehicles via `Get Device`; calls `callback` when ready.
2. **`focus()`** — resets UI, binds vehicle-select handler, populates vehicle dropdown.
3. **`blur()`** — clears auto-refresh interval and resets UI.

### API calls used

| TypeName | Purpose |
|----------|---------|
| `Device` | List vehicles (`search.fromDate: new Date()`) |
| `StatusData` | Raw diagnostic readings for a device (time window) |
| `Diagnostic` | Resolve diagnostic IDs → names + `conversion` factor |
| `DeviceStatusInfo` | Live lat/lng for map marker |
| `IoxAddOn` | Fetched per vehicle (logged only; not shown in UI) |
| `Trip` / `LogRecord` | Trip replay helpers exist but trip UI is commented out |
| `Group` | Fetched in an alternate/unused multiCall path |

### Weight value formula

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

Events are shaped as: `{ name, weight, timestamp }` where `name` comes from the Diagnostic (must include `"Onboard scale"`).

### Refresh / lookback

1. Discover onboard-scale diagnostics: StatusData over **last 30 minutes**; if fewer than **8** matching diagnostics, retry with **120 minutes**.
2. For each diagnostic, fetch StatusData for **last 30 minutes**.
3. After vehicle selection: initial load + **auto-refresh every 5 seconds**.
4. Interval cleared on vehicle change, blur, or empty selection.

---

## 4. Weight / channel logic (business rules)

### Channel naming

Diagnostic names are normalized with `normalizeChannelKey`:

- Take text **before the first `:`**
- Strip trailing ` error` when name contains `"error on channel"` or ends with `" error"`

Known channel concepts:

| Key pattern | Display name | Sort rank |
|-------------|--------------|-----------|
| `A+B+C+D` (total) | **Total** | 0 |
| steer | (raw / cleaned) | 1 |
| `channel A` (not combined) | e.g. `channel A` | 2 |
| `channel B` | e.g. `channel B` | 3 |
| `channel C` | … | 4 |
| `channel D` | … | 5 |
| `Onboard scale A+B` or `Onboard scale channel A+B` | **channel E** | 6 |
| other | cleaned name | 7 |

Display: strip leading `"Onboard scale"` from the key (except Total / channel E special cases).

### Event classification

- **Net weight:** name includes `'net weight'`
- **Gross weight:** name includes `'gross weight'`
- **Channel diagnostic error:** name includes `"error on channel"`
- **Total scale error:** total channel key + name ends with `" error"` + not `"error on channel"`
- **Cycle end marker:** total channel + `'gross weight'` (not diagnostic error)
- **Cycle net marker:** total channel + `'net weight'` (not diagnostic error)

### Cycle windowing (critical)

Weights are **not** simply “latest reading.” They are derived from a **cycle** bounded by Total (`A+B+C+D`) **gross** events:

1. Sort events by timestamp.
2. Collect unique Total gross end times; if ≥ 3, look back only from the third-to-last gross end.
3. Determine current cycle:
   - **Open cycle:** events after latest Total gross → window from latest gross → now/latest event; may **merge** incomplete channels with previous completed cycle.
   - **Completed cycle:** previous gross → latest gross.
   - Fallback: last 5 minutes if no Total gross markers.
4. Visibility:
   - Prefer channels that have **net** in the current cycle.
   - During open cycle **without** current Total net: show channels with any weight in current cycle **plus** channels that had net in previous completed cycle.
5. Always emit a **Total** row first (via `applyTotalDisplayLogic`), then visible per-channel rows.

### Error display rules

`applyWeightErrorLogic` / `applyTotalDisplayLogic`:

- If `errorTimestamp` (or Total `scaleErrorTimestamp`) is **newer** than latest net/gross timestamps (or no weights), show **Error** for net and gross (`netIsError` / `grossIsError`).
- Format: Error → `"Error"`; null → `"—"`; else `"{value} kg"` (values rounded to 2 decimals).

### Empty / no-data state

If no diagnostics / no channel data:

- Default table with **channel A** and **channel B**, both net/gross `null` (displays as `—`).
- Also shows `"No weight data available"` / `"Loading...."` text states before/during load.

---

## 5. Current UI behavior

1. User focuses Add-in → vehicle dropdown: “Select a vehicle…”
2. On select → Loading text → fetch location + weight → render Net/Gross table + map marker
3. Every 5s → refresh location + weights; update time dashboard (`moment().format('lll')`)
4. Map default view: `[43.436884, -79.711760]` zoom 15 (Niagara / Ontario area)
5. Marker popup: vehicle name + lat/lng
6. Speed dashboard UI exists but is hidden (`display: none`); time remains visible

---

## 6. Important business rules (must preserve)

1. Multi-channel onboard scale: **A, B, C, D, combined A+B (E), Total A+B+C+D**
2. Per-channel **net** and **gross**
3. **Cycle-based** aggregation using Total gross markers (not naive “latest only”)
4. **Merge previous cycle** when a new cycle is in progress and incomplete
5. Channel / scale **error** states with clear “Error” display
6. Empty state with placeholder channels (A/B)
7. Vehicle list from MyGeotab Devices
8. Live map position from `DeviceStatusInfo`
9. Near real-time refresh (~5 seconds)
10. Weight units: **kg**
11. Diagnostic filter: name must include `"Onboard scale"`

---

## 7. What will be preserved

- All channel / Total / net / gross / error / cycle concepts above
- Vehicle selection → weight + map workflow
- Status vocabulary: live / offline / error / no-data (mapped from legacy behaviors)
- Compact embedded Add-in (no full MyGeotab chrome)
- Leaflet / OpenStreetMap for maps
- Service-layer abstraction so MyGeotab `api.call` can replace mocks without UI changes (implemented via `WeightDataProvider`)

Legacy source remains at `legacy/mygeotab-cleral/` (do not delete).

---

## 8. What will be replaced

| Legacy | New |
|--------|-----|
| Monolithic HTML + inline JS | React + TypeScript + Vite in `apps/addin` |
| CDN CSS / Pure | Tailwind CSS + local Add-in UI components (`apps/addin/src/components/ui`) |
| Direct DOM manipulation | Component tree + hooks in `apps/addin/src` |
| Inline business logic | `apps/addin/src/services/weightProcessing` (ported) + local types; MyGeotab host fetch implemented in `apps/addin/src/services/mygeotab/` (live DB validation pending) |
| Hardcoded CDN Leaflet 0.7 | Modern Leaflet / React-Leaflet |
| Single HTML file repo | npm workspaces monorepo with independent `apps/*` (no shared packages) |
| Live MyGeotab in this phase | **Host integration implemented in source**; mock mode for local browser; **live database validation still pending** |

---

## 9. Implications for the modern Add-in MVP

For this phase:

- UI must support **dynamic channels** (not hardcoded Channel A/B components).
- Types should include net/gross, status, timestamps, vehicle location.
- **Legacy weight-processing rules are ported** into `apps/addin/src/services/weightProcessing/` (cycle windowing, channel normalization, error precedence, Total-first ordering).
- **MyGeotab host integration** is implemented under `apps/addin/src/services/mygeotab/` (Device, StatusData, Diagnostic, DeviceStatusInfo, initialize/focus/blur). Browser/local still uses mock raw events when the host API is not initialized.
- Service-layer abstraction (`WeightDataProvider`) keeps UI unchanged across mock vs MyGeotab.

---

## 10. File inventory

```
legacy/mygeotab-cleral/
├── cleral.html   # Add-in entry, all JS logic, HTML structure
└── style.css     # Map overlays, channel table, layout grid
```
