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 <[email protected]>
This commit is contained in:
quanticsoul4772andClaude Opus 5.5 committed 2026-10-16 12:06:10 -07:00
1 parent acd75efc7c
commit 52863a9f42
2 files changed
+42 -1

No files matched your search

+7 -1
View File
@@ -32,6 +32,12 @@ const ALL_VESSELS = 1_000_000;
const round = (value, digits) => const round = (value, digits) =>
Number.isFinite(value) ? Number(value.toFixed(digits)) : null; Number.isFinite(value) ? Number(value.toFixed(digits)) : null;
const text = (value) => (typeof value === 'string' && value ? value : 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) { function vesselRow(record, from) {
const point = { lat: record.latitude, lon: record.longitude }; const point = { lat: record.latitude, lon: record.longitude };
@@ -39,7 +45,7 @@ function vesselRow(record, from) {
mmsi: record.id, mmsi: record.id,
name: text(record.name), name: text(record.name),
imo: text(record.imo), imo: text(record.imo),
type: text(record.type), type: vesselType(record.type),
destination: text(record.destination), destination: text(record.destination),
lat: round(record.latitude, 5), lat: round(record.latitude, 5),
lon: round(record.longitude, 5), lon: round(record.longitude, 5),
+35
View File
@@ -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); 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 () => { test('vessels in an area can be counted by destination with a speed summary', async () => {
const catalog = composeCatalog({ const catalog = composeCatalog({
tools: coreTools, tools: coreTools,