FleetAI has no position-history endpoint, so to answer "where was this
rep's vehicle at 10:20 last Tuesday" we have to record it ourselves.
An n8n Schedule Trigger calls POST /api/v1/tracker/poll every 5 minutes
during working hours; a nightly run derives stops and prunes.
poll: */5 6-18 * * 1-5 (the working-hours window lives in the
cron, not in app settings — disabling
the workflow is the off switch)
nightly: 30 2 * * *
One getTokenBaseLiveData call covers the whole fleet per cycle, and only
genuinely new GPS fixes are written: dedupe keys on (vehicle_no,
fixed_at) against the newest fix already held. A parked tracker keeps
re-reporting the same GPSActualTime, so without this a 5-minute cycle
would store the same row a dozen times an hour.
Raw breadcrumbs are pruned at 35 days; derived rep_stops are kept
indefinitely, since a stop is small and is what reports actually ask
about.
Two bugs found by running this against the real stack, neither of which
the unit tests could have caught:
- FakeTrackerAdapter derived its fix time from Date.now(), so every poll
looked like new movement. It could never reproduce a parked vehicle —
the exact case dedupe exists for — and made the poller look correct
while storing a duplicate every cycle. The fake now quantises its fix
time to a bucket, so repeated polls inside one window return an
identical fix.
- PocketBase's filter parser silently matches NOTHING against an
ISO-8601 `T` separator. `fixed_at >= "2026-09-06T00:00:00.000Z"`
returned 0 rows where the space-separated form returned 2, with no
error either way — the rollup would have derived no stops and the
prune deleted no rows, both reporting success, forever. Fixed with a
`pbDate()` helper matching the existing convention in
boss-dashboard-service.ts and order-delivery-note-link-service.ts.
Verified end to end on :4001 against the live PocketBase: three
consecutive polls returned stored=0/duplicates=1, and the nightly rollup
went from vehicles=0 to vehicles=1 once the date format was fixed. Test
rows were removed afterwards.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Groundwork for the rep visit check-in feature: a rep taps "I'm on site",
the vehicle's tracker position surfaces which customers are there, and
the visit is logged against them.
Pure/unit-testable layers only — no PocketBase writes and no UI yet,
both of which are blocked on schema that has to be added in the PB
admin first. Design doc: docs/superpowers/specs/2026-09-05-rep-visit-check-in-design.md
- lib/visit-matching.ts rankNearbyCustomers: radius match around a
point, rep's own customers first
- lib/visit-stops.ts detectStops: breadcrumb trail -> stops.
Anchor-based clustering rather than a running
centroid, which would let a slow crawl chain
into one bogus stationary stop
- lib/tracker-parsing.ts Netstar FleetAI response decoding
- lib/server/tracker-service.ts
FleetAiAdapter + FakeTrackerAdapter behind a
TrackerAdapter seam. The fake is auto-selected
when no credentials are configured, so the rest
of the feature builds and tests without vendor
access.
- lib/geo.ts isRealGeoPoint, lifting the Null-Island guard
that was inlined identically in customers/[id]
and companies/[id]
Three FleetAI conventions drive real code here and are each pinned by a
test: errors arrive as HTTP 200 with a failure envelope; "--" means
"unavailable" and is not null (and for IGN, not false); timestamps are
platform-local UTC+2 with no offset marker, so parsing them naively puts
every fresh fix two hours in the past.
FleetAI has no position-history endpoint, so the design polls
getTokenBaseLiveData (whole fleet in one call) and keeps its own trail.
scripts/fleetai-probe.mjs is a throwaway read-only probe to confirm the
three account-shaped assumptions before more is built on top.
102 tests pass. The 9 pre-existing failing test files elsewhere in the
suite were verified identical on a stashed tree and are unrelated.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>