Gap-fill: re-sequence the session by ts on save (filled points landed at the front on merged/split tracks with NULL seq)
This commit is contained in:
parent
514ff4edb3
commit
5f58eabcc5
2 changed files with 45 additions and 0 deletions
27
docs/session-logs/2026-09-22_gapfill-order-fix.md
Normal file
27
docs/session-logs/2026-09-22_gapfill-order-fix.md
Normal file
|
|
@ -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): подсадка склеенного трека -> 🧩 -> Сохранить -> переоткрыть: без прямой к
|
||||
началу. Затем сборка + телефон, вытянуть сегодняшний трек.
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue