From 33445aec735d278b3336b98e94d05d904b6e950b Mon Sep 17 00:00:00 2001 From: Sameh Khamis Date: Thu, 1 Oct 2026 17:39:48 -0700 Subject: [PATCH] fix: keep each aircraft feed's freshness in search answers find_aircraft merged the records of the feeds that answered and dropped their freshness, so a stale OpenSky snapshot read as a current "not reported". Answers now list each feed with its freshness and coverage and say which feed's data may be stale. --- src/tools/queries/aviation.js | 19 ++++++++++++++- src/tools/queries/aviation.test.mjs | 37 +++++++++++++++++++++++++++++ 2 files changed, 55 insertions(+), 1 deletion(-) diff --git a/src/tools/queries/aviation.js b/src/tools/queries/aviation.js index df904297c..d4b95d3b2 100644 --- a/src/tools/queries/aviation.js +++ b/src/tools/queries/aviation.js @@ -201,6 +201,15 @@ export const findAircraft = defineTool({ ) .filter((record) => !seen.has(record.id) && seen.add(record.id)) .map((record) => aircraftRow(record)); + // Each feed that answered, with its own freshness and coverage. + const answered = feeds.flatMap(({ name }, index) => + settled[index].status === 'fulfilled' + ? [{ feed: name, ...snapshotInfo(settled[index].value) }] + : [], + ); + const stale = answered + .filter((feed) => feed.freshness === 'stale') + .map((feed) => feed.feed); return { summary: (rows.length @@ -208,8 +217,16 @@ export const findAircraft = defineTool({ : `No aircraft with ${key} ${wanted} is currently reported.`) + (unavailable.length ? ` The ${unavailable.join(' and ')} feed did not answer.` + : '') + + (stale.length + ? ` The ${stale.join(' and ')} feed data may be stale.` : ''), - data: { ...capRows(rows, args.limit), unavailable_feeds: unavailable }, + data: { + ...capRows(rows, args.limit), + unavailable_feeds: unavailable, + stale_feeds: stale, + feeds: answered, + }, }; }, }); diff --git a/src/tools/queries/aviation.test.mjs b/src/tools/queries/aviation.test.mjs index 4db854345..ca800f8e9 100644 --- a/src/tools/queries/aviation.test.mjs +++ b/src/tools/queries/aviation.test.mjs @@ -351,3 +351,40 @@ test('a regional fallback feed is named in the answer', async () => { }); assert.match(result.summary, /\(regional feed: 250nm regional fallback\)\.$/); }); + +test('aircraft searches say when a feed that answered is stale', async () => { + const stale = feed([], { freshness: 'stale' }); + const result = await composeCatalog({ + tools: coreTools, + services: { aircraft: stale, military: feed([]) }, + }).call('find_aircraft', { callsign: 'UAL1' }); + assert.equal( + result.summary, + 'No aircraft with callsign UAL1 is currently reported. The civil feed data may be stale.', + ); + assert.deepEqual(result.data.stale_feeds, ['civil']); + assert.deepEqual( + result.data.feeds.map((entry) => [entry.feed, entry.freshness]), + [ + ['civil', 'stale'], + ['military', 'current'], + ], + ); +}); + +test('the real OpenSky source marks an hour-old snapshot stale for searches', async () => { + const { createOpenSkySource } = + await import('../../sources/live/standalone.js'); + const now = Date.UTC(2026, 0, 1, 12); + const opensky = createOpenSkySource({ + now: () => now, + fetchImpl: async () => + Response.json({ time: now / 1000 - 3600, states: [] }), + }); + const result = await composeCatalog({ + tools: coreTools, + services: { aircraft: opensky }, + }).call('find_aircraft', { callsign: 'UAL1' }); + assert.deepEqual(result.data.stale_feeds, ['civil']); + assert.match(result.summary, /may be stale\.$/); +});