Geocoder trips open after N consecutive Nominatim failures and skips ticks
for a cooldown, instead of grinding the whole batch 1 req/sec on every tick.
Adds tests for _coerce_payload (malformed JSON degrades to {_raw}) and
parse_raw tolerating a malformed payload.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Trip days split on reporting gaps rendered as disconnected coloured segments. Add serve.fn_vehicle_day_track (one primary-device LineString for the EAT day), merge it into the trips API as day_track, and draw it as a faint base line beneath the per-trip colours so the route reads as one continuous drive.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Vendor maplibre-gl 4.7.1 (js+css) and serve from /vendor instead of the unpkg CDN — no external dependency/SRI gap for the core map.
- Projector skips duplicate (imei, occurred_at) history rows via NOT EXISTS (parked devices re-report the same gpsTime each poll); migration 23 dedupes existing rows and adds a unique index.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
ruff: drop stale SLF001 noqa, wrap json.load in a context manager (SIM115), remove unused imports + placeholder-less f-strings; ignore PLR0912/PLR0915 for one-off scripts.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Migration 19: serve.fn_vehicle_trips(vehicle_id, date) — PL/pgSQL state
machine that walks state.position_history for one vehicle on one EAT day
and emits the trip breakdown. Rules:
- reporting_time = first ACC_ON of the day
- trip starts at ACC_ON (or first fix if already ACC_ON / moving)
- trip ends:
* ACC_OFF + stationary (<5 km/h) for >=5 min → end_reason 'work_stop'
* fix gap >30 min → end_reason 'long_gap'
* end of day's data → end_reason 'day_end'
- within a trip, ACC_ON + stationary >=5 min is logged as an idling
segment (no trip split — engine still on)
- distance only accumulates when speed >= 5 km/h (excludes GPS jitter)
- falls back to movement-only segmentation when acc_state is null
across the day (has_acc_data=false in the response)
Returns one jsonb document: vehicle, date, reporting_time, day totals
(distance, driving/idling/stopped/unknown minutes), data_quality flags,
trips[] with start/end/duration/distance/idling/end_reason/stops/path
where path is a GeoJSON LineString ready for the map.
New endpoints (read:fleet, rate-limited):
GET /api/views/vehicle/{id}/trips?date=YYYY-MM-DD JSON
GET /api/views/vehicle/{id}/trips.csv?date=YYYY-MM-DD one row per trip
Defaults date to today in EAT (UTC+3) regardless of host TZ.
Migration 18: ops.contract_check_log table — append-only log of probes
against the Tracksolid Pro endpoints we depend on.
New worker app/workers/contract_check.py — per run:
- jimi.oauth.token.get (token refresh succeeds)
- jimi.user.device.location.list per configured target (parse first item
with JimiPollFix)
- jimi.device.location.get with a sample IMEI from the list (parse first
item with JimiPollFix)
Each probe logs success or {error_class, error_detail, sample}; failures
are recorded, not raised.
slo_metrics now also computes contract_drift_days = days since the most-
recent successful probe of the laggard endpoint. With threshold 1d (from
mig 5), a single failed daily run flips the badge red within 24h.
cron entrypoint registers the check daily at 02:00 UTC plus once on
startup, gated on TRACKSOLID_APP_KEY + a configured target.