From 62df42f892328debb1592b5b247f46af55d648da Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Wed, 29 Jul 2026 17:01:39 +0000 Subject: [PATCH 1/5] Add example UDL federation plugin (ephemeris + conjunction assessments) Co-Authored-By: Jake Cosme --- .../udlFederation/ConjunctionLimitProvider.js | 87 ++++++++++++ example/udlFederation/UDLObjectProvider.js | 131 ++++++++++++++++++ example/udlFederation/UDLTelemetryProvider.js | 124 +++++++++++++++++ example/udlFederation/plugin.js | 72 ++++++++++ example/udlFederation/satellites.js | 59 ++++++++ index.html | 1 + src/plugins/plugins.js | 2 + 7 files changed, 476 insertions(+) create mode 100644 example/udlFederation/ConjunctionLimitProvider.js create mode 100644 example/udlFederation/UDLObjectProvider.js create mode 100644 example/udlFederation/UDLTelemetryProvider.js create mode 100644 example/udlFederation/plugin.js create mode 100644 example/udlFederation/satellites.js diff --git a/example/udlFederation/ConjunctionLimitProvider.js b/example/udlFederation/ConjunctionLimitProvider.js new file mode 100644 index 0000000000..241246e30e --- /dev/null +++ b/example/udlFederation/ConjunctionLimitProvider.js @@ -0,0 +1,87 @@ +/***************************************************************************** + * Open MCT, Copyright (c) 2014-2024, United States Government + * as represented by the Administrator of the National Aeronautics and Space + * Administration. All rights reserved. + * + * Open MCT is licensed under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * Open MCT includes source code licensed under additional open source + * licenses. See the Open Source Licenses file (LICENSES.md) included with + * this source code distribution or the Licensing information page available + * at runtime from the About dialog for additional information. + *****************************************************************************/ + +// Screening thresholds commonly used for conjunction assessment triage. +const PC_CRITICAL = 1e-4; +const PC_WARNING = 1e-5; + +const LIMITS = { + critical: { + cssClass: 'is-limit--upr is-limit--red', + low: PC_CRITICAL, + high: Number.POSITIVE_INFINITY, + name: 'Pc Critical' + }, + warning: { + cssClass: 'is-limit--upr is-limit--yellow', + low: PC_WARNING, + high: PC_CRITICAL, + name: 'Pc Warning' + } +}; + +export default class ConjunctionLimitProvider { + supportsLimits(domainObject) { + return domainObject.type === 'udl.conjunctions'; + } + + getLimitEvaluator() { + return { + evaluate: function (datum, valueMetadata) { + if (!valueMetadata || valueMetadata.key !== 'pc') { + return undefined; + } + + if (datum.pc >= PC_CRITICAL) { + return LIMITS.critical; + } + + if (datum.pc >= PC_WARNING) { + return LIMITS.warning; + } + + return undefined; + } + }; + } + + getLimits() { + return { + limits: function () { + return Promise.resolve({ + WARNING: { + high: { + color: 'yellow', + pc: PC_WARNING + } + }, + CRITICAL: { + high: { + color: 'red', + pc: PC_CRITICAL + } + } + }); + } + }; + } +} diff --git a/example/udlFederation/UDLObjectProvider.js b/example/udlFederation/UDLObjectProvider.js new file mode 100644 index 0000000000..d1db456af5 --- /dev/null +++ b/example/udlFederation/UDLObjectProvider.js @@ -0,0 +1,131 @@ +/***************************************************************************** + * Open MCT, Copyright (c) 2014-2024, United States Government + * as represented by the Administrator of the National Aeronautics and Space + * Administration. All rights reserved. + * + * Open MCT is licensed under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * Open MCT includes source code licensed under additional open source + * licenses. See the Open Source Licenses file (LICENSES.md) included with + * this source code distribution or the Licensing information page available + * at runtime from the About dialog for additional information. + *****************************************************************************/ + +import { SATELLITES } from './satellites.js'; + +const EPHEMERIS_VALUES = [ + { + key: 'utc', + source: 'utc', + name: 'Timestamp', + format: 'utc', + hints: { domain: 1 } + }, + { + key: 'altitude', + name: 'Altitude', + unit: 'km', + format: 'float', + hints: { range: 1 } + }, + { + key: 'latitude', + name: 'Latitude', + unit: 'deg', + format: 'float', + hints: { range: 2 } + }, + { + key: 'longitude', + name: 'Longitude', + unit: 'deg', + format: 'float', + hints: { range: 3 } + }, + { + key: 'velocity', + name: 'Velocity', + unit: 'km/s', + format: 'float', + hints: { range: 4 } + } +]; + +const CONJUNCTION_VALUES = [ + { + key: 'utc', + source: 'utc', + name: 'Timestamp', + format: 'utc', + hints: { domain: 1 } + }, + { + key: 'pc', + name: 'Probability of Collision', + format: 'float', + hints: { range: 1 } + }, + { + key: 'missDistance', + name: 'Miss Distance', + unit: 'km', + format: 'float', + hints: { range: 2 } + }, + { + key: 'secondary', + name: 'Secondary Object', + format: 'string', + hints: { range: 3 } + } +]; + +export default class UDLObjectProvider { + get(identifier) { + if (identifier.key === 'node') { + return Promise.resolve({ + identifier, + name: 'UDL Federation Node', + type: 'folder', + location: 'ROOT' + }); + } + + if (identifier.key === 'conjunctions') { + return Promise.resolve({ + identifier, + name: 'Conjunction Assessments', + type: 'udl.conjunctions', + location: 'udl:node', + telemetry: { + values: CONJUNCTION_VALUES + } + }); + } + + const satellite = SATELLITES.find((candidate) => candidate.key === identifier.key); + if (satellite === undefined) { + return Promise.reject(new Error(`Unknown UDL object: ${identifier.key}`)); + } + + return Promise.resolve({ + identifier, + name: satellite.name, + type: 'udl.ephemeris', + location: 'udl:node', + noradId: satellite.noradId, + telemetry: { + values: EPHEMERIS_VALUES + } + }); + } +} diff --git a/example/udlFederation/UDLTelemetryProvider.js b/example/udlFederation/UDLTelemetryProvider.js new file mode 100644 index 0000000000..a0b9693332 --- /dev/null +++ b/example/udlFederation/UDLTelemetryProvider.js @@ -0,0 +1,124 @@ +/***************************************************************************** + * Open MCT, Copyright (c) 2014-2024, United States Government + * as represented by the Administrator of the National Aeronautics and Space + * Administration. All rights reserved. + * + * Open MCT is licensed under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * Open MCT includes source code licensed under additional open source + * licenses. See the Open Source Licenses file (LICENSES.md) included with + * this source code distribution or the Licensing information page available + * at runtime from the About dialog for additional information. + *****************************************************************************/ + +import { EARTH_RADIUS_KM, MU_EARTH, SATELLITES } from './satellites.js'; + +const EPHEMERIS_PERIOD_MS = 1000; +const CONJUNCTION_PERIOD_MS = 5000; +const SECONDARY_OBJECTS = [ + 'COSMOS 2251 DEB', + 'FENGYUN 1C DEB', + 'SL-16 R/B', + 'STARLINK-30452', + 'IRIDIUM 33 DEB' +]; + +function ephemerisDatum(satellite, timestamp) { + const radiusKm = EARTH_RADIUS_KM + satellite.altitudeKm; + const periodSeconds = 2 * Math.PI * Math.sqrt(Math.pow(radiusKm, 3) / MU_EARTH); + const meanMotion = (2 * Math.PI) / periodSeconds; + const theta = meanMotion * (timestamp / 1000) + (satellite.phaseDeg * Math.PI) / 180; + const inclination = (satellite.inclinationDeg * Math.PI) / 180; + + const latitude = (Math.asin(Math.sin(inclination) * Math.sin(theta)) * 180) / Math.PI; + const earthRotationDeg = ((timestamp / 1000) * 360) / 86164; + const longitude = + (((((Math.atan2(Math.cos(inclination) * Math.sin(theta), Math.cos(theta)) * 180) / Math.PI + + satellite.raanDeg - + earthRotationDeg) % + 360) + + 540) % + 360) - + 180; + + return { + id: satellite.key, + utc: timestamp, + altitude: satellite.altitudeKm + Math.sin(theta * 3) * 2, + latitude, + longitude, + velocity: Math.sqrt(MU_EARTH / radiusKm) + }; +} + +function conjunctionDatum(timestamp) { + const slot = Math.floor(timestamp / CONJUNCTION_PERIOD_MS); + const wave = Math.abs(Math.sin(slot / 7)); + const pc = wave > 0.92 ? 0.0002 + wave / 2000 : wave / 50000; + + return { + id: 'conjunctions', + utc: timestamp, + pc: Number(pc.toPrecision(3)), + missDistance: Number((0.4 + (1 - wave) * 24).toFixed(2)), + secondary: SECONDARY_OBJECTS[slot % SECONDARY_OBJECTS.length] + }; +} + +export default class UDLTelemetryProvider { + supportsRequest(domainObject) { + return this.#isUDLTelemetry(domainObject); + } + + supportsSubscribe(domainObject) { + return this.#isUDLTelemetry(domainObject); + } + + request(domainObject, options) { + const period = + domainObject.type === 'udl.conjunctions' ? CONJUNCTION_PERIOD_MS : EPHEMERIS_PERIOD_MS; + const start = Math.floor(options.start / period) * period; + const data = []; + + for (let timestamp = start; timestamp <= options.end; timestamp += period) { + data.push(this.#datumFor(domainObject, timestamp)); + } + + return Promise.resolve(data); + } + + subscribe(domainObject, callback) { + const period = + domainObject.type === 'udl.conjunctions' ? CONJUNCTION_PERIOD_MS : EPHEMERIS_PERIOD_MS; + const interval = setInterval(() => { + callback(this.#datumFor(domainObject, Date.now())); + }, period); + + return function unsubscribe() { + clearInterval(interval); + }; + } + + #datumFor(domainObject, timestamp) { + if (domainObject.type === 'udl.conjunctions') { + return conjunctionDatum(timestamp); + } + + const satellite = SATELLITES.find((candidate) => candidate.key === domainObject.identifier.key); + + return ephemerisDatum(satellite, timestamp); + } + + #isUDLTelemetry(domainObject) { + return domainObject.type === 'udl.ephemeris' || domainObject.type === 'udl.conjunctions'; + } +} diff --git a/example/udlFederation/plugin.js b/example/udlFederation/plugin.js new file mode 100644 index 0000000000..b18a726419 --- /dev/null +++ b/example/udlFederation/plugin.js @@ -0,0 +1,72 @@ +/***************************************************************************** + * Open MCT, Copyright (c) 2014-2024, United States Government + * as represented by the Administrator of the National Aeronautics and Space + * Administration. All rights reserved. + * + * Open MCT is licensed under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * Open MCT includes source code licensed under additional open source + * licenses. See the Open Source Licenses file (LICENSES.md) included with + * this source code distribution or the Licensing information page available + * at runtime from the About dialog for additional information. + *****************************************************************************/ + +import ConjunctionLimitProvider from './ConjunctionLimitProvider.js'; +import { SATELLITES } from './satellites.js'; +import UDLObjectProvider from './UDLObjectProvider.js'; +import UDLTelemetryProvider from './UDLTelemetryProvider.js'; + +/** + * Simulated Unified Data Library (UDL) federation node. Exposes a + * constellation's ephemeris streams and a conjunction-assessment feed the way + * a UDL data-integration node would federate them into a mission-ops tool. + */ +export default function UDLFederationPlugin() { + return function install(openmct) { + openmct.types.addType('udl.ephemeris', { + name: 'UDL Ephemeris', + description: 'Satellite state vectors federated from a Unified Data Library node.', + cssClass: 'icon-telemetry' + }); + + openmct.types.addType('udl.conjunctions', { + name: 'UDL Conjunction Assessments', + description: + 'Conjunction data messages federated from a Unified Data Library node, with probability-of-collision screening limits.', + cssClass: 'icon-alert-triangle' + }); + + openmct.objects.addRoot({ + namespace: 'udl', + key: 'node' + }); + + openmct.objects.addProvider('udl', new UDLObjectProvider()); + + openmct.composition.addProvider({ + appliesTo: function (domainObject) { + return domainObject.identifier.namespace === 'udl' && domainObject.type === 'folder'; + }, + load: function () { + return Promise.resolve( + SATELLITES.map((satellite) => ({ + namespace: 'udl', + key: satellite.key + })).concat([{ namespace: 'udl', key: 'conjunctions' }]) + ); + } + }); + + openmct.telemetry.addProvider(new UDLTelemetryProvider()); + openmct.telemetry.addProvider(new ConjunctionLimitProvider()); + }; +} diff --git a/example/udlFederation/satellites.js b/example/udlFederation/satellites.js new file mode 100644 index 0000000000..024cf5c3cd --- /dev/null +++ b/example/udlFederation/satellites.js @@ -0,0 +1,59 @@ +/***************************************************************************** + * Open MCT, Copyright (c) 2014-2024, United States Government + * as represented by the Administrator of the National Aeronautics and Space + * Administration. All rights reserved. + * + * Open MCT is licensed under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * Open MCT includes source code licensed under additional open source + * licenses. See the Open Source Licenses file (LICENSES.md) included with + * this source code distribution or the Licensing information page available + * at runtime from the About dialog for additional information. + *****************************************************************************/ + +/** + * Simulated constellation for the UDL federation node. Orbital parameters are + * simplified two-body circular orbits, sufficient for a representative + * ephemeris stream (UDL `elset`/`statevector` analog). + */ +export const SATELLITES = [ + { + key: 'gps-iii-sv05', + name: 'GPS III SV05 (NAVSTAR 82)', + noradId: 48859, + altitudeKm: 20180, + inclinationDeg: 55, + raanDeg: 40, + phaseDeg: 0 + }, + { + key: 'wgs-11', + name: 'WGS-11', + noradId: 54800, + altitudeKm: 35786, + inclinationDeg: 0.02, + raanDeg: 0, + phaseDeg: 130 + }, + { + key: 'sbirs-geo-6', + name: 'SBIRS GEO-6 (USA 336)', + noradId: 53355, + altitudeKm: 35786, + inclinationDeg: 1.4, + raanDeg: 0, + phaseDeg: 245 + } +]; + +export const EARTH_RADIUS_KM = 6371; +export const MU_EARTH = 398600.4418; // km^3/s^2 diff --git a/index.html b/index.html index 42417f0022..001972ddc8 100644 --- a/index.html +++ b/index.html @@ -105,6 +105,7 @@ openmct.install(openmct.plugins.example.EventGeneratorPlugin()); openmct.install(openmct.plugins.example.ExampleImagery()); openmct.install(openmct.plugins.example.ExampleTags()); + openmct.install(openmct.plugins.example.UDLFederation()); openmct.install(openmct.plugins.Espresso()); openmct.install(openmct.plugins.MyItems()); diff --git a/src/plugins/plugins.js b/src/plugins/plugins.js index 938bc51c09..62f383d41f 100644 --- a/src/plugins/plugins.js +++ b/src/plugins/plugins.js @@ -28,6 +28,7 @@ import ExampleUser from '../../example/exampleUser/plugin.js'; import ExampleFaultSource from '../../example/faultManagement/exampleFaultSource.js'; import GeneratorPlugin from '../../example/generator/plugin.js'; import ExampleImagery from '../../example/imagery/plugin.js'; +import UDLFederationPlugin from '../../example/udlFederation/plugin.js'; import AutoflowPlugin from './autoflow/AutoflowTabularPlugin.js'; import BarChartPlugin from './charts/bar/plugin.js'; import ScatterPlotPlugin from './charts/scatter/plugin.js'; @@ -105,6 +106,7 @@ plugins.example.ExampleDataVisualizationSourcePlugin = ExampleDataVisualizationS plugins.example.ExampleTags = ExampleTags; plugins.example.Generator = () => GeneratorPlugin; plugins.example.ExampleStaleness = ExampleStaleness; +plugins.example.UDLFederation = UDLFederationPlugin; plugins.UTCTimeSystem = UTCTimeSystem; plugins.LocalTimeSystem = LocalTimeSystem; From 29cb665981d73c20aa7e4f5bbb27b8814d5693a6 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Wed, 29 Jul 2026 17:09:46 +0000 Subject: [PATCH 2/5] Address review feedback: cspell words, formatString metadata, bounded request size Co-Authored-By: Jake Cosme --- .cspell.json | 10 +++- example/udlFederation/UDLObjectProvider.js | 14 +++--- example/udlFederation/UDLTelemetryProvider.js | 11 ++++- test-plan-udl.md | 25 ++++++++++ test-report.md | 48 +++++++++++++++++++ 5 files changed, 97 insertions(+), 11 deletions(-) create mode 100644 test-plan-udl.md create mode 100644 test-report.md diff --git a/.cspell.json b/.cspell.json index f8710f7fc7..8b99ef2db1 100644 --- a/.cspell.json +++ b/.cspell.json @@ -489,7 +489,15 @@ "requestfinished", "LOCF", "Unack", - "Tabnabbing" + "Tabnabbing", + "elset", + "statevector", + "NAVSTAR", + "raan", + "sbirs", + "SBIRS", + "FENGYUN", + "STARLINK" ], "dictionaries": ["npm", "softwareTerms", "node", "html", "css", "bash", "en_US", "en-gb", "misc"], "ignorePaths": [ diff --git a/example/udlFederation/UDLObjectProvider.js b/example/udlFederation/UDLObjectProvider.js index d1db456af5..ac408f12fa 100644 --- a/example/udlFederation/UDLObjectProvider.js +++ b/example/udlFederation/UDLObjectProvider.js @@ -34,28 +34,28 @@ const EPHEMERIS_VALUES = [ key: 'altitude', name: 'Altitude', unit: 'km', - format: 'float', + formatString: '%0.2f', hints: { range: 1 } }, { key: 'latitude', name: 'Latitude', unit: 'deg', - format: 'float', + formatString: '%0.4f', hints: { range: 2 } }, { key: 'longitude', name: 'Longitude', unit: 'deg', - format: 'float', + formatString: '%0.4f', hints: { range: 3 } }, { key: 'velocity', name: 'Velocity', unit: 'km/s', - format: 'float', + formatString: '%0.3f', hints: { range: 4 } } ]; @@ -71,21 +71,19 @@ const CONJUNCTION_VALUES = [ { key: 'pc', name: 'Probability of Collision', - format: 'float', hints: { range: 1 } }, { key: 'missDistance', name: 'Miss Distance', unit: 'km', - format: 'float', + formatString: '%0.2f', hints: { range: 2 } }, { key: 'secondary', name: 'Secondary Object', - format: 'string', - hints: { range: 3 } + format: 'string' } ]; diff --git a/example/udlFederation/UDLTelemetryProvider.js b/example/udlFederation/UDLTelemetryProvider.js index a0b9693332..91c66f1a3e 100644 --- a/example/udlFederation/UDLTelemetryProvider.js +++ b/example/udlFederation/UDLTelemetryProvider.js @@ -24,6 +24,7 @@ import { EARTH_RADIUS_KM, MU_EARTH, SATELLITES } from './satellites.js'; const EPHEMERIS_PERIOD_MS = 1000; const CONJUNCTION_PERIOD_MS = 5000; +const MAX_REQUEST_DATUMS = 50000; const SECONDARY_OBJECTS = [ 'COSMOS 2251 DEB', 'FENGYUN 1C DEB', @@ -86,9 +87,15 @@ export default class UDLTelemetryProvider { request(domainObject, options) { const period = domainObject.type === 'udl.conjunctions' ? CONJUNCTION_PERIOD_MS : EPHEMERIS_PERIOD_MS; - const start = Math.floor(options.start / period) * period; - const data = []; + const size = options.size ?? MAX_REQUEST_DATUMS; + let start = Math.floor(options.start / period) * period; + const requestedCount = Math.floor((options.end - start) / period) + 1; + + if (requestedCount > size) { + start = options.end - (size - 1) * period; + } + const data = []; for (let timestamp = start; timestamp <= options.end; timestamp += period) { data.push(this.#datumFor(domainObject, timestamp)); } diff --git a/test-plan-udl.md b/test-plan-udl.md new file mode 100644 index 0000000000..394875faf2 --- /dev/null +++ b/test-plan-udl.md @@ -0,0 +1,25 @@ +# Test Plan: UDL Federation example plugin (PR #14) + +App: http://localhost:8080 (webpack dev server, branch devin/1785344499-udl-federation-plugin). Record the run. + +## Test 1: It should show UDL Federation Node tree with 3 satellites + Conjunction Assessments +- Expand "UDL Federation Node" root in left tree. +- PASS: children exactly: "GPS III SV05 (NAVSTAR 82)", "WGS-11", "SBIRS GEO-6 (USA 336)", "Conjunction Assessments". + +## Test 2: It should render live-updating ephemeris plot for a satellite +- Click GPS III SV05. Expect a plot view with series (Altitude ~20180 km ±2, plus lat/lon/velocity). +- Switch time conductor to Real-Time (local clock) mode; wait ~10s. +- PASS: plot renders data (non-empty line), new points appear at right edge over time (compare two screenshots ~10s apart). + +## Test 3: It should plot Pc values and highlight limit rows in telemetry table +- Click Conjunction Assessments: plot shows Pc values (mostly <2e-5, occasional spikes ≥2e-4). +- Switch view to "Telemetry Table" via view switcher (top right). +- PASS: table shows rows with Timestamp, Probability of Collision, Miss Distance, Secondary Object columns; rows with pc ≥1e-4 highlighted red (is-limit--red), 1e-5≤pc<1e-4 yellow. Widen historical window if needed to capture a spike (pc spikes when |sin(slot/7)|>0.92, slot=5s buckets, so spikes occur in bursts every ~110s — a 30+ min historical window guarantees several). + +## Test 4: It should produce no console errors from the plugin +- Check browser console after above interactions. +- PASS: no uncaught errors/warnings referencing udlFederation files. + +## Test 5 (Regression): existing objects still work +- Create > Sine Wave Generator into My Items; open it. +- PASS: object created and plot renders. diff --git a/test-report.md b/test-report.md new file mode 100644 index 0000000000..bc51d6baab --- /dev/null +++ b/test-report.md @@ -0,0 +1,48 @@ +# Test Report — PR #14: UDL Federation example plugin + +Tested end-to-end in the browser against the local webpack dev server (`npm start`, http://localhost:8080) on branch `devin/1785344499-udl-federation-plugin` (HEAD 62df42f). Recording: `/home/ubuntu/screencasts/rec-07188730-0284-41ec-8f54-9a65314a7790/rec-07188730-0284-41ec-8f54-9a65314a7790-edited.mp4`. + +## Results + +| # | Test | Result | +|---|------|--------| +| 1 | Tree shows UDL Federation Node with 3 satellites + Conjunction Assessments | ✅ Passed | +| 2 | Satellite ephemeris plot renders and streams live in Real-Time mode | ✅ Passed | +| 3 | Conjunction Assessments plots Pc; telemetry table highlights limit rows (red ≥1e-4, yellow ≥1e-5) | ✅ Passed | +| 4 | No console errors from the plugin | ✅ Passed | +| 5 | Regression: Sine Wave Generator create + plot still works | ✅ Passed | + +## Evidence + +### 1. Root node and children +Tree and Grid View show exactly: GPS III SV05 (NAVSTAR 82), WGS-11, SBIRS GEO-6 (USA 336) (all UDL Ephemeris) and Conjunction Assessments (UDL Conjunction Assessments). + +| Tree (zoom) | Grid View | +|---|---| +| ![Tree](https://app.devin.ai/attachments/0cd84fa7-e148-40b7-b220-b34382e86d90/ss_zoom_c74004fe.png) | ![Grid](https://app.devin.ai/attachments/51f780b0-67f9-4fec-bb01-b40d6feb123b/ss_93d8323d.png) | + +### 2. Live ephemeris plot (GPS III SV05) +Altitude plots ~20181.8–20182 km (matches simulated 20180 km ±2 wobble); Latitude/Longitude/Velocity available as series options. After switching the time conductor to Real-Time (Local Clock), the plot right edge advanced each second (axis extended 17:04 → 17:06 during observation, "Last update" ticking). + +| Fixed timespan plot | Real-Time mode streaming (axis extended, new points at right edge) | +|---|---| +| ![Fixed](https://app.devin.ai/attachments/6ae57317-142b-4bac-aa29-6539780e4368/ss_217a2175.png) | ![Realtime](https://app.devin.ai/attachments/5a1bc27c-92ea-4e5f-9821-f17fd1a81550/ss_3eb63459.png) | + +### 3. Conjunction Assessments — Pc plot and limit highlighting +Pc plot shows baseline values <2e-5 with periodic spikes up to ~7e-4. Telemetry Table view highlights Probability of Collision cells: **red** for Pc ≥ 1e-4 (e.g. 0.000679, 0.000694, 0.0007), **yellow** for 1e-5 ≤ Pc < 1e-4 (e.g. 0.0000107–0.0000183), no highlight below 1e-5 (e.g. 0.00000549). Rows carry Miss Distance (km) and Secondary Object (debris/RB names). + +| Pc plot | Table limit highlighting (zoom) | +|---|---| +| ![Pc plot](https://app.devin.ai/attachments/8aa3239f-53be-4423-aacd-d7a1ff2922fd/ss_14ad1a8d.png) | ![Table limits](https://app.devin.ai/attachments/9cd36722-c472-423d-9906-93eef5f53518/ss_zoom_fe05ab94.png) | + +### 4. Console +After a fresh page reload and interacting with all UDL objects, the console contained no errors. Only pre-existing noise: a Vue `defineExpose()` compiler-hint warning from `AppLayout/StatusIndicators` (present on master, unrelated to plugin) and webpack-dev-server/HMR info logs. + +### 5. Regression — Sine Wave Generator +Created via Create > Sine Wave Generator into My Items; plot renders live sine data in Real-Time mode. + +![Sine regression](https://app.devin.ai/attachments/d5d489cf-400b-4a26-a8e9-6a09b68535b9/ss_f6064aaa.png) + +## Notes +- The limit thresholds observed in the table match `ConjunctionLimitProvider.js` (PC_CRITICAL=1e-4 red, PC_WARNING=1e-5 yellow). +- No issues found. From 5a0bdcbaad6e4de136fa7071a519ca27048334a2 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Wed, 29 Jul 2026 17:16:49 +0000 Subject: [PATCH 3/5] Down-sample bounded requests across full window; drop test artifacts; add norad to cspell Co-Authored-By: Jake Cosme --- .cspell.json | 4 +- example/udlFederation/UDLTelemetryProvider.js | 12 ++--- test-plan-udl.md | 25 ---------- test-report.md | 48 ------------------- 4 files changed, 8 insertions(+), 81 deletions(-) delete mode 100644 test-plan-udl.md delete mode 100644 test-report.md diff --git a/.cspell.json b/.cspell.json index 8b99ef2db1..2bd524dbdd 100644 --- a/.cspell.json +++ b/.cspell.json @@ -497,7 +497,9 @@ "sbirs", "SBIRS", "FENGYUN", - "STARLINK" + "STARLINK", + "norad", + "NORAD" ], "dictionaries": ["npm", "softwareTerms", "node", "html", "css", "bash", "en_US", "en-gb", "misc"], "ignorePaths": [ diff --git a/example/udlFederation/UDLTelemetryProvider.js b/example/udlFederation/UDLTelemetryProvider.js index 91c66f1a3e..ad6db11679 100644 --- a/example/udlFederation/UDLTelemetryProvider.js +++ b/example/udlFederation/UDLTelemetryProvider.js @@ -88,16 +88,14 @@ export default class UDLTelemetryProvider { const period = domainObject.type === 'udl.conjunctions' ? CONJUNCTION_PERIOD_MS : EPHEMERIS_PERIOD_MS; const size = options.size ?? MAX_REQUEST_DATUMS; - let start = Math.floor(options.start / period) * period; + const start = Math.floor(options.start / period) * period; const requestedCount = Math.floor((options.end - start) / period) + 1; - - if (requestedCount > size) { - start = options.end - (size - 1) * period; - } + const count = Math.min(requestedCount, size); + const step = count > 1 ? (options.end - start) / (count - 1) : period; const data = []; - for (let timestamp = start; timestamp <= options.end; timestamp += period) { - data.push(this.#datumFor(domainObject, timestamp)); + for (let i = 0; i < count; i++) { + data.push(this.#datumFor(domainObject, start + Math.round(i * step))); } return Promise.resolve(data); diff --git a/test-plan-udl.md b/test-plan-udl.md deleted file mode 100644 index 394875faf2..0000000000 --- a/test-plan-udl.md +++ /dev/null @@ -1,25 +0,0 @@ -# Test Plan: UDL Federation example plugin (PR #14) - -App: http://localhost:8080 (webpack dev server, branch devin/1785344499-udl-federation-plugin). Record the run. - -## Test 1: It should show UDL Federation Node tree with 3 satellites + Conjunction Assessments -- Expand "UDL Federation Node" root in left tree. -- PASS: children exactly: "GPS III SV05 (NAVSTAR 82)", "WGS-11", "SBIRS GEO-6 (USA 336)", "Conjunction Assessments". - -## Test 2: It should render live-updating ephemeris plot for a satellite -- Click GPS III SV05. Expect a plot view with series (Altitude ~20180 km ±2, plus lat/lon/velocity). -- Switch time conductor to Real-Time (local clock) mode; wait ~10s. -- PASS: plot renders data (non-empty line), new points appear at right edge over time (compare two screenshots ~10s apart). - -## Test 3: It should plot Pc values and highlight limit rows in telemetry table -- Click Conjunction Assessments: plot shows Pc values (mostly <2e-5, occasional spikes ≥2e-4). -- Switch view to "Telemetry Table" via view switcher (top right). -- PASS: table shows rows with Timestamp, Probability of Collision, Miss Distance, Secondary Object columns; rows with pc ≥1e-4 highlighted red (is-limit--red), 1e-5≤pc<1e-4 yellow. Widen historical window if needed to capture a spike (pc spikes when |sin(slot/7)|>0.92, slot=5s buckets, so spikes occur in bursts every ~110s — a 30+ min historical window guarantees several). - -## Test 4: It should produce no console errors from the plugin -- Check browser console after above interactions. -- PASS: no uncaught errors/warnings referencing udlFederation files. - -## Test 5 (Regression): existing objects still work -- Create > Sine Wave Generator into My Items; open it. -- PASS: object created and plot renders. diff --git a/test-report.md b/test-report.md deleted file mode 100644 index bc51d6baab..0000000000 --- a/test-report.md +++ /dev/null @@ -1,48 +0,0 @@ -# Test Report — PR #14: UDL Federation example plugin - -Tested end-to-end in the browser against the local webpack dev server (`npm start`, http://localhost:8080) on branch `devin/1785344499-udl-federation-plugin` (HEAD 62df42f). Recording: `/home/ubuntu/screencasts/rec-07188730-0284-41ec-8f54-9a65314a7790/rec-07188730-0284-41ec-8f54-9a65314a7790-edited.mp4`. - -## Results - -| # | Test | Result | -|---|------|--------| -| 1 | Tree shows UDL Federation Node with 3 satellites + Conjunction Assessments | ✅ Passed | -| 2 | Satellite ephemeris plot renders and streams live in Real-Time mode | ✅ Passed | -| 3 | Conjunction Assessments plots Pc; telemetry table highlights limit rows (red ≥1e-4, yellow ≥1e-5) | ✅ Passed | -| 4 | No console errors from the plugin | ✅ Passed | -| 5 | Regression: Sine Wave Generator create + plot still works | ✅ Passed | - -## Evidence - -### 1. Root node and children -Tree and Grid View show exactly: GPS III SV05 (NAVSTAR 82), WGS-11, SBIRS GEO-6 (USA 336) (all UDL Ephemeris) and Conjunction Assessments (UDL Conjunction Assessments). - -| Tree (zoom) | Grid View | -|---|---| -| ![Tree](https://app.devin.ai/attachments/0cd84fa7-e148-40b7-b220-b34382e86d90/ss_zoom_c74004fe.png) | ![Grid](https://app.devin.ai/attachments/51f780b0-67f9-4fec-bb01-b40d6feb123b/ss_93d8323d.png) | - -### 2. Live ephemeris plot (GPS III SV05) -Altitude plots ~20181.8–20182 km (matches simulated 20180 km ±2 wobble); Latitude/Longitude/Velocity available as series options. After switching the time conductor to Real-Time (Local Clock), the plot right edge advanced each second (axis extended 17:04 → 17:06 during observation, "Last update" ticking). - -| Fixed timespan plot | Real-Time mode streaming (axis extended, new points at right edge) | -|---|---| -| ![Fixed](https://app.devin.ai/attachments/6ae57317-142b-4bac-aa29-6539780e4368/ss_217a2175.png) | ![Realtime](https://app.devin.ai/attachments/5a1bc27c-92ea-4e5f-9821-f17fd1a81550/ss_3eb63459.png) | - -### 3. Conjunction Assessments — Pc plot and limit highlighting -Pc plot shows baseline values <2e-5 with periodic spikes up to ~7e-4. Telemetry Table view highlights Probability of Collision cells: **red** for Pc ≥ 1e-4 (e.g. 0.000679, 0.000694, 0.0007), **yellow** for 1e-5 ≤ Pc < 1e-4 (e.g. 0.0000107–0.0000183), no highlight below 1e-5 (e.g. 0.00000549). Rows carry Miss Distance (km) and Secondary Object (debris/RB names). - -| Pc plot | Table limit highlighting (zoom) | -|---|---| -| ![Pc plot](https://app.devin.ai/attachments/8aa3239f-53be-4423-aacd-d7a1ff2922fd/ss_14ad1a8d.png) | ![Table limits](https://app.devin.ai/attachments/9cd36722-c472-423d-9906-93eef5f53518/ss_zoom_fe05ab94.png) | - -### 4. Console -After a fresh page reload and interacting with all UDL objects, the console contained no errors. Only pre-existing noise: a Vue `defineExpose()` compiler-hint warning from `AppLayout/StatusIndicators` (present on master, unrelated to plugin) and webpack-dev-server/HMR info logs. - -### 5. Regression — Sine Wave Generator -Created via Create > Sine Wave Generator into My Items; plot renders live sine data in Real-Time mode. - -![Sine regression](https://app.devin.ai/attachments/d5d489cf-400b-4a26-a8e9-6a09b68535b9/ss_f6064aaa.png) - -## Notes -- The limit thresholds observed in the table match `ConjunctionLimitProvider.js` (PC_CRITICAL=1e-4 red, PC_WARNING=1e-5 yellow). -- No issues found. From f4d22f7e188acfba22ce041c258b9ca9372245d0 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Wed, 29 Jul 2026 17:22:06 +0000 Subject: [PATCH 4/5] Handle latest strategy in request(); enforce hard datum cap Co-Authored-By: Jake Cosme --- example/udlFederation/UDLTelemetryProvider.js | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/example/udlFederation/UDLTelemetryProvider.js b/example/udlFederation/UDLTelemetryProvider.js index ad6db11679..d10beaa58b 100644 --- a/example/udlFederation/UDLTelemetryProvider.js +++ b/example/udlFederation/UDLTelemetryProvider.js @@ -87,15 +87,22 @@ export default class UDLTelemetryProvider { request(domainObject, options) { const period = domainObject.type === 'udl.conjunctions' ? CONJUNCTION_PERIOD_MS : EPHEMERIS_PERIOD_MS; - const size = options.size ?? MAX_REQUEST_DATUMS; + const size = Math.min(options.size ?? MAX_REQUEST_DATUMS, MAX_REQUEST_DATUMS); const start = Math.floor(options.start / period) * period; const requestedCount = Math.floor((options.end - start) / period) + 1; const count = Math.min(requestedCount, size); - const step = count > 1 ? (options.end - start) / (count - 1) : period; - const data = []; - for (let i = 0; i < count; i++) { - data.push(this.#datumFor(domainObject, start + Math.round(i * step))); + + if (options.strategy === 'latest') { + const alignedEnd = Math.floor(options.end / period) * period; + for (let i = count - 1; i >= 0; i--) { + data.push(this.#datumFor(domainObject, alignedEnd - i * period)); + } + } else { + const step = count > 1 ? (options.end - start) / (count - 1) : period; + for (let i = 0; i < count; i++) { + data.push(this.#datumFor(domainObject, start + Math.round(i * step))); + } } return Promise.resolve(data); From a0e7ac1ee4d9151a3605a07a5a1d6dd6c62c5132 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Wed, 29 Jul 2026 17:31:26 +0000 Subject: [PATCH 5/5] Do not install UDL federation example by default in dev sandbox Co-Authored-By: Jake Cosme --- index.html | 1 - 1 file changed, 1 deletion(-) diff --git a/index.html b/index.html index 001972ddc8..42417f0022 100644 --- a/index.html +++ b/index.html @@ -105,7 +105,6 @@ openmct.install(openmct.plugins.example.EventGeneratorPlugin()); openmct.install(openmct.plugins.example.ExampleImagery()); openmct.install(openmct.plugins.example.ExampleTags()); - openmct.install(openmct.plugins.example.UDLFederation()); openmct.install(openmct.plugins.Espresso()); openmct.install(openmct.plugins.MyItems());