Skip to content

Add cable health scoring via sebuf InfrastructureService#220

Merged
koala73 merged 2 commits into
mainfrom
feat/cable-health-sebuf
Feb 21, 2026
Merged

Add cable health scoring via sebuf InfrastructureService#220
koala73 merged 2 commits into
mainfrom
feat/cable-health-sebuf

Conversation

@SebastienMelki

@SebastienMelki SebastienMelki commented Feb 21, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • Ports submarine cable health monitoring from Add cable health scoring layer with NGA signal processing #134 to the sebuf architecture
  • Adds GetCableHealth RPC to InfrastructureService — analyzes NGA maritime warnings to detect cable faults/repair activity, computes health scores with time-decay
  • Frontend colors cables red (fault) or orange (degraded) on the map, shows evidence in popup

Replaces #134.

Changes

  • Proto: GetCableHealthRequest/Response, CableHealthRecord, CableHealthEvidence, CableHealthStatus enum
  • Handler: NGA warning fetch, cable matching (name + proximity), signal processing, health computation with redis caching
  • Client: circuit breaker, proto enum → frontend string adapter, 1-min local cache
  • Frontend: health-based cable coloring (fault=red dashed pulse, degraded=orange), evidence in cable popup, SVG + DeckGL support

Test plan

  • npm run devcurl -X POST http://localhost:3000/api/infrastructure/v1/get-cable-health -H 'Content-Type: application/json' -d '{}' returns valid response
  • Enable cables layer in UI, verify health coloring works for any active NGA warnings
  • Click a cable with health data — popup shows status badge and evidence section
  • No errors when NGA has no cable-related warnings (empty cables: {})
  • npm run typecheck passes (ignoring pre-existing posthog-js issue)

🤖 Generated with Claude Code

Port submarine cable health monitoring from PR #134 to the sebuf
architecture. Adds GetCableHealth RPC to InfrastructureService that
analyzes NGA maritime warnings to detect cable faults and repair
activity, computing health scores with time-decay.

- Proto: GetCableHealthRequest/Response, CableHealthRecord, evidence
- Handler: NGA warning fetch, cable matching (name + proximity), signal
  processing, health computation with redis caching
- Client: circuit breaker, proto enum → frontend string adapter, 1-min cache
- Frontend: health-based cable coloring (fault=red, degraded=orange),
  evidence display in cable popup, SVG + DeckGL support

Co-Authored-By: Claude Opus 4.6 <[email protected]>
@vercel

vercel Bot commented Feb 21, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
worldmonitor Ready Ready Preview, Comment Feb 21, 2026 11:13pm
worldmonitor-finance Ready Ready Preview, Comment Feb 21, 2026 11:13pm
worldmonitor-startup Ready Ready Preview, Comment Feb 21, 2026 11:13pm

Request Review

@SebastienMelki

Copy link
Copy Markdown
Collaborator Author

This replaces #134, porting the cable health feature from a standalone edge function to the sebuf InfrastructureService architecture.

@koala73

koala73 commented Feb 21, 2026

Copy link
Copy Markdown
Owner

PR Review: Cable Health Scoring via sebuf InfrastructureService

Overall: Well-structured feature with clean proto definition, solid signal processing, and good integration across both map renderers. A few issues worth addressing.


Issues

1. BLOCKING — Euclidean distance for geographic proximity is inaccurate
server/worldmonitor/infrastructure/v1/get-cable-health.ts (findNearestCable)

const dist = Math.sqrt((lat - lLat) ** 2 + (lon - lLon) ** 2);

Uses Euclidean distance on lat/lon degrees. At the equator 1° = 111km, but at 60°N it's ~55km for longitude. A cable landing at Singapore (1.35°N) vs one at Norway (62°N) will have wildly different "5 degree" thresholds. The distanceKm = Math.round(nearest.distanceDeg * 111) also assumes equatorial spacing.

At minimum, scale longitude by cos(lat):

Math.sqrt((lat - lLat) ** 2 + ((lon - lLon) * Math.cos(lat * Math.PI / 180)) ** 2)

Or use haversine, but the cosine adjustment is sufficient for a 5-degree radius.


2. BLOCKING — kind: 'operator_fault' used for both fault AND advisory signals
get-cable-health.ts line 462 — The non-fault branch (generic cable advisory, no FAULT_KEYWORDS match) also sets kind: 'operator_fault'. This means computeHealthMap treats a generic "cable laying" advisory the same as a confirmed fault when checking hasOperatorFault. Should be kind: 'cable_advisory' for the non-fault path.


3. SUGGESTION — Race between loadCableActivity() and loadCableHealth() on toggle
App.ts — When cables layer is toggled on, both run sequentially:

case 'cables':
  await this.loadCableActivity();
  await this.loadCableHealth();

These are independent and could run in parallel with Promise.all. Not blocking, but adds unnecessary latency.


4. SUGGESTION — console.log left in production path
src/services/cable-health.ts:

console.log(`[CableHealth] ${faultCount} faults, ${degradedCount} degraded`);

Logs every time health is fetched (every 5 minutes, or 1 minute from local cache). Consider removing or guarding behind verbose/debug mode.


