From 52863a9f422c4120b6533339b770b8ca7995121c Mon Sep 17 00:00:00 2001 From: quanticsoul4772 <71096528+quanticsoul4772@users.noreply.github.com> Date: Fri, 16 Oct 2026 12:06:10 -0700 Subject: [PATCH] fix(tools): count AIS ship type 0 as unknown in vessel breakdowns AIS type 0 means "not available" (ITU-R M.1371), but the source keeps it as '0', so group_by: 'type' listed "0" as a type and understated unknown_count. vessels_in_area rows now report it as null, and a regression fixture mixes type 0 with Cargo and Tanker. Co-Authored-By: Claude Opus 5.5 --- src/tools/queries/maritime.js | 8 ++++++- src/tools/queries/maritime.test.mjs | 35 +++++++++++++++++++++++++++++ 2 files changed, 42 insertions(+), 1 deletion(-) diff --git a/src/tools/queries/maritime.js b/src/tools/queries/maritime.js index a918c1f52..b5bdc4afe 100644 --- a/src/tools/queries/maritime.js +++ b/src/tools/queries/maritime.js @@ -32,6 +32,12 @@ const ALL_VESSELS = 1_000_000; const round = (value, digits) => Number.isFinite(value) ? Number(value.toFixed(digits)) : null; const text = (value) => (typeof value === 'string' && value ? value : null); +// AIS ship type 0 is "not available" (ITU-R M.1371); the source keeps it as +// '0', so it is reported as unknown rather than counted as a type. +const vesselType = (value) => { + const type = text(value); + return type === '0' ? null : type; +}; function vesselRow(record, from) { const point = { lat: record.latitude, lon: record.longitude }; @@ -39,7 +45,7 @@ function vesselRow(record, from) { mmsi: record.id, name: text(record.name), imo: text(record.imo), - type: text(record.type), + type: vesselType(record.type), destination: text(record.destination), lat: round(record.latitude, 5), lon: round(record.longitude, 5), diff --git a/src/tools/queries/maritime.test.mjs b/src/tools/queries/maritime.test.mjs index 662e5bdf2..c5a5ab21f 100644 --- a/src/tools/queries/maritime.test.mjs +++ b/src/tools/queries/maritime.test.mjs @@ -225,6 +225,41 @@ test('an area search names its area to the server; a vessel lookup asks for ever assert.equal(lookup.has('radius_km'), false); }); +test('AIS type 0 (not available) is counted as unknown, not as a type', async () => { + const catalog = composeCatalog({ + tools: coreTools, + services: { + vessels: vessels({ + records: [ + vessel('366000001', 37.78, -122.4, { type: '0' }), + vessel('366000002', 37.79, -122.41, { type: '0' }), + vessel('366000003', 37.77, -122.39, { type: 'Cargo' }), + vessel('366000004', 37.76, -122.38, { type: 'Tanker' }), + ], + }), + }, + }); + const result = await catalog.call('vessels_in_area', { + area: bay, + group_by: 'type', + }); + assert.deepEqual(result.data.breakdown, { + field: 'type', + groups: [ + { value: 'Cargo', count: 1 }, + { value: 'Tanker', count: 1 }, + ], + other_count: 0, + unknown_count: 2, + }); + assert.match(result.summary, /most by type: Cargo \(1\), Tanker \(1\); 2 unknown\./); + assert.doesNotMatch(result.summary, /0 \(/); + assert.equal( + result.data.rows.find((row) => row.mmsi === '366000001').type, + null, + ); +}); + test('vessels in an area can be counted by destination with a speed summary', async () => { const catalog = composeCatalog({ tools: coreTools,