mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-05 19:04:02 +00:00
Merge main into PR #1390
This commit is contained in:
commit
f4b3517e9b
8 files changed
+100
-4
No files matched your search
@@ -0,0 +1,6 @@
|
|||||||
|
- 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
|
||||||
|
00:45 UTC detection read "2026-01-01 45"; it now reads
|
||||||
|
`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).
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
- Show a locally heard aircraft tracking 359.5° or more as TRK 0°. The Local
|
||||||
|
ADS-B card rounded the track without wrapping, so 359.7° read "TRK 360°"
|
||||||
|
(Rudy Celekli, @rudycelekli, #1396).
|
||||||
@@ -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.
|
||||||
@@ -5995,6 +5998,8 @@ callsign, class and category, adsbdb operator/type and a plausible route when
|
|||||||
known, altitude, ground speed, track, vertical rate, position age, message
|
known, altitude, ground speed, track, vertical rate, position age, message
|
||||||
count, "Heard by your receiver"); markers are not camera-followed. Positioned
|
count, "Heard by your receiver"); markers are not camera-followed. Positioned
|
||||||
aircraft join detection boxes, labelled only with a decoded callsign.
|
aircraft join detection boxes, labelled only with a decoded callsign.
|
||||||
|
The card's `TRK` label wraps after rounding to whole degrees, so a track of
|
||||||
|
359.5° displays as `TRK 0°` rather than `TRK 360°`.
|
||||||
|
|
||||||
Receivers publish one record shape from `src/sources/adsbRecords.js`
|
Receivers publish one record shape from `src/sources/adsbRecords.js`
|
||||||
(`icao`, `callsign`, `category`, `onGround`, `lat`, `lon`, `altitudeFt`,
|
(`icao`, `callsign`, `category`, `onGround`, `lat`, `lon`, `altitudeFt`,
|
||||||
|
|||||||
@@ -152,7 +152,7 @@ export function localAdsbCardModel(
|
|||||||
t('ALT {altitude} FT · GS {speed} KT · TRK {track}', {
|
t('ALT {altitude} FT · GS {speed} KT · TRK {track}', {
|
||||||
altitude: grouped(altitude),
|
altitude: grouped(altitude),
|
||||||
speed: speed === null ? DASH : speed,
|
speed: speed === null ? DASH : speed,
|
||||||
track: track === null ? DASH : `${track}°`,
|
track: track === null ? DASH : `${track % 360}°`,
|
||||||
}),
|
}),
|
||||||
vertical === null
|
vertical === null
|
||||||
? t('V/S {rate}', { rate: DASH })
|
? t('V/S {rate}', { rate: DASH })
|
||||||
|
|||||||
@@ -0,0 +1,19 @@
|
|||||||
|
import test from 'node:test';
|
||||||
|
import assert from 'node:assert/strict';
|
||||||
|
import { setLocale } from '../../i18n/index.js';
|
||||||
|
import { localAdsbCardModel } from './card.js';
|
||||||
|
|
||||||
|
test('a track of 359.7° reads TRK 0°, not TRK 360°', () => {
|
||||||
|
setLocale('en');
|
||||||
|
const { details } = localAdsbCardModel(
|
||||||
|
{
|
||||||
|
icao: 'abc123',
|
||||||
|
altitudeFt: 12500,
|
||||||
|
groundSpeedKt: 240,
|
||||||
|
trackDeg: 359.7,
|
||||||
|
lastPositionAt: 1000,
|
||||||
|
},
|
||||||
|
5000,
|
||||||
|
);
|
||||||
|
assert.equal(details[1], 'ALT 12,500 FT · GS 240 KT · TRK 0°');
|
||||||
|
});
|
||||||
@@ -5,6 +5,24 @@ import { defineTool, ToolError } from '../catalog.js';
|
|||||||
import { AREA_SCHEMA, areaContains, resolveArea } from '../area.js';
|
import { AREA_SCHEMA, areaContains, resolveArea } from '../area.js';
|
||||||
import { LIMIT_SCHEMA, capRows, countNoun, isoTime } from '../results.js';
|
import { LIMIT_SCHEMA, capRows, countNoun, isoTime } from '../results.js';
|
||||||
|
|
||||||
|
function acquiredMsUtc(acqDate, acqTime) {
|
||||||
|
if (typeof acqDate !== 'string' || !/^\d{4}-\d{2}-\d{2}$/.test(acqDate))
|
||||||
|
return NaN;
|
||||||
|
const timeText = String(acqTime ?? '').trim();
|
||||||
|
if (!/^\d{1,4}$/.test(timeText)) return NaN;
|
||||||
|
const hhmm = timeText.padStart(4, '0');
|
||||||
|
const hours = Number(hhmm.slice(0, 2));
|
||||||
|
const minutes = Number(hhmm.slice(2, 4));
|
||||||
|
if (hours > 23 || minutes > 59) return NaN;
|
||||||
|
const timestamp = Date.parse(
|
||||||
|
`${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({
|
||||||
name: 'get_earthquakes',
|
name: 'get_earthquakes',
|
||||||
title: 'Recent earthquakes',
|
title: 'Recent earthquakes',
|
||||||
@@ -114,8 +132,7 @@ export const getActiveFires = defineTool({
|
|||||||
lon: fire.lon,
|
lon: fire.lon,
|
||||||
frp_mw: fire.frp ?? null,
|
frp_mw: fire.frp ?? null,
|
||||||
confidence: fire.confidence ?? null,
|
confidence: fire.confidence ?? null,
|
||||||
acquired:
|
acquired: isoTime(acquiredMsUtc(fire.acqDate, fire.acqTime)),
|
||||||
[fire.acqDate, fire.acqTime].filter(Boolean).join(' ') || null,
|
|
||||||
satellite: fire.satellite ?? null,
|
satellite: fire.satellite ?? null,
|
||||||
day_night: fire.daynight ?? null,
|
day_night: fire.daynight ?? null,
|
||||||
})),
|
})),
|
||||||
|
|||||||
@@ -0,0 +1,46 @@
|
|||||||
|
import assert from 'node:assert/strict';
|
||||||
|
import test from 'node:test';
|
||||||
|
import { composeCatalog } from '../catalog.js';
|
||||||
|
import { getActiveFires } from './hazards.js';
|
||||||
|
|
||||||
|
const catalogWith = (fires) =>
|
||||||
|
composeCatalog({
|
||||||
|
tools: [getActiveFires],
|
||||||
|
services: { fires: { getSnapshot: async () => ({ fires }) } },
|
||||||
|
});
|
||||||
|
|
||||||
|
test('a fire acquired at 00:45 UTC reads as 00:45, not "45"', async () => {
|
||||||
|
const result = await catalogWith([
|
||||||
|
{ lat: 24, lon: 121, frp: 5, acqDate: '2026-01-01', acqTime: '45' },
|
||||||
|
]).call('get_active_fires', { area: { bbox: [120, 23, 122, 25] } });
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
});
|
||||||
@@ -125,7 +125,7 @@ test('fires need an area and report a missing key as unavailable', async () => {
|
|||||||
result.data.rows.map((row) => row.frp_mw),
|
result.data.rows.map((row) => row.frp_mw),
|
||||||
[40, 12],
|
[40, 12],
|
||||||
);
|
);
|
||||||
assert.equal(result.data.rows[1].acquired, '2026-01-01 0130');
|
assert.equal(result.data.rows[1].acquired, '2026-01-01T01:30:00.000Z');
|
||||||
|
|
||||||
const keyless = composeCatalog({
|
const keyless = composeCatalog({
|
||||||
tools: coreTools,
|
tools: coreTools,
|
||||||
|
|||||||
Reference in new issue
Block a user