5. SUGGESTION — No in-memory fallback cache on server handler
get-cable-health.ts — If Redis is down AND NGA is down, returns { cables: {} }. Other handlers in this codebase (e.g., UCDP) have in-memory fallback caches that serve stale data. Consider adding one so a transient failure doesn't wipe existing health state on Railway.


6. NITPICK — CablesEntry schema is orphaned
InfrastructureService.openapi.yamlCablesEntry is generated but not referenced anywhere (the response uses additionalProperties directly). Harmless but noisy in the schema.


7. NITPICK — Missing i18n keys for other locales
Only en.json gets popups.cable.health.evidence. Other locales will fall back to the key string. Fine for now but should be noted for a future translation pass.


What looks good

  • Proto design is clean — map<string, CableHealthRecord> is the right choice for a sparse cable-keyed response
  • Signal processing with time-decay weighting is well thought out
  • Both SVG and DeckGL map renderers updated consistently
  • Circuit breaker on client side with proper fallback
  • Server-side Redis caching with 3-min TTL is reasonable for NGA data freshness
  • Evidence shown in popup gives users transparency into scoring

- Fix geographic proximity: use cosine-latitude correction instead of
  raw Euclidean distance on lat/lon degrees, return distanceKm directly
- Fix signal kind: use 'cable_advisory' (not 'operator_fault') for
  non-fault NGA warnings so advisories don't trigger fault status
- Parallelize loadCableActivity + loadCableHealth with Promise.all
- Remove console.log from client-side cable-health service
- Add in-memory fallback cache on server so transient Redis+NGA
  failures serve stale data instead of empty response

Co-Authored-By: Claude Opus 4.6 <[email protected]>
@SebastienMelki

Copy link
Copy Markdown
Collaborator Author

Thanks for the thorough review! All addressed in d3ac428:

1. BLOCKING — Euclidean distance — Fixed. findNearestCable now scales longitude by cos(lat) and computes distance in km directly. The caller no longer does the broken distanceDeg * 111 conversion. Threshold is 555 km (~5° at equator).

2. BLOCKING — kind: 'operator_fault' on advisories — Fixed. Non-fault branch now uses kind: 'cable_advisory', so generic cable-laying/maintenance NGA warnings won't trigger the hasOperatorFault path in computeHealthMap.

3. SUGGESTION — Sequential cable loads — Fixed. Wrapped in Promise.all([loadCableActivity(), loadCableHealth()]).

4. SUGGESTION — console.log in production — Removed.

5. SUGGESTION — In-memory fallback cache — Added. Follows the same pattern as UCDP — fallbackCache is populated on every successful response (from Redis or NGA), and the catch block serves stale data when both are down.

6. NITPICK — Orphaned CablesEntry schema — Acknowledged. This is auto-generated by buf generate from the map<string, CableHealthRecord> proto field. We can't easily suppress it without a custom openapi plugin config, so leaving as-is.

7. NITPICK — Missing i18n keys — Noted for future translation pass. Only en.json is actively maintained right now.

Ready for re-review when you get a chance @koala73!

@koala73

koala73 commented Feb 21, 2026

Copy link
Copy Markdown
Owner

Thank you @SebastienMelki

@koala73
koala73 merged commit 845e6e9 into main Feb 21, 2026
4 checks passed
koala73 added a commit that referenced this pull request Feb 21, 2026
PR #220 added popups.cable.health.evidence only to en.json.
Add translated values to all 15 other locales.
@koala73
koala73 deleted the feat/cable-health-sebuf branch February 25, 2026 08:01
facusturla pushed a commit to facusturla/worldmonitor that referenced this pull request Feb 27, 2026
* feat: add cable health scoring via sebuf InfrastructureService

Port submarine cable health monitoring from PR koala73#134 to the sebuf
architecture. Adds GetCableHealth RPC to InfrastructureService that
analyzes NGA maritime warnings to detect cable faults and repair
activity, computing health scores with time-decay.

- Proto: GetCableHealthRequest/Response, CableHealthRecord, evidence
- Handler: NGA warning fetch, cable matching (name + proximity), signal
  processing, health computation with redis caching
- Client: circuit breaker, proto enum → frontend string adapter, 1-min cache
- Frontend: health-based cable coloring (fault=red, degraded=orange),
  evidence display in cable popup, SVG + DeckGL support

Co-Authored-By: Claude Opus 4.6 <[email protected]>

* fix: address PR review feedback for cable health scoring

- Fix geographic proximity: use cosine-latitude correction instead of
  raw Euclidean distance on lat/lon degrees, return distanceKm directly
- Fix signal kind: use 'cable_advisory' (not 'operator_fault') for
  non-fault NGA warnings so advisories don't trigger fault status
- Parallelize loadCableActivity + loadCableHealth with Promise.all
- Remove console.log from client-side cable-health service
- Add in-memory fallback cache on server so transient Redis+NGA
  failures serve stale data instead of empty response

Co-Authored-By: Claude Opus 4.6 <[email protected]>

---------

Co-authored-by: Claude Opus 4.6 <[email protected]>
facusturla pushed a commit to facusturla/worldmonitor that referenced this pull request Feb 27, 2026
PR koala73#220 added popups.cable.health.evidence only to en.json.
Add translated values to all 15 other locales.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants