Add example UDL federation plugin (ephemeris + conjunction assessments) - #14
Add example UDL federation plugin (ephemeris + conjunction assessments)#14jakexcosme wants to merge 5 commits into
Conversation
Co-Authored-By: Jake Cosme <jake@cognition.ai>
Original prompt from Jake
|
🤖 Devin AI EngineerI'll be helping with this pull request! Here's what you should know: ✅ I will automatically:
Note: I can only respond to comments from users who have write access to this repository. ⚙️ Control Options:
|
| 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); | ||
| } |
There was a problem hiding this comment.
📝 Info: Ephemeris view could generate up to 50000 full-resolution datums per request
When no size is provided, request() defaults to MAX_REQUEST_DATUMS = 50000 and generates that many datums, each running trig-heavy orbital math (ephemerisDatum). Plots pass size: 1000 so this ceiling is normally not hit there, but other consumers (e.g. tables/exports without a size) could trigger large synchronous loops. Acceptable for an example plugin, but flagging the potential main-thread cost since the generator plugin offloads this to a web worker.
Was this helpful? React with 👍 or 👎 to provide feedback.
There was a problem hiding this comment.
Addressed — request() now honors options.size and applies a 50k-datum hard cap, keeping the most recent window when the requested range exceeds the bound, so very large custom time ranges can no longer allocate unbounded datum arrays.
| 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); | ||
| }; | ||
| } |
There was a problem hiding this comment.
📝 Info: Realtime datum timestamps are unaligned versus aligned historical timestamps
request() aligns timestamps to period boundaries (Math.floor(options.start / period) * period, example/udlFederation/UDLTelemetryProvider.js:91), while subscribe() emits at Date.now() (:115), which is not period-aligned. At the historical→realtime handoff in a plot this produces a small timestamp discontinuity. Cosmetic only; values are deterministic functions of the timestamp so no data corruption occurs.
Was this helpful? React with 👍 or 👎 to provide feedback.
There was a problem hiding this comment.
Acknowledged — as noted, conjunction datum content is slot-keyed and ephemeris is a continuous function of timestamp, so the unaligned live-edge timestamps stay consistent with historical values. Leaving as-is.
… request size Co-Authored-By: Jake Cosme <jake@cognition.ai>
✅ E2E test results — UDL Federation pluginTested in-browser against the local dev server (
Test run preview: Conjunction Assessments telemetry table — limit highlighting (red ≥1e-4, yellow ≥1e-5): Console was clean after a fresh reload (only a pre-existing Vue |
| 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()); |
There was a problem hiding this comment.
📝 Info: Plugin registration and install conventions verified against existing examples
Verified plugins.example.UDLFederation = UDLFederationPlugin combined with openmct.install(openmct.plugins.example.UDLFederation()) is correct: UDLFederationPlugin() returns the install(openmct) closure, matching the EventGeneratorPlugin pattern (example/eventGenerator/plugin.js:27-28). The root object (type: 'folder', location: 'ROOT') plus the custom composition provider keyed on namespace udl + type folder correctly yields the three satellites and the conjunctions feed as children; leaf objects have no composition, so the default provider does not apply. No bug here.
Was this helpful? React with 👍 or 👎 to provide feedback.
… add norad to cspell Co-Authored-By: Jake Cosme <jake@cognition.ai>
| getLimits() { | ||
| return { | ||
| limits: function () { | ||
| return Promise.resolve({ | ||
| WARNING: { | ||
| high: { | ||
| color: 'yellow', | ||
| pc: PC_WARNING | ||
| } | ||
| }, | ||
| CRITICAL: { | ||
| high: { | ||
| color: 'red', | ||
| pc: PC_CRITICAL | ||
| } | ||
| } | ||
| }); | ||
| } | ||
| }; | ||
| } |
There was a problem hiding this comment.
📝 Info: Limit provider interface and getLimits format match existing conventions
I verified ConjunctionLimitProvider against src/api/telemetry/TelemetryAPI.js:180-231,961-1016 and the existing example/generator/SinewaveLimitProvider.js. supportsLimits/getLimitEvaluator/getLimits signatures are correct, and the getLimits payload uses the range key (pc) inside the high objects consistent with Sinewave's use of sin/cos. No issue here — noted since the limit shape is easy to get wrong.
Was this helpful? React with 👍 or 👎 to provide feedback.
Co-Authored-By: Jake Cosme <jake@cognition.ai>
| getLimits() { | ||
| return { | ||
| limits: function () { | ||
| return Promise.resolve({ | ||
| WARNING: { | ||
| high: { | ||
| color: 'yellow', | ||
| pc: PC_WARNING | ||
| } | ||
| }, | ||
| CRITICAL: { | ||
| high: { | ||
| color: 'red', | ||
| pc: PC_CRITICAL | ||
| } | ||
| } | ||
| }); | ||
| } | ||
| }; | ||
| } |
There was a problem hiding this comment.
📝 Info: Pc limit lines render a bogus value if a non-pc range is plotted
ConjunctionLimitProvider.getLimits() (example/udlFederation/ConjunctionLimitProvider.js:67-86) returns limit definitions keyed only under pc (e.g. { color, pc }). The plot's MCTChartAlarmLineSet.getLimitPoints() (src/plugins/plot/chart/MCTChartAlarmLineSet.js:71-89) draws a limit line for any level whose high exists, reading the series' y-value via series.getYVal(limitForLevel.high). If a user plots the missDistance (or another) range of the conjunction object, high has no missDistance key, so getYVal yields undefined and a meaningless/degenerate limit line could be drawn. The evaluator (:49-63) already guards on valueMetadata.key !== 'pc', so table highlighting is unaffected; only stray plot limit lines for non-pc ranges are the concern. This mirrors the simplicity of SinewaveLimitProvider, so it is an edge case rather than a clear defect.
Was this helpful? React with 👍 or 👎 to provide feedback.
| } 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))); | ||
| } | ||
| } |
There was a problem hiding this comment.
📝 Info: Historical request spaces datums evenly rather than at true cadence when capped
In UDLTelemetryProvider.request() the non-latest branch (example/udlFederation/UDLTelemetryProvider.js:101-106) computes step = (options.end - start) / (count - 1) where count = Math.min(requestedCount, size) (capped at MAX_REQUEST_DATUMS = 50000). When requestedCount exceeds the cap/size, datums are spread evenly across the window instead of at the true period cadence (1s ephemeris / 5s conjunction). For plots this is fine (effectively downsampling), but a telemetry table viewing large historical windows would show rows at non-period-aligned timestamps. Not a correctness bug for the example's purpose, just worth noting.
Was this helpful? React with 👍 or 👎 to provide feedback.
Co-Authored-By: Jake Cosme <jake@cognition.ai>
| #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); | ||
| } |
There was a problem hiding this comment.
📝 Info: Ephemeris datum lookup assumes the satellite key always exists
example/udlFederation/UDLTelemetryProvider.js:128-130 looks up the satellite via SATELLITES.find(... === domainObject.identifier.key) and immediately dereferences it in ephemerisDatum (satellite.altitudeKm). If any object of type udl.ephemeris existed whose key is not in SATELLITES, this would throw. In the current design the only udl.ephemeris objects are the three provider-defined satellites (types are not creatable), so this is unreachable today. Worth noting if the type is ever made user-creatable.
Was this helpful? React with 👍 or 👎 to provide feedback.
| "Tabnabbing", | ||
| "elset", | ||
| "statevector", | ||
| "NAVSTAR", | ||
| "raan", | ||
| "sbirs", | ||
| "SBIRS", | ||
| "FENGYUN", | ||
| "STARLINK", | ||
| "norad", | ||
| "NORAD" |
There was a problem hiding this comment.
📝 Info: cspell relies on 3-letter identifiers being under minWordLength
The PR adds many domain terms to .cspell.json (elset, statevector, NAVSTAR, raan, sbirs, FENGYUN, STARLINK, norad) but not 'udl', 'gps', 'geo', 'wgs', 'deb', which appear widely in the new code. This passes CI only because cspell's default minWordLength (4) skips those 3-char tokens, and multi-char terms like 'ephemeris'/'conjunction'/'iridium'/'cosmos' are standard dictionary words. If the spellcheck config or dictionary set changes, these could start failing. Not flagged as a bug since the current CI check passes.
Was this helpful? React with 👍 or 👎 to provide feedback.
|
CI status: all checks green except e2e-couchdb, which is failing at the Codecov upload step ( |
Describe your changes:
Adds an example plugin simulating a Unified Data Library (UDL) federation node: a
udl:noderoot exposing three simulated satellites (GPS III SV05, WGS-11, SBIRS GEO-6) with live ephemeris telemetry (altitude/lat/lon/velocity from simplified two-body circular-orbit math) and a Conjunction Assessments feed (Pc, miss distance, secondary object) with probability-of-collision screening limits (warning ≥ 1e-5, critical ≥ 1e-4).Structure (mirrors
example/generatorandexample/eventGeneratorconventions):example/udlFederation/plugin.js— registersudl.ephemeris/udl.conjunctionstypes, root object, object + composition providers, telemetry + limit providersUDLTelemetryProvider.js— deterministic historicalrequest()(honorsoptions.sizeandstrategy: 'latest', spreads capped results across the full window, hard-capped at 50k datums) and realtimesubscribe()for both feed typesConjunctionLimitProvider.js— Pc limit evaluator/levelsopenmct.plugins.example.UDLFederationThe plugin is not installed in
index.htmlby default (its root object changes the tree baseline that the e2e suite assumes). To enable it in the dev sandbox, add:Smoke tested against
npm startwith the plugin enabled: tree renders the federation node, ephemeris plots stream, conjunction Pc plot renders with limit thresholds, telemetry table rows highlight red/yellow on Pc threshold crossings. Full E2E evidence (recording + screenshots) in the PR comments.Original prompt
All Submissions:
Author Checklist
type:label? Note: this is not necessarily the same as the original issue.Reviewer Checklist
Link to Devin session: https://app.devin.ai/sessions/d40ff69ddaa84563a7003d6330be7a2f
Requested by: @jakexcosme
Devin Review