-- migrate:up -- -- One-time cleanup + guard for state.position_history duplicates. -- -- A parked device re-reports the same gpsTime on every 60s poll, so before the -- projector's write-time NOT EXISTS guard (shipped alongside this migration) -- the history hypertable accumulated identical (imei, occurred_at) rows — -- bloating storage and inflating the "fix count" shown in the trip dock. -- -- Step 1 delete duplicates, keeping the lowest history_id per -- (imei, occurred_at). -- Step 2 add a unique index so duplicates can never reappear. It includes -- occurred_at (the hypertable partition column), which TimescaleDB -- requires for a unique index on a hypertable. -- -- NOTE: the DELETE scans the whole hypertable; run it in a quiet window. The -- migration is independent of the projector deploy order — the code's -- NOT EXISTS guard needs no constraint, so applying this before/after a -- redeploy is both safe. DELETE FROM state.position_history a USING state.position_history b WHERE a.imei = b.imei AND a.occurred_at = b.occurred_at AND a.history_id > b.history_id; CREATE UNIQUE INDEX IF NOT EXISTS position_history_imei_occurred_uq ON state.position_history (imei, occurred_at); -- migrate:down DROP INDEX IF EXISTS state.position_history_imei_occurred_uq;