mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-05 02:44:02 +00:00
fix(tools): average aircraft speed over airborne aircraft only
aircraft_in_area stats summarized altitude over airborne aircraft but speed over every match, so parked aircraft pulled the mean speed toward zero. Both figures now use the same airborne subset, and the on-ground fixture is stationary so the test can tell the difference. Reserved AIS ship-type codes 100 to 255 now read as unknown, as type 0 does, instead of showing up in the breakdown as their own type. Adds tests for an area where every aircraft is on the ground and for find_vessel type and destination normalization, and notes in the changelog fragment that every vessel answer is now normalized. Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
d39256e700
commit
4c383214f7
6 files changed
+112
-19
No files matched your search
@@ -175,6 +175,7 @@ export const aircraftInArea = defineTool({
|
||||
const groups = args.group_by
|
||||
? breakdown(rows, args.group_by, groupValue(args.group_by))
|
||||
: null;
|
||||
const airborne = rows.filter((row) => !row.on_ground);
|
||||
return {
|
||||
summary:
|
||||
`${rows.length} ${kind} in ${area.label}` +
|
||||
@@ -189,12 +190,10 @@ export const aircraftInArea = defineTool({
|
||||
...(groups ? { breakdown: groups } : {}),
|
||||
...(args.stats
|
||||
? {
|
||||
// Parked aircraft would pull both figures toward zero.
|
||||
stats: {
|
||||
altitude_m: numericSummary(
|
||||
rows.filter((row) => !row.on_ground),
|
||||
'altitude_m',
|
||||
),
|
||||
speed_mps: numericSummary(rows, 'speed_mps'),
|
||||
altitude_m: numericSummary(airborne, 'altitude_m'),
|
||||
speed_mps: numericSummary(airborne, 'speed_mps'),
|
||||
},
|
||||
}
|
||||
: {}),
|
||||
|
||||
@@ -395,7 +395,11 @@ test('aircraft in an area can be counted by operator over every match', async ()
|
||||
record('a1', 37.6, -122.3, { operator: 'United' }),
|
||||
record('a2', 37.61, -122.3, { operator: 'Delta' }),
|
||||
record('a3', 37.62, -122.3, { operator: 'United', baroAltitudeM: 3000 }),
|
||||
record('a4', 37.63, -122.3, { onGround: true, baroAltitudeM: 0 }),
|
||||
record('a4', 37.63, -122.3, {
|
||||
onGround: true,
|
||||
baroAltitudeM: 0,
|
||||
speedMps: 0,
|
||||
}),
|
||||
record('a5', 51.5, -0.1, { operator: 'British Airways' }),
|
||||
];
|
||||
const catalog = composeCatalog({
|
||||
@@ -422,15 +426,44 @@ test('aircraft in an area can be counted by operator over every match', async ()
|
||||
other_count: 0,
|
||||
unknown_count: 1,
|
||||
});
|
||||
// Altitude covers airborne aircraft only; speed covers every match.
|
||||
assert.deepEqual(result.data.stats.altitude_m, {
|
||||
count: 3,
|
||||
min: 3000,
|
||||
median: 10000,
|
||||
mean: 7666.7,
|
||||
max: 10000,
|
||||
// Both figures cover airborne aircraft only: the parked a4 stands at 0 m
|
||||
// and 0 m/s and would pull each mean down.
|
||||
assert.deepEqual(result.data.stats, {
|
||||
altitude_m: {
|
||||
count: 3,
|
||||
min: 3000,
|
||||
median: 10000,
|
||||
mean: 7666.7,
|
||||
max: 10000,
|
||||
},
|
||||
speed_mps: { count: 3, min: 230, median: 230, mean: 230, max: 230 },
|
||||
});
|
||||
assert.equal(result.data.stats.speed_mps.count, 4);
|
||||
});
|
||||
|
||||
test('aircraft stats are empty when every match is on the ground', async () => {
|
||||
const catalog = composeCatalog({
|
||||
tools: coreTools,
|
||||
services: {
|
||||
aircraft: feed([
|
||||
record('g1', 37.6, -122.3, {
|
||||
onGround: true,
|
||||
baroAltitudeM: 0,
|
||||
speedMps: 0,
|
||||
}),
|
||||
record('g2', 37.61, -122.3, {
|
||||
onGround: true,
|
||||
baroAltitudeM: 0,
|
||||
speedMps: 4,
|
||||
}),
|
||||
]),
|
||||
},
|
||||
});
|
||||
const result = await catalog.call('aircraft_in_area', {
|
||||
area: bay,
|
||||
stats: true,
|
||||
});
|
||||
assert.match(result.summary, /^2 aircraft in /);
|
||||
assert.deepEqual(result.data.stats, { altitude_m: null, speed_mps: null });
|
||||
});
|
||||
|
||||
test('aircraft answers carry no breakdown or stats unless asked', async () => {
|
||||
|
||||
@@ -297,6 +297,60 @@ test('numeric AIS ship-type codes group and filter by their family', async () =>
|
||||
);
|
||||
});
|
||||
|
||||
test('reserved AIS ship-type codes 100 to 255 count as unknown', async () => {
|
||||
const catalog = composeCatalog({
|
||||
tools: coreTools,
|
||||
services: {
|
||||
vessels: vessels({
|
||||
records: [
|
||||
vessel('366000001', 37.78, -122.4, { type: '100' }),
|
||||
vessel('366000002', 37.79, -122.41, { type: '255' }),
|
||||
vessel('366000003', 37.77, -122.39, { type: '70' }),
|
||||
],
|
||||
}),
|
||||
},
|
||||
});
|
||||
const result = await catalog.call('vessels_in_area', {
|
||||
area: bay,
|
||||
group_by: 'type',
|
||||
});
|
||||
assert.deepEqual(result.data.breakdown.groups, [
|
||||
{ value: 'CARGO', count: 1 },
|
||||
]);
|
||||
assert.equal(result.data.breakdown.unknown_count, 2);
|
||||
assert.doesNotMatch(result.summary, /\b(100|255) \(/);
|
||||
});
|
||||
|
||||
test('find_vessel answers carry normalized types and destinations', async () => {
|
||||
const catalog = composeCatalog({
|
||||
tools: coreTools,
|
||||
services: {
|
||||
vessels: vessels({
|
||||
records: [
|
||||
vessel('366000001', 37.78, -122.4, {
|
||||
type: '71',
|
||||
destination: '---',
|
||||
}),
|
||||
vessel('366000002', 37.79, -122.41, {
|
||||
type: '0',
|
||||
destination: 'NLRTM',
|
||||
}),
|
||||
],
|
||||
}),
|
||||
},
|
||||
});
|
||||
const cargo = await catalog.call('find_vessel', { mmsi: '366000001' });
|
||||
assert.deepEqual(
|
||||
cargo.data.rows.map((row) => [row.type, row.destination]),
|
||||
[['CARGO', null]],
|
||||
);
|
||||
const unknown = await catalog.call('find_vessel', { mmsi: '366000002' });
|
||||
assert.deepEqual(
|
||||
unknown.data.rows.map((row) => [row.type, row.destination]),
|
||||
[[null, 'NLRTM']],
|
||||
);
|
||||
});
|
||||
|
||||
test('a destination with no letters counts as unknown', async () => {
|
||||
const catalog = composeCatalog({
|
||||
tools: coreTools,
|
||||
|
||||
Reference in new issue
Block a user