wiki: confirm SPA implements IMEI join per WS contract

Adds an explicit SPA-side join section in position-record.md and
references the implementation file (src/data/devices.ts ->
useDevicesByImei) from the deviceId field row in the WS contract page.
Captures the brief drift-and-realign episode where the SPA was keying
by uuid before Phase 3.4 and the device list panel rendered id
prefixes.

The contract itself didn't change - the SPA implementation just caught
up with what the wiki already said.
This commit is contained in:
2026-05-04 17:57:16 +02:00
parent c6b8507ca6
commit 111a542034
2 changed files with 17 additions and 4 deletions
+15 -2
View File
@@ -2,7 +2,7 @@
title: Position Record
type: concept
created: 2026-04-30
updated: 2026-05-03
updated: 2026-05-04
sources: [gps-tracking-architecture, teltonika-ingestion-architecture, teltonika-data-sending-protocols]
tags: [data-model, boundary-contract]
---
@@ -94,4 +94,17 @@ Why store the IMEI rather than the uuid:
- [[tcp-ingestion]] writes positions without any business-plane round-trip ([[plane-separation]]), so the only identifier it has at write time is the IMEI.
- Devices can move between `entry_devices` rows across events; positions are an immutable per-IMEI record.
If/when a Phase 2 redesign moves to uuid-keyed positions, this is the section to revise — and `processor` `src/live/snapshot.ts` + `src/live/device-event-map.ts` both lose the translation hop.
If/when a Phase 2 redesign moves to uuid-keyed positions, this is the section to revise — and `processor` `src/live/snapshot.ts` + `src/live/device-event-map.ts` both lose the translation hop, alongside `react-spa`'s `src/data/devices.ts` (`useDevicesByImei` becomes `useDevicesById`).
### Consumer-side join (SPA)
The same translation hop applies in [[react-spa]] when the live channel's `position.deviceId` (IMEI) needs to resolve to a Device row's `model` / `serial_number` / vehicle / crew metadata. Implementation:
```ts
// src/data/devices.ts
export function useDevicesByImei() {
// readItems('devices') -> Map<imei, Device>
}
```
Used by `<MapPositions>`, `<MapTrails>`, and `<DeviceListPanel>`. Briefly drifted to keying by `devices.id` (uuid) between Phase 2 and Phase 3.2 — surfaced as device labels rendering 8-char id prefixes ("35042406") instead of model + IMEI tail ("FMB920 #3619"). Re-aligned with this contract on 2026-05-04 before Task 3.4's per-device detail panel started consuming the join.
+2 -2
View File
@@ -2,7 +2,7 @@
title: Processor WebSocket contract
type: synthesis
created: 2026-05-02
updated: 2026-05-03
updated: 2026-05-04
sources: [gps-tracking-architecture, traccar-maps-architecture]
tags: [websocket, protocol, contract, telemetry-plane, decision]
---
@@ -161,7 +161,7 @@ Field semantics:
|---|---|---|---|
| `type` | `"position"` | yes | Discriminator. |
| `topic` | string | yes | Echoes the subscription. Allows multiplexing on one connection. |
| `deviceId` | string | yes | **Phase 1: the IMEI** (vendor identifier, e.g. `"350424064163619"`) — same value as `Position.device_id` per [[position-record]]. Originally specified as `devices.id` (uuid) here; the implementation diverged because [[tcp-ingestion]] only knows the IMEI at write time and the live channel ships the same identifier through end-to-end. SPA joins `deviceId``devices.imei` to look up entry/vehicle/crew via TanStack Query against [[directus]]. Closing this divergence (uuid on the wire) is a Phase 2 question; not blocking dogfood. |
| `deviceId` | string | yes | **Phase 1: the IMEI** (vendor identifier, e.g. `"350424064163619"`) — same value as `Position.device_id` per [[position-record]]. Originally specified as `devices.id` (uuid) here; the implementation diverged because [[tcp-ingestion]] only knows the IMEI at write time and the live channel ships the same identifier through end-to-end. SPA joins `deviceId``devices.imei` to look up entry/vehicle/crew via TanStack Query against [[directus]]. Consumer-side implementation lives in [[react-spa]] at `src/data/devices.ts` (`useDevicesByImei()` — builds `Map<imei, Device>`); used by `<MapPositions>`, `<MapTrails>`, and `<DeviceListPanel>` for label / metadata lookup. Closing this divergence (uuid on the wire) is a Phase 2 question; not blocking dogfood. |
| `lat` / `lon` | number (degrees, WGS84) | yes | GPS coordinates. **Coordinate order in JSON is `lat`/`lon`** (not `[lon,lat]` GeoJSON ordering — that conversion happens in the SPA). |
| `ts` | number (epoch milliseconds, UTC) | yes | Authoritative timestamp from the device's GPS fix. **Always use this, never `Date.now()` on the client.** |
| `speed` | number (km/h) | optional | Omitted if device reports speed=0 with invalid GPS fix (per [[teltonika]] convention). |