mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-04 18:34:02 +00:00
Add get_weather, get_regional_brief, get_cyclones, get_fire_perimeters, get_terrain_height, find_military_installations and get_map_features over the application request services and the layers' sources. situation_brief combines weather, earthquakes, fires, aircraft and cyclones for an area by reusing those tools, marking sections that are unavailable instead of failing. get_hud_caption returns the app's heads-up display caption for the same overview.
456 lines
12 KiB
JavaScript
456 lines
12 KiB
JavaScript
import assert from 'node:assert/strict';
|
|
import test from 'node:test';
|
|
import { composeCatalog, coreTools, ToolError } from '../index.js';
|
|
|
|
const conditions = {
|
|
observedAt: '2026-10-01T19:15:00.000Z',
|
|
temperatureC: 26.3,
|
|
apparentTemperatureC: 32,
|
|
precipitationMm: 0,
|
|
cloudCoverPct: 100,
|
|
windKph: 5.4,
|
|
windDirectionDeg: 318,
|
|
visibilityM: 15400,
|
|
weatherCode: 3,
|
|
};
|
|
const weather = {
|
|
calls: [],
|
|
async getConditions(latitude, longitude) {
|
|
this.calls.push([latitude, longitude]);
|
|
return { status: 'ready', weather: conditions };
|
|
},
|
|
};
|
|
const places = {
|
|
resolve: async (name) =>
|
|
name === 'Austin'
|
|
? {
|
|
name: 'Austin, Texas',
|
|
bounds: { west: -97.94, south: 30.1, east: -97.56, north: 30.52 },
|
|
}
|
|
: null,
|
|
};
|
|
const catalogWith = (services) =>
|
|
composeCatalog({ tools: coreTools, services });
|
|
|
|
test('weather describes current conditions at a point or place', async () => {
|
|
const catalog = catalogWith({ weather, places });
|
|
const result = await catalog.call('get_weather', {
|
|
location: { place: 'Austin' },
|
|
});
|
|
assert.equal(
|
|
result.summary,
|
|
'Weather at Austin, Texas: overcast, 26°C, wind 5 km/h.',
|
|
);
|
|
assert.deepEqual(result.data.weather, {
|
|
observed_at: '2026-10-01T19:15:00.000Z',
|
|
conditions: 'overcast',
|
|
weather_code: 3,
|
|
temperature_c: 26.3,
|
|
feels_like_c: 32,
|
|
precipitation_mm: 0,
|
|
cloud_cover_pct: 100,
|
|
wind_kph: 5.4,
|
|
wind_direction_deg: 318,
|
|
visibility_m: 15400,
|
|
});
|
|
const [latitude, longitude] = weather.calls.at(-1);
|
|
assert.ok(Math.abs(latitude - 30.31) < 1e-9 && longitude === -97.75);
|
|
});
|
|
|
|
test('the regional brief returns place, weather and at most ten headlines', async () => {
|
|
const regional = {
|
|
async getBrief() {
|
|
return {
|
|
place: {
|
|
label: 'Edwards Plateau',
|
|
locality: null,
|
|
region: 'Edwards Plateau',
|
|
country: null,
|
|
kind: 'natural',
|
|
},
|
|
weather: conditions,
|
|
newsSource: 'Google News RSS',
|
|
articles: Array.from({ length: 12 }, (_, index) => ({
|
|
title: `Story ${index}`,
|
|
url: `https://news.example/${index}`,
|
|
domain: 'news.example',
|
|
publishedAt: '2026-10-01T00:00:00Z',
|
|
})),
|
|
};
|
|
},
|
|
};
|
|
const result = await catalogWith({ regional }).call('get_regional_brief', {
|
|
location: { lat: 30.27, lon: -97.74 },
|
|
});
|
|
assert.equal(
|
|
result.summary,
|
|
'Edwards Plateau: overcast, 26°C, wind 5 km/h; 10 recent headlines.',
|
|
);
|
|
assert.equal(result.data.articles.length, 10);
|
|
assert.deepEqual(result.data.articles[0], {
|
|
title: 'Story 0',
|
|
url: 'https://news.example/0',
|
|
source: 'news.example',
|
|
published_at: '2026-10-01T00:00:00Z',
|
|
});
|
|
});
|
|
|
|
const storm = (id, name, latitude, longitude, windKt) => ({
|
|
id,
|
|
name,
|
|
classification: 'Hurricane',
|
|
basin: 'Atlantic',
|
|
position: { latitude, longitude },
|
|
positionAt: '2026-10-01T18:00:00.000Z',
|
|
advisoryNumber: '12',
|
|
issuedAt: '2026-10-01T15:00:00.000Z',
|
|
windKt,
|
|
pressureHpa: 980,
|
|
movement: { towardDeg: 300, speedKt: 10 },
|
|
advisoryUrl: 'https://www.nhc.noaa.gov/text/X.shtml',
|
|
});
|
|
|
|
test('cyclones are listed strongest first and can be limited to an area', async () => {
|
|
const cyclones = {
|
|
async getSnapshot() {
|
|
return {
|
|
source: 'NOAA NHC / CPHC',
|
|
coverage: 'Atlantic and eastern/central North Pacific',
|
|
stale: false,
|
|
unavailable: false,
|
|
storms: [
|
|
storm('a', 'Alpha', 25, -70, 65),
|
|
storm('b', 'Beta', 15, -110, 120),
|
|
],
|
|
};
|
|
},
|
|
};
|
|
const all = await catalogWith({ cyclones }).call('get_cyclones', {});
|
|
assert.equal(
|
|
all.summary,
|
|
'2 active tropical cyclones; strongest Beta at 120 kt.',
|
|
);
|
|
assert.deepEqual(
|
|
all.data.storms.map((row) => row.id),
|
|
['b', 'a'],
|
|
);
|
|
const atlantic = await catalogWith({ cyclones }).call('get_cyclones', {
|
|
area: { bbox: [-100, 0, -10, 50] },
|
|
});
|
|
assert.equal(
|
|
atlantic.summary,
|
|
'1 active tropical cyclone in the requested box; strongest Alpha at 65 kt.',
|
|
);
|
|
const down = { getSnapshot: async () => ({ unavailable: true, storms: [] }) };
|
|
await assert.rejects(
|
|
catalogWith({ cyclones: down }).call('get_cyclones', {}),
|
|
(error) => error.code === 'unavailable',
|
|
);
|
|
});
|
|
|
|
test('fire perimeters are placed by their outline and sorted by size', async () => {
|
|
const ring = (lon, lat) => [
|
|
[
|
|
[lon, lat],
|
|
[lon + 0.1, lat],
|
|
[lon + 0.1, lat + 0.1],
|
|
[lon, lat + 0.1],
|
|
],
|
|
];
|
|
const perimeters = {
|
|
async getSnapshot() {
|
|
return [
|
|
{
|
|
stableId: 'small',
|
|
name: 'Small Fire',
|
|
acres: 120.4,
|
|
containedPct: 80,
|
|
state: 'US-CA',
|
|
county: 'Kern',
|
|
category: 'WF',
|
|
cause: 'Human',
|
|
behavior: null,
|
|
personnel: 20,
|
|
discoveredTime: Date.UTC(2026, 8, 1),
|
|
updatedTime: null,
|
|
polygons: [ring(-118.5, 35.2)],
|
|
},
|
|
{
|
|
stableId: 'big',
|
|
name: 'Big Fire',
|
|
acres: 5000,
|
|
containedPct: 10,
|
|
polygons: [ring(-118.8, 35.4)],
|
|
},
|
|
{
|
|
stableId: 'far',
|
|
name: 'Far Fire',
|
|
acres: 9000,
|
|
polygons: [ring(-80, 30)],
|
|
},
|
|
{ stableId: 'empty', name: 'No Outline', acres: 1, polygons: [] },
|
|
];
|
|
},
|
|
};
|
|
const result = await catalogWith({ perimeters }).call('get_fire_perimeters', {
|
|
area: { bbox: [-120, 34, -117, 36] },
|
|
});
|
|
assert.equal(
|
|
result.summary,
|
|
'2 mapped wildfire perimeters in the requested box.',
|
|
);
|
|
assert.deepEqual(
|
|
result.data.rows.map((row) => row.id),
|
|
['big', 'small'],
|
|
);
|
|
assert.deepEqual(result.data.rows[1], {
|
|
id: 'small',
|
|
name: 'Small Fire',
|
|
acres: 120,
|
|
contained_pct: 80,
|
|
state: 'US-CA',
|
|
county: 'Kern',
|
|
category: 'WF',
|
|
cause: 'Human',
|
|
behavior: null,
|
|
personnel: 20,
|
|
discovered: '2026-09-01T00:00:00.000Z',
|
|
updated: null,
|
|
lat: 35.25,
|
|
lon: -118.45,
|
|
});
|
|
});
|
|
|
|
test('terrain heights are returned in request order', async () => {
|
|
const terrain = {
|
|
async getHeights(points) {
|
|
return points.map((point) => ({
|
|
...point,
|
|
elevation: point.lat * 100,
|
|
geoid: -30.04,
|
|
ellipsoid: point.lat * 100 - 30.04,
|
|
}));
|
|
},
|
|
};
|
|
const one = await catalogWith({ terrain }).call('get_terrain_height', {
|
|
points: [{ lat: 27.9881, lon: 86.925 }],
|
|
});
|
|
assert.equal(one.summary, 'Ground elevation is 2798.8 m at 27.9881, 86.925.');
|
|
const many = await catalogWith({ terrain }).call('get_terrain_height', {
|
|
points: [
|
|
{ lat: 1, lon: 1 },
|
|
{ lat: 2, lon: 2 },
|
|
],
|
|
});
|
|
assert.deepEqual(
|
|
many.data.points.map((point) => point.elevation_m),
|
|
[100, 200],
|
|
);
|
|
const short = { getHeights: async () => [] };
|
|
await assert.rejects(
|
|
catalogWith({ terrain: short }).call('get_terrain_height', {
|
|
points: [{ lat: 1, lon: 1 }],
|
|
}),
|
|
(error) => error.code === 'malformed',
|
|
);
|
|
await assert.rejects(
|
|
catalogWith({ terrain }).call('get_terrain_height', {
|
|
points: Array(21).fill({ lat: 0, lon: 0 }),
|
|
}),
|
|
/at most 20 items/,
|
|
);
|
|
});
|
|
|
|
test('military installations need a bounded area and are nearest first', async () => {
|
|
const requested = [];
|
|
const installations = {
|
|
async getMappedSites(box) {
|
|
requested.push(box);
|
|
return {
|
|
source: 'OpenStreetMap',
|
|
records: [
|
|
{
|
|
id: 'osm:1',
|
|
kind: 'installation',
|
|
class: 'base',
|
|
name: 'Naval Base Point Loma',
|
|
latitude: 32.6941,
|
|
longitude: -117.2494,
|
|
},
|
|
{
|
|
id: 'osm:2',
|
|
kind: 'installation',
|
|
class: 'airfield',
|
|
name: 'North Island',
|
|
latitude: 32.7,
|
|
longitude: -117.2,
|
|
},
|
|
],
|
|
};
|
|
},
|
|
};
|
|
const catalog = catalogWith({ installations });
|
|
const result = await catalog.call('find_military_installations', {
|
|
area: { lat: 32.7, lon: -117.2, radius_km: 20 },
|
|
});
|
|
assert.equal(
|
|
result.summary,
|
|
'2 mapped military installations in 20 km around 32.700, -117.200.',
|
|
);
|
|
assert.deepEqual(
|
|
result.data.rows.map((row) => row.id),
|
|
['osm:2', 'osm:1'],
|
|
);
|
|
assert.equal(result.data.source, 'OpenStreetMap');
|
|
assert.ok(requested[0].north - requested[0].south < 1);
|
|
await assert.rejects(
|
|
catalog.call('find_military_installations', {
|
|
area: { bbox: [-130, 20, -100, 50] },
|
|
}),
|
|
/at most 10° on each side/,
|
|
);
|
|
});
|
|
|
|
test('map features name what is at a location and report an unconfigured server', async () => {
|
|
const features = {
|
|
async getAdministrativeAreas() {
|
|
return [
|
|
{
|
|
id: 1,
|
|
category: 'administrative',
|
|
names: { primary: 'Texas' },
|
|
level: 4,
|
|
point: null,
|
|
},
|
|
{
|
|
id: 2,
|
|
category: 'administrative',
|
|
names: { primary: 'Travis County', english: 'Travis County' },
|
|
level: 6,
|
|
point: null,
|
|
},
|
|
{ id: 3, category: 'administrative', names: {}, level: 8, point: null },
|
|
];
|
|
},
|
|
getEnclosingAreas: async () => null,
|
|
getMonuments: async () => ({
|
|
unavailable: true,
|
|
code: 'OVERPASS_NOT_CONFIGURED',
|
|
}),
|
|
};
|
|
const catalog = catalogWith({ features });
|
|
const result = await catalog.call('get_map_features', {
|
|
location: { lat: 30.27, lon: -97.74 },
|
|
kind: 'administrative_areas',
|
|
});
|
|
assert.equal(
|
|
result.summary,
|
|
'2 named features (administrative areas) at 30.2700, -97.7400.',
|
|
);
|
|
assert.deepEqual(
|
|
result.data.rows.map((row) => [row.name, row.admin_level]),
|
|
[
|
|
['Texas', 4],
|
|
['Travis County', 6],
|
|
],
|
|
);
|
|
await assert.rejects(
|
|
catalog.call('get_map_features', {
|
|
location: { lat: 30.27, lon: -97.74 },
|
|
kind: 'monuments',
|
|
}),
|
|
(error) => error.code === 'unavailable' && /Overpass/.test(error.message),
|
|
);
|
|
await assert.rejects(
|
|
catalog.call('get_map_features', {
|
|
location: { lat: 30.27, lon: -97.74 },
|
|
kind: 'named_places',
|
|
}),
|
|
(error) => error.code === 'retry_later',
|
|
);
|
|
});
|
|
|
|
test('the situation brief combines available sections and marks failures', async () => {
|
|
const services = {
|
|
weather,
|
|
earthquakes: {
|
|
getSnapshot: async () => [
|
|
{
|
|
stableId: 'q',
|
|
usgsId: 'us1',
|
|
lat: 30.3,
|
|
lon: -97.7,
|
|
mag: 3.1,
|
|
place: 'near Austin',
|
|
time: 0,
|
|
depthKm: 5,
|
|
},
|
|
],
|
|
},
|
|
fires: { getSnapshot: async () => ({ keyRequired: true }) },
|
|
cyclones: {
|
|
getSnapshot: async () => {
|
|
throw new Error('upstream down');
|
|
},
|
|
},
|
|
places,
|
|
};
|
|
const result = await catalogWith(services).call('situation_brief', {
|
|
area: { place: 'Austin' },
|
|
});
|
|
assert.equal(
|
|
result.summary,
|
|
'Situation in Austin, Texas: Weather at 30.3100, -97.7500: overcast, 26°C, wind 5 km/h. ' +
|
|
'1 earthquake of M2.5+ in the last 24 hours in the requested box; strongest M3.1 near Austin.',
|
|
);
|
|
assert.deepEqual(Object.keys(result.data.sections), [
|
|
'weather',
|
|
'earthquakes',
|
|
'fires',
|
|
'cyclones',
|
|
]);
|
|
assert.deepEqual(result.data.sections.fires, {
|
|
unavailable: true,
|
|
reason: 'Fire detections need a NASA FIRMS key configured for this server',
|
|
});
|
|
assert.deepEqual(result.data.sections.cyclones, {
|
|
unavailable: true,
|
|
reason: 'unavailable right now',
|
|
});
|
|
assert.equal(result.data.sections.aircraft, undefined);
|
|
});
|
|
|
|
test('the HUD caption summarizes the brief through the summary service', async () => {
|
|
const sent = [];
|
|
const summary = {
|
|
async summarize(context) {
|
|
sent.push(context);
|
|
return {
|
|
ok: true,
|
|
status: 200,
|
|
data: { summary: 'Overcast skies over Austin', error: null },
|
|
};
|
|
},
|
|
};
|
|
const result = await catalogWith({ weather, summary, places }).call(
|
|
'get_hud_caption',
|
|
{ area: { place: 'Austin' } },
|
|
);
|
|
assert.equal(result.summary, 'Overcast skies over Austin');
|
|
assert.equal(sent[0].location, 'Austin, Texas');
|
|
assert.match(sent[0].observations[0], /^Weather at /);
|
|
const failing = {
|
|
summarize: async () => ({
|
|
ok: false,
|
|
status: 502,
|
|
data: { summary: null },
|
|
}),
|
|
};
|
|
await assert.rejects(
|
|
catalogWith({ weather, summary: failing, places }).call('get_hud_caption', {
|
|
area: { place: 'Austin' },
|
|
}),
|
|
(error) => error instanceof ToolError && error.code === 'unavailable',
|
|
);
|
|
});
|