From 182e6ceddd8a9731770cc0cbe9376ea2d671b8c5 Mon Sep 17 00:00:00 2001 From: Aditya <205600203+Rohilalala@users.noreply.github.com> Date: Wed, 28 Oct 2026 15:13:15 +0530 Subject: [PATCH] fix(tools): list a split submarine cable once in find_submarine_cables TeleGeography stores 17 cables (2Africa, Echo, Hawaiki...) as several features under one id, and the tool made a row per feature, so "Echo" read "3 submarine cables" and area counts ran high. Rows are now unique by cable id, and a cable still matches when any of its parts does. --- docs/CURRENT-STATE.md | 3 ++ src/tools/queries/cables.js | 6 ++++ src/tools/queries/cables.test.mjs | 53 +++++++++++++++++++++++++++++++ 3 files changed, 62 insertions(+) diff --git a/docs/CURRENT-STATE.md b/docs/CURRENT-STATE.md index c55031a6a..34a8ee692 100644 --- a/docs/CURRENT-STATE.md +++ b/docs/CURRENT-STATE.md @@ -179,6 +179,9 @@ catalog, loaded on first use. See [tools and the MCP server](TOOLS.md). Aircraft altitude-band queries require a reported barometric or geometric altitude for each match, so a grounded contact that reports none is left out of a band too. Queries without an altitude band retain unknown-altitude contacts. +`find_submarine_cables` lists and counts each cable once: TeleGeography splits +a few cables (2Africa, Echo) into several features under one id, and such a +cable matches when any of its parts crosses the area. Catalog calls check their cancellation signal immediately before executing a tool. Already-cancelled queries do not start; cancellation after execution starts diff --git a/src/tools/queries/cables.js b/src/tools/queries/cables.js index 8d8c0cecf..a4f020150 100644 --- a/src/tools/queries/cables.js +++ b/src/tools/queries/cables.js @@ -57,6 +57,7 @@ export const findSubmarineCables = defineTool({ Number.isFinite(point.lat) && (!area || areaContains(area, point)), ); + const seen = new Set(); const rows = (cables?.features || []) .filter((feature) => { const name = String(feature.properties?.name || ''); @@ -71,6 +72,11 @@ export const findSubmarineCables = defineTool({ id: feature.properties.id ?? null, name: feature.properties.name, })) + // TeleGeography splits some cables into several features under one id. + .filter((row) => { + const key = row.id ?? row.name; + return !seen.has(key) && seen.add(key); + }) .sort((a, b) => a.name.localeCompare(b.name)); const where = area ? ` in ${area.label}` : ''; const what = wanted ? ` matching "${args.name}"` : ''; diff --git a/src/tools/queries/cables.test.mjs b/src/tools/queries/cables.test.mjs index f74457087..37c18fa61 100644 --- a/src/tools/queries/cables.test.mjs +++ b/src/tools/queries/cables.test.mjs @@ -89,6 +89,59 @@ test('cables are found by area and name, with landing points and attribution', a ); }); +test('a cable split into several features is listed once', async () => { + // TeleGeography splits a few cables (2Africa, Echo, Hawaiki) into several + // features under one id. + const cable = (id, name, coordinates) => ({ + properties: { id, name }, + geometry: { type: 'MultiLineString', coordinates: [coordinates] }, + }); + const split = { + async fetch() { + return { + cables: { + features: [ + cable('2africa', '2Africa', [ + [60, 18], + [57, 22], + ]), + cable('equiano', 'Equiano', [ + [-10, 5], + [3, 6], + ]), + cable('2africa', '2Africa', [ + [-21, 16], + [-17, 14], + ]), + ], + }, + landingPoints: { features: [] }, + }; + }, + }; + const catalog = composeCatalog({ + tools: coreTools, + services: { cables: split }, + }); + const named = await catalog.call('find_submarine_cables', { + name: '2africa', + }); + assert.equal(named.summary, '1 submarine cable matching "2africa".'); + assert.deepEqual(named.data.rows, [{ id: '2africa', name: '2Africa' }]); + const both = await catalog.call('find_submarine_cables', { + area: { bbox: [-30, 0, 70, 30] }, + }); + assert.deepEqual( + both.data.rows.map((row) => row.id), + ['2africa', 'equiano'], + ); + // Only the second part crosses this box; the cable is still found. + const west = await catalog.call('find_submarine_cables', { + area: { bbox: [-25, 10, -10, 20] }, + }); + assert.deepEqual(west.data.rows, [{ id: '2africa', name: '2Africa' }]); +}); + test('the local fetch serves bundled data files and nothing outside them', async () => { const fetchImpl = createApiFetch({ apiBase: 'http://127.0.0.1:1',