fix(fires): reject rolled-over acquisition dates and document UTC times

This commit is contained in:
Sameh Khamis committed 2026-10-25 14:08:25 -07:00
1 parent 5ae18032fc
commit 3de745bd33
4 files changed
+40 -2

No files matched your search

+3 -1
View File
@@ -1,4 +1,6 @@
- Report a fire detection's acquisition time as a UTC timestamp in - Report a fire detection's acquisition time as a UTC timestamp in
`get_active_fires`. The tool joined FIRMS's unpadded date and time, so a `get_active_fires`. The tool joined FIRMS's unpadded date and time, so a
00:45 UTC detection read "2026-01-01 45"; it now reads 00:45 UTC detection read "2026-01-01 45"; it now reads
`2026-01-01T00:45:00.000Z` (Rudy Celekli, @rudycelekli, #1395). `2026-01-01T00:45:00.000Z`. Missing or invalid dates and times remain
unknown rather than rolling into another calendar day
(Rudy Celekli, @rudycelekli, #1395).
+3
View File
@@ -1984,6 +1984,9 @@ accessible name remains "Don't show this again". Control behavior is unchanged.
## FIRMS source status ## FIRMS source status
The FIRMS CSV parser rejects impossible acquisition calendar dates before the proxy filters records to the trailing 24 hours, while retaining valid UTC acquisition times. The FIRMS CSV parser rejects impossible acquisition calendar dates before the proxy filters records to the trailing 24 hours, while retaining valid UTC acquisition times.
The `get_active_fires` tool reports acquisition times as UTC ISO timestamps,
including zero-padded hours and minutes; missing or invalid dates and times
remain `null`, including impossible calendar dates from a custom source.
The FIRMS proxy records source success after appending its rows. If aggregation The FIRMS proxy records source success after appending its rows. If aggregation
throws, that source reports failure without a contradictory success entry. throws, that source reports failure without a contradictory success entry.
+5 -1
View File
@@ -14,9 +14,13 @@ function acquiredMsUtc(acqDate, acqTime) {
const hours = Number(hhmm.slice(0, 2)); const hours = Number(hhmm.slice(0, 2));
const minutes = Number(hhmm.slice(2, 4)); const minutes = Number(hhmm.slice(2, 4));
if (hours > 23 || minutes > 59) return NaN; if (hours > 23 || minutes > 59) return NaN;
return Date.parse( const timestamp = Date.parse(
`${acqDate}T${hhmm.slice(0, 2)}:${hhmm.slice(2, 4)}:00.000Z`, `${acqDate}T${hhmm.slice(0, 2)}:${hhmm.slice(2, 4)}:00.000Z`,
); );
return Number.isFinite(timestamp) &&
new Date(timestamp).toISOString().slice(0, 10) === acqDate
? timestamp
: NaN;
} }
export const getEarthquakes = defineTool({ export const getEarthquakes = defineTool({
+29
View File
@@ -15,3 +15,32 @@ test('a fire acquired at 00:45 UTC reads as 00:45, not "45"', async () => {
]).call('get_active_fires', { area: { bbox: [120, 23, 122, 25] } }); ]).call('get_active_fires', { area: { bbox: [120, 23, 122, 25] } });
assert.equal(result.data.rows[0].acquired, '2026-01-01T00:45:00.000Z'); assert.equal(result.data.rows[0].acquired, '2026-01-01T00:45:00.000Z');
}); });
test('fire acquisition timestamps reject impossible calendar dates', async () => {
for (const acqDate of [
'2026-02-29',
'2026-02-30',
'2026-04-31',
'1900-02-29',
'2026-13-01',
'2026-01-00',
]) {
const result = await catalogWith([
{ lat: 24, lon: 121, acqDate, acqTime: '45' },
]).call('get_active_fires', { area: { bbox: [120, 23, 122, 25] } });
assert.equal(result.data.rows[0].acquired, null, acqDate);
}
});
test('fire acquisition timestamps preserve valid leap days and UTC times', async () => {
for (const [acqDate, acqTime, expected] of [
['2024-02-29', '45', '2024-02-29T00:45:00.000Z'],
['2000-02-29', 0, '2000-02-29T00:00:00.000Z'],
['2026-04-30', '2359', '2026-04-30T23:59:00.000Z'],
]) {
const result = await catalogWith([
{ lat: 24, lon: 121, acqDate, acqTime },
]).call('get_active_fires', { area: { bbox: [120, 23, 122, 25] } });
assert.equal(result.data.rows[0].acquired, expected);
}
});