fix(viewer): polylines + batched T3 load

- loadSortieData: call applyTrailAndCursor() after sortie load so map
  polylines appear when allPoints already populated from datebar
- loadDiveData: split into Phase1 (track only, batched by 4) + Phase2
  (series + sub, batched by 4) — map draws as soon as tracks load
- loadShipSession split into fetchShipTrack + fetchShipSeries helpers
- T3: filter ship sessions by date (sess.start.slice(0,10) === filterDate)
- Pass date param from loadDate to loadDiveData

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Flag
2026-04-28 14:54:16 +00:00
parent f5debc8afc
commit f5788a01f4

View File

@@ -783,7 +783,7 @@ async function loadDate(date) {
for (const { mission, dives } of missionDives) { for (const { mission, dives } of missionDives) {
for (const dive of dives) { for (const dive of dives) {
allFetches.push(loadDiveData(mission.id, dive.id)); allFetches.push(loadDiveData(mission.id, dive.id, date));
totalShip += dive.ship_session_count || 0; totalShip += dive.ship_session_count || 0;
totalSub += dive.sub_session_count || 0; totalSub += dive.sub_session_count || 0;
} }
@@ -848,52 +848,60 @@ async function loadDate(date) {
} }
} }
async function loadDiveData(missionId, diveId) { async function loadDiveData(missionId, diveId, filterDate) {
try { try {
const sResp = await fetch(`${API}/api/dives/${missionId}/${diveId}/sessions`); const sResp = await fetch(`${API}/api/dives/${missionId}/${diveId}/sessions`);
const sessions = await sResp.json(); const sessions = await sResp.json();
const sessionFetches = [];
// Ship sessions // T3 FIX: filter ship sessions by date if provided (session.start = "YYYY-MM-DD_HH-MM-SS")
if (sessions.ship) { const shipSessions = (sessions.ship || []).filter(sess => {
sessions.ship.forEach(sess => { if (!filterDate) return true;
sessionFetches.push(loadShipSession(missionId, diveId, sess.id)); // sess.start: "2026-04-17_07-43-23" → compare first 10 chars "2026-04-17" to filterDate
return !sess.start || sess.start.slice(0, 10) === filterDate;
}); });
}
// Sub sessions // Phase 1: track-only fetches (needed for map polylines) — batch by 4
if (sessions.sub) { const trackFetches = shipSessions.map(sess =>
sessions.sub.forEach(sess => { fetchShipTrack(missionId, diveId, sess.id)
sessionFetches.push(loadSubSession(missionId, diveId, sess.id)); );
}); await _batchedAll(trackFetches, 4);
}
await Promise.all(sessionFetches); // Phase 2: series + sub sessions in background (charts data)
const seriesFetches = [
...shipSessions.map(sess => fetchShipSeries(missionId, diveId, sess.id)),
...(sessions.sub || []).map(sess => loadSubSession(missionId, diveId, sess.id)),
];
await _batchedAll(seriesFetches, 4);
} catch(e) { console.warn('loadDiveData error', diveId, e); } } catch(e) { console.warn('loadDiveData error', diveId, e); }
} }
async function loadShipSession(missionId, diveId, sessionId) { // Run an array of promise-factories in batches of size n
// Parallel fetch: track + series with 20s timeout each — neither blocks the other async function _batchedAll(fns, n) {
const [trackResult, seriesResult] = await Promise.allSettled([ for (let i = 0; i < fns.length; i += n) {
fetch(`${API}/api/ship/${missionId}/${diveId}/${sessionId}/track`, await Promise.all(fns.slice(i, i + n));
{ signal: AbortSignal.timeout(20000) }), }
fetch(`${API}/api/ship/${missionId}/${diveId}/${sessionId}/series`, }
{ signal: AbortSignal.timeout(20000) }),
]); async function fetchShipTrack(missionId, diveId, sessionId) {
if (trackResult.status === 'fulfilled' && trackResult.value.ok) {
try { try {
const d = await trackResult.value.json(); const resp = await fetch(`${API}/api/ship/${missionId}/${diveId}/${sessionId}/track`,
{ signal: AbortSignal.timeout(20000) });
if (!resp.ok) { console.warn('fetchShipTrack fail', sessionId, resp.status); return; }
const d = await resp.json();
const pts = (d.points||[]).map(p => ({ const pts = (d.points||[]).map(p => ({
t_ms: isoToMs(p.t), lat: p.lat, lon: p.lon, t_ms: isoToMs(p.t), lat: p.lat, lon: p.lon,
heading: p.heading || null, source: sessionId heading: p.heading || null, source: sessionId
})); }));
allPoints.push(...pts); allPoints.push(...pts);
} catch(e) { console.warn('loadShipSession track json error', sessionId, e); } } catch(e) { console.warn('fetchShipTrack error', sessionId, e.name); }
} else {
console.warn('loadShipSession track timeout/fail', sessionId,
trackResult.status === 'rejected' ? trackResult.reason.name : trackResult.value?.status);
} }
if (seriesResult.status === 'fulfilled' && seriesResult.value.ok) {
async function fetchShipSeries(missionId, diveId, sessionId) {
try { try {
const d = await seriesResult.value.json(); const resp = await fetch(`${API}/api/ship/${missionId}/${diveId}/${sessionId}/series`,
{ signal: AbortSignal.timeout(20000) });
if (!resp.ok) return;
const d = await resp.json();
const motorKeys = Object.keys(d).filter(k => /^M\d+$/.test(k)); const motorKeys = Object.keys(d).filter(k => /^M\d+$/.test(k));
motorKeys.forEach((k, i) => { motorKeys.forEach((k, i) => {
const pts = d[k]; const pts = d[k];
@@ -901,18 +909,14 @@ async function loadShipSession(missionId, diveId, sessionId) {
pwmUsvTraces.push({ pwmUsvTraces.push({
x: pts.map(p => new Date(isoToMs(p.t))), x: pts.map(p => new Date(isoToMs(p.t))),
y: pts.map(p => p.v), y: pts.map(p => p.v),
name: k, name: k, type: 'scatter', mode: 'lines',
type: 'scatter', mode: 'lines',
line: { color: COLORS[i % COLORS.length], width: 1 }, line: { color: COLORS[i % COLORS.length], width: 1 },
}); });
}); });
} catch(e) { console.warn('loadShipSession series json error', sessionId, e); } } catch(e) { console.warn('fetchShipSeries error', sessionId, e.name); }
} else {
console.warn('loadShipSession series timeout/fail', sessionId,
seriesResult.status === 'rejected' ? seriesResult.reason.name : seriesResult.value?.status);
}
} }
async function loadSubSession(missionId, diveId, sessionId) { async function loadSubSession(missionId, diveId, sessionId) {
// Both usbl_track and series can hang — use parallel with timeout, non-blocking // Both usbl_track and series can hang — use parallel with timeout, non-blocking
const [usblResult, seriesResult] = await Promise.allSettled([ const [usblResult, seriesResult] = await Promise.allSettled([
@@ -1287,6 +1291,8 @@ async function loadSortieData(sortieId) {
prog.textContent = 'Chargement AUV…'; prog.textContent = 'Chargement AUV…';
await loadAuvTabs(sortieId); await loadAuvTabs(sortieId);
prog.textContent = `${sortieId} chargé`; prog.textContent = `${sortieId} chargé`;
// POLYLINE FIX: ensure map trail is drawn if allPoints already loaded from datebar
if (allPoints.length > 0) applyTrailAndCursor();
} catch(e) { } catch(e) {
prog.textContent = `Erreur: ${e.message}`; prog.textContent = `Erreur: ${e.message}`;
} }