Zhen Ye Claude Opus 4.6 commited on
Commit
ba91677
·
1 Parent(s): fe2ace8

Fix track cards stuck at UNASSESSED due to frontend race condition

Browse files

syncWithBackend (200ms) was overwriting GPT-enriched data that
pollAsyncJob (3s) had just merged. Now syncWithBackend preserves
cached GPT fields, the one-shot _gptEnriched gate is removed so
the poller can re-apply data each cycle, and an ASSESSED badge
fallback covers tracks with zero threat score.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

frontend/js/api/client.js CHANGED
@@ -184,8 +184,7 @@ APP.api.client.pollAsyncJob = async function () {
184
 
185
  // Phase B: One-shot GPT feature merge (gated on gpt_raw + _gptEnriched flag)
186
  const hasGptData = rawDets.some(d => d.gpt_raw);
187
- if (hasGptData && !state.hf._gptEnriched) {
188
- state.hf._gptEnriched = true;
189
  state.hf.firstFrameDetections = rawDets;
190
  for (const rd of rawDets) {
191
  const tid = rd.track_id || `T${String(rawDets.indexOf(rd) + 1).padStart(2, "0")}`;
 
184
 
185
  // Phase B: One-shot GPT feature merge (gated on gpt_raw + _gptEnriched flag)
186
  const hasGptData = rawDets.some(d => d.gpt_raw);
187
+ if (hasGptData) {
 
188
  state.hf.firstFrameDetections = rawDets;
189
  for (const rd of rawDets) {
190
  const tid = rd.track_id || `T${String(rawDets.indexOf(rd) + 1).padStart(2, "0")}`;
frontend/js/core/tracker.js CHANGED
@@ -235,6 +235,46 @@ APP.core.tracker.syncWithBackend = async function (frameIdx) {
235
  };
236
  });
237
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
238
  // Update state
239
  state.tracker.tracks = newTracks;
240
  state.detections = newTracks; // Keep synced
 
235
  };
236
  });
237
 
238
+ // Preserve GPT enrichment data that syncWithBackend would otherwise wipe
239
+ const gptCache = state.hf.firstFrameDetections;
240
+ if (gptCache && gptCache.length > 0) {
241
+ const gptMap = {};
242
+ for (const gd of gptCache) {
243
+ const tid = gd.track_id || `T${String(gptCache.indexOf(gd) + 1).padStart(2, "0")}`;
244
+ if (gd.gpt_raw) gptMap[tid] = gd;
245
+ }
246
+ for (const track of newTracks) {
247
+ const cached = gptMap[track.id];
248
+ if (!cached || track.gpt_raw) continue;
249
+ const g = cached.gpt_raw;
250
+ track.gpt_raw = g;
251
+ track.assessment_status = cached.assessment_status || "ASSESSED";
252
+ track.threat_level_score = cached.threat_level_score || g.threat_level_score || 0;
253
+ track.threat_classification = cached.threat_classification || g.threat_classification || "Unknown";
254
+ track.weapon_readiness = cached.weapon_readiness || g.weapon_readiness || "Unknown";
255
+ track.gpt_distance_m = cached.gpt_distance_m || null;
256
+ track.gpt_direction = cached.gpt_direction || null;
257
+ track.mission_relevant = cached.mission_relevant ?? track.mission_relevant;
258
+ track.relevance_reason = cached.relevance_reason || track.relevance_reason;
259
+ track.features = {
260
+ "Type": g.object_type || "Unknown",
261
+ "Size": g.size || "Unknown",
262
+ "Threat Lvl": (g.threat_level || g.threat_level_score || "?") + "/10",
263
+ "Status": g.threat_classification || "?",
264
+ "Weapons": (g.visible_weapons || []).join(", ") || "None Visible",
265
+ "Readiness": g.weapon_readiness || "Unknown",
266
+ "Motion": g.motion_status || "Unknown",
267
+ "Range": g.range_estimate && g.range_estimate !== "Unknown"
268
+ ? g.range_estimate + " (est.)" : "Unknown",
269
+ "Bearing": g.bearing || "Unknown",
270
+ "Intent": g.tactical_intent || "Unknown",
271
+ };
272
+ for (const feat of (g.dynamic_features || [])) {
273
+ if (feat && feat.key && feat.value) track.features[feat.key] = feat.value;
274
+ }
275
+ }
276
+ }
277
+
278
  // Update state
279
  state.tracker.tracks = newTracks;
280
  state.detections = newTracks; // Keep synced
frontend/js/ui/cards.js CHANGED
@@ -75,6 +75,8 @@ APP.ui.cards.renderFrameTrackList = function () {
75
  statusBadge = '<span class="badgemini" style="background:#ffc107; color:#333">STALE</span>';
76
  } else if (det.threat_level_score > 0) {
77
  statusBadge = `<span class="badgemini" style="background:${det.threat_level_score >= 8 ? '#ff4d4d' : '#ff9f43'}; color:white">T-${det.threat_level_score}</span>`;
 
 
78
  }
79
 
80
  // GPT description (collapsed summary)
 
75
  statusBadge = '<span class="badgemini" style="background:#ffc107; color:#333">STALE</span>';
76
  } else if (det.threat_level_score > 0) {
77
  statusBadge = `<span class="badgemini" style="background:${det.threat_level_score >= 8 ? '#ff4d4d' : '#ff9f43'}; color:white">T-${det.threat_level_score}</span>`;
78
+ } else if (assessStatus === "ASSESSED") {
79
+ statusBadge = '<span class="badgemini" style="background:#17a2b8; color:white">ASSESSED</span>';
80
  }
81
 
82
  // GPT description (collapsed summary)