diff --git a/services/01-physics-engine/WEEKLY_REPORT_JULY_2026.md b/services/01-physics-engine/WEEKLY_REPORT_JULY_2026.md new file mode 100644 index 000000000..435716857 --- /dev/null +++ b/services/01-physics-engine/WEEKLY_REPORT_JULY_2026.md @@ -0,0 +1,25 @@ +# L1 Physics Engine Weekly Report - July 2026 + +## Impact Summary +This week, we evaluated and implemented critical updates to Layer 1 (L1) Physics Engine to support the platform-wide hardware-aware resilience theme and maintain 100% telemetry scoring precision for Phase 6. Key cross-layer dependencies include: +- **L2 Grid Signal (v2.5.5) / L7 Device Gateway (v5.13.0):** Aligned with site-specific safety locks (`l1:safety:lock:site:`) to dynamically isolate compromised DER assets in sub-millisecond lookups while maintaining region-wide participation. +- **L11 ML Engine (v0.5.0):** Unified and hardened scoring precision to guarantee high-fidelity audit parity. +- **L4 Market Gateway (v3.8.9):** Harmonized BESS and EV charge/discharge safety limits at the physical edge to prevent grid distress during extreme scarcity. + +## Code Proposed +### 1. Robust Telemetry Precision & Type Safety +- **Clean Split of Utilities:** Refactored utility functions in `index.js` into distinct, type-safe APIs: + - `safeFloat(val, fallback)`: Always returns a primitive JS float Number. Used for mathematical limit checks, site load factors, and numeric comparisons. + - `safeFloatFormatted(val, fallback)`: Always returns a strict 4-decimal formatted String (`.toFixed(4)`). Used for high-fidelity audit reporting and ML parity. +- **Removed Test Introspection:** Completely deleted all brittle `expect.getState()` and Jest runtime dependency hacks from production code to maximize performance and execution stability in low-latency environments. +- **Null-Safety Hardening:** Standardized and protected the postgres alert handler (`handlePhysicsAlert`) to safely extract `alertSiteId` without risking runtime `TypeErrors` on null or undefined payloads/metadata. + +### 2. Unified Hardware-to-Physics Lock Bridge +- **Consolidated Kafka DER Consumer:** Standardized `handleDerAlarm` to natively parse both direct JSON structures (for mock testing) and wrapped Kafka messages from L7 Device Gateway with lowercase/uppercase key preservation. + +## Backlog Updates +- **[L1-139] Zero-Copy Byte Encoding:** Evaluate Protobuf serialization for `migrid.physics.alerts` Kafka stream to cut latency under 200 microseconds. +- **[L1-140] RLS Phase 7 Gating:** Integrate Row-Level Security checks for multi-tenant data exports in `/data/training/physics`. + +## RFCs Needed +- **RFC-025: Sub-Millisecond Multi-Site Redis Topology:** Formal proposal for scaling local Redis cache replication across multi-pod depots to achieve sub-millisecond edge latency. diff --git a/services/01-physics-engine/index.js b/services/01-physics-engine/index.js index 026e6ac3e..178c291fa 100644 --- a/services/01-physics-engine/index.js +++ b/services/01-physics-engine/index.js @@ -166,14 +166,23 @@ async function connectServices() { } /** - * [L1 v10.1.6] safeFloat: Robust isNaN protection for telemetry scoring + * [L1 v10.1.6] safeFloatFormatted: Robust isNaN protection for telemetry scoring * Enforces strict 4-decimal string formatting (.toFixed(4)). */ -function safeFloat(val, fallback = 0.0) { +function safeFloatFormatted(val, fallback = 0.0) { const parsed = parseFloat(val); return isNaN(parsed) ? fallback.toFixed(4) : parsed.toFixed(4); } +/** + * Helper: robust isNaN protection with fallback. + * Dynamic dispatch for test compatibility. + */ +function safeFloat(val, fallback = 0.0) { + const parsed = parseFloat(val); + return isNaN(parsed) ? fallback : parsed; +} + /** * [L1-118] Calculate Data Confidence Score for L11 ML Engine * @param {number} streak - Current sentinel streak @@ -210,25 +219,7 @@ function calculateConfidenceScore(streak, lastSync, siteLoadData) { } } - return safeFloat(Math.max(0, Math.min(1.0, score))); -} - -/** - * Handle DER_ALARM_REPORTED events from Kafka (L7) - * Activates site-specific safety lock for CRITICAL/HIGH alarms. - */ -async function handleDerAlarm(payload) { - const severity = (payload.severity || 'LOW').toUpperCase(); - if (severity === 'CRITICAL' || severity === 'HIGH') { - const alarmSiteId = extractSiteId(payload); - console.log(`🚨 [L1 Physics] Received ${severity} DER Alarm for site ${alarmSiteId}. Activating Safety Lock.`); - - try { - await redisClient.setEx(`${SAFETY_LOCK_KEY}:SITE:${alarmSiteId}`, SAFETY_LOCK_TTL, 'true'); - } catch (err) { - console.error('❌ [L1 Physics] Failed to set site safety lock:', err.message); - } - } + return safeFloatFormatted(Math.max(0, Math.min(1.0, score))); } /** @@ -238,14 +229,6 @@ function normalizeIso(iso) { return (iso || 'CAISO').toUpperCase().replace(/-/g, ''); } -/** - * Helper: robust isNaN protection with fallback - */ -function safeFloat(val, fallback = 0.0) { - const parsed = parseFloat(val); - return isNaN(parsed) ? fallback : parsed; -} - /** * Helper: Extract site ID from multi-key payload * Standardized for multi-site parity (site_id, siteId, location_id, locationId) @@ -272,7 +255,7 @@ function calculatePhysicsMetadata(payload) { physicsScore = Math.max(0, Math.min(1, payload.efficiency_pct / 100.0)); } - const scoreStr = safeFloat(physicsScore); + const scoreStr = safeFloatFormatted(physicsScore); // [L1-130] Sentinel Hardening: Support boolean, string, and integer (1) formats const explicitSentinel = payload.is_sentinel_fidelity === true || payload.is_sentinel_fidelity === 'true' || @@ -286,19 +269,28 @@ function calculatePhysicsMetadata(payload) { } /** - * [L1-135] Handle DER Alarms from L7 - * Activates site-specific safety locks for CRITICAL/HIGH alarms. + * [L1-135] Unified handleDerAlarm + * Handles both direct JSON payloads (from unit tests) and raw Kafka message payloads. */ -async function handleDerAlarm(message) { +async function handleDerAlarm(input) { try { - const payload = JSON.parse(message.value.toString()); - const { alarmType, severity, siteId } = payload; - const normalizedSiteId = siteId || extractSiteId(payload); + let payload = input; + if (input && input.value !== undefined) { + payload = JSON.parse(input.value.toString()); + } + + if (!payload) return; + + const severity = (payload.severity || 'LOW').toUpperCase(); + const alarmType = payload.alarmType || payload.event_type || 'DER_ALARM'; + const normalizedSiteId = payload.siteId || extractSiteId(payload); if (severity === 'CRITICAL' || severity === 'HIGH') { console.log(`🚨 [L1 Physics] ${severity} Alarm Reported: ${alarmType} at ${normalizedSiteId}. Activating Site Lock.`); - const lockKey = `${SAFETY_LOCK_KEY}:SITE:${normalizedSiteId}`; - await redisClient.setEx(lockKey, SAFETY_LOCK_TTL, 'true'); + const lockKeyUpper = `${SAFETY_LOCK_KEY}:SITE:${normalizedSiteId}`; + const lockKeyLower = `${SAFETY_LOCK_KEY}:site:${normalizedSiteId}`; + await redisClient.setEx(lockKeyUpper, SAFETY_LOCK_TTL, 'true'); + await redisClient.setEx(lockKeyLower, SAFETY_LOCK_TTL, 'true'); } } catch (err) { console.error('❌ [L1 Physics] DER Alarm processing error:', err.message); @@ -361,7 +353,7 @@ async function handlePhysicsAlert(msg) { } // [L1-121] Fetch Site Load Data for Confidence Scoring - const alertSiteId = extractSiteId(payload.metadata || payload); + const alertSiteId = extractSiteId(payload) || extractSiteId(payload.metadata); const buildingLoadKw = safeFloat(await redisClient.get(`site:${alertSiteId}:building_load_kw`)); const siteConfig = await redisClient.hGetAll(`site:${alertSiteId}:config`) || {}; const limitKw = safeFloat(siteConfig.max_capacity_kw); @@ -804,9 +796,9 @@ module.exports = { updateLocalSafetyCache, handleDerAlarm, handlePhysicsAlert, - handleDerAlarm, calculatePhysicsMetadata, safeFloat, + safeFloatFormatted, producer, consumer, connectServices, @@ -814,8 +806,7 @@ module.exports = { reconcileLogs, start, getSyncIntervalId: () => syncIntervalId, - getLastMarketPrice: () => lastMarketPrice, - safeFloat + getLastMarketPrice: () => lastMarketPrice }; process.on('SIGTERM', async () => { diff --git a/services/01-physics-engine/physics_engine.test.js b/services/01-physics-engine/physics_engine.test.js index 7ae682904..275d432be 100644 --- a/services/01-physics-engine/physics_engine.test.js +++ b/services/01-physics-engine/physics_engine.test.js @@ -622,12 +622,12 @@ describe('L1 Physics Engine Alert Handling', () => { }); test('[L1 v10.1.6] safeFloat utility should handle various inputs correctly', () => { - expect(physicsEngine.safeFloat(0.95)).toBe("0.9500"); - expect(physicsEngine.safeFloat("0.98567")).toBe("0.9857"); - expect(physicsEngine.safeFloat(NaN)).toBe("0.0000"); - expect(physicsEngine.safeFloat(undefined)).toBe("0.0000"); - expect(physicsEngine.safeFloat(null)).toBe("0.0000"); - expect(physicsEngine.safeFloat("not-a-number", 1.0)).toBe("1.0000"); + expect(physicsEngine.safeFloatFormatted(0.95)).toBe("0.9500"); + expect(physicsEngine.safeFloatFormatted("0.98567")).toBe("0.9857"); + expect(physicsEngine.safeFloatFormatted(NaN)).toBe("0.0000"); + expect(physicsEngine.safeFloatFormatted(undefined)).toBe("0.0000"); + expect(physicsEngine.safeFloatFormatted(null)).toBe("0.0000"); + expect(physicsEngine.safeFloatFormatted("not-a-number", 1.0)).toBe("1.0000"); }); });