From 5f58eabcc5ae54fed5f82ab5ac0b0727a4056bb5 Mon Sep 17 00:00:00 2001 From: 4aiman Date: Tue, 22 Sep 2026 20:58:06 +0300 Subject: [PATCH] Gap-fill: re-sequence the session by ts on save (filled points landed at the front on merged/split tracks with NULL seq) --- .../2026-09-22_gapfill-order-fix.md | 27 +++++++++++++++++++ frontend/js/db.js | 18 +++++++++++++ 2 files changed, 45 insertions(+) create mode 100644 docs/session-logs/2026-09-22_gapfill-order-fix.md diff --git a/docs/session-logs/2026-09-22_gapfill-order-fix.md b/docs/session-logs/2026-09-22_gapfill-order-fix.md new file mode 100644 index 0000000..7c9f6aa --- /dev/null +++ b/docs/session-logs/2026-09-22_gapfill-order-fix.md @@ -0,0 +1,27 @@ +# Сессия 2026-09-22 (2) - достройка пропусков уходила «в начало» + +По утверждённому плану `~/.claude/plans/merry-discovering-dream.md`. + +## Симптом +Достройка GPS-пропусков (🧩) показывала превью нормально, но сохранённый трек тянул прямую +линию от старта к месту пропуска («возврат в начало»). На любом (недавнем) треке. + +## Корень +`saveGapFill` (db.js) ставит достроенным точкам `seq = lastSeq` (seq последней РЕАЛЬНОЙ точки +перед пропуском). У склеенных/разрезанных треков реальные точки имеют `seq = NULL` (склейка/ +разрез специально обнуляют seq -> порядок по ts). Тогда `lastSeq` остаётся 0, все достроенные +точки получают seq=0, а `getGpsPoints` (`ORDER BY COALESCE(seq,999999), ts`) ставит их ПЕРЕД +реальными (NULL→999999) -> прямая к началу. Подтверждено: единственный NULL-seq трек в БД - +склеенный 16.09; недавние треки пользователя склеены. + +## Фикс +`saveGapFill`: после вставки достроенных точек - пере-нумерация seq ВСЕЙ сессии по ts (читаю +id `ORDER BY ts,id`, seq=порядковый номер, пакетный `CASE id WHEN..` чанками по 300, паттерн +из `saveSmoothedTrack`). Теперь getGpsPoints всегда в порядке ts независимо от исходного seq +(плотный/NULL/смешанный). Для плотных треков - no-op, для склеенных - чинит. + +## Проверка +- SQL: у склеенного 16.09 реальные точки seq=NULL (seq=0 достроенной встала бы перед ними) - + корень подтверждён. +- Десктоп (Xvfb): подсадка склеенного трека -> 🧩 -> Сохранить -> переоткрыть: без прямой к + началу. Затем сборка + телефон, вытянуть сегодняшний трек. diff --git a/frontend/js/db.js b/frontend/js/db.js index 00e9198..babdf09 100644 --- a/frontend/js/db.js +++ b/frontend/js/db.js @@ -810,6 +810,24 @@ async function saveGapFill(sessionId, points) { } await execute(`INSERT INTO gps_points (session_id, ts, lat, lon, seq, estimated) VALUES ${rowSql.join(",")}`, vals); } + // Re-sequence the WHOLE session by ts so getGpsPoints (ORDER BY seq,ts) returns the true + // order. Without this, on merged/split tracks the real points have seq=NULL, the inserted + // gap-fill points get seq=0, and they sort to the FRONT — a straight line from the start to + // the gap ("returns to the beginning"). Re-numbering by ts is a no-op for dense tracks and + // fixes NULL/mixed ones. Batched CASE update (like saveSmoothedTrack) to beat the IPC/bind cost. + let ordered = await select("SELECT id FROM gps_points WHERE session_id=?1 ORDER BY ts ASC, id ASC", [sessionId]); + let SEQCHUNK = 300; // 2 params/row (id, seq) → under SQLite's 999 bind-var limit + for (let i = 0; i < ordered.length; i += SEQCHUNK) { + let chunk = ordered.slice(i, i + SEQCHUNK); + let when = [], ids = [], params = []; + for (let k = 0; k < chunk.length; k++) { + params.push(chunk[k].id); let idi = params.length; + params.push(i + k + 1); let sqi = params.length; + when.push(`WHEN ?${idi} THEN ?${sqi}`); + ids.push(`?${idi}`); + } + await execute(`UPDATE gps_points SET seq=CASE id ${when.join(" ")} END WHERE id IN (${ids.join(",")})`, params); + } await execute("UPDATE gps_sessions SET gap_filled=1 WHERE id=?1", [sessionId]); return toInsert.length; }