Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

62 changes: 48 additions & 14 deletions services/04-market-gateway/BiddingOptimizer.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,33 +27,61 @@ class BiddingOptimizer {

/**
* Fetches real-time aggregated capacity from Redis.
* Supports regional aggregation (Phase 5 Forward Engineering).
* Supports regional aggregation and high-fidelity breakdowns (Phase 5/6 Forward Engineering).
* @param {string} iso - Optional ISO for regional capacity lookup
* @returns {Promise<Object>} { capacity: Decimal, fidelity: string }
* @returns {Promise<Object>} { capacity: Decimal, fidelity: string, breakdown: { ev: number, bess: number } }
*/
async getAggregatedCapacity(iso = null) {
await this.connect();

if (iso) {
try {
const regionalCapacityRaw = await this.redisClient.get('vpp:capacity:regional');
if (regionalCapacityRaw) {
const regionalCapacity = JSON.parse(regionalCapacityRaw);
// ISO Normalization: Uppercase and remove hyphens (consistent with L3 v3.3.0)
const isoKey = iso.toUpperCase().replace(/-/g, '');
const data = regionalCapacity[isoKey];
// [L3 v3.3.0 Upgrade] Prioritize high-fidelity regional breakdown
const hfKey = 'vpp:capacity:regional:high_fidelity';
const legacyKey = 'vpp:capacity:regional';

const [hfRaw, legacyRaw] = await Promise.all([
this.redisClient.get(hfKey),
this.redisClient.get(legacyKey)
]);

const isoKey = iso.toUpperCase().replace(/-/g, '');

// 1. Attempt high-fidelity lookup
if (hfRaw) {
const hfData = JSON.parse(hfRaw);
const data = hfData[isoKey];

if (data && typeof data === 'object') {
const fidelity = data.is_high_fidelity ? 'HIGH_FIDELITY' : 'STANDARD';
console.log(`[BiddingOptimizer] Using HIGH-FIDELITY regional capacity for ${isoKey}: ${data.total} kWh (EV: ${data.ev}, BESS: ${data.bess})`);
return {
capacity: new Decimal(data.total || '0'),
fidelity: fidelity,
breakdown: {
ev: data.ev || 0,
bess: data.bess || 0
}
};
}
}

// 2. Fallback to legacy regional lookup
if (legacyRaw) {
const legacyData = JSON.parse(legacyRaw);
const data = legacyData[isoKey];

if (data !== undefined) {
// Support both flat value (legacy) and nested object (v3.3.0+)
const isObject = (typeof data === 'object' && data !== null);
const capacityValue = isObject ? data.capacity : data;
const isHighFidelity = isObject ? !!data.is_high_fidelity : false;
const fidelity = isHighFidelity ? 'HIGH_FIDELITY' : 'STANDARD';

console.log(`[BiddingOptimizer] Using regional capacity for ${isoKey}: ${capacityValue} kWh (Fidelity: ${fidelity})`);
console.log(`[BiddingOptimizer] Using legacy regional capacity for ${isoKey}: ${capacityValue} kWh`);
return {
capacity: new Decimal(capacityValue || '0'),
fidelity: fidelity
fidelity: fidelity,
breakdown: { ev: capacityValue || 0, bess: 0 } // Assume EV if breakdown missing
};
}
}
Expand All @@ -65,7 +93,8 @@ class BiddingOptimizer {
const capacity = await this.redisClient.get('vpp:capacity:available');
return {
capacity: new Decimal(capacity || '0'),
fidelity: 'STANDARD'
fidelity: 'STANDARD',
breakdown: { ev: parseFloat(capacity || '0'), bess: 0 }
};
}

Expand Down Expand Up @@ -147,7 +176,7 @@ class BiddingOptimizer {
}

// 4. Fetch Capacity Data
const { capacity: pVppKw, fidelity: capacityFidelityFromRedis } = await this.getAggregatedCapacity(iso);
const { capacity: pVppKw, fidelity: capacityFidelityFromRedis, breakdown } = await this.getAggregatedCapacity(iso);
const pVppMw = pVppKw.dividedBy(1000);

const degradationCostKwh = new Decimal(process.env.DEGRADATION_COST_KWH || '0.02');
Expand Down Expand Up @@ -177,7 +206,12 @@ class BiddingOptimizer {
locks,
physics_score: physicsScore,
capacity_fidelity: capacityFidelityFromRedis, // Already normalized in getAggregatedCapacity
audit_context: auditContext,
audit_context: {
...auditContext,
ev_capacity_kw: breakdown.ev,
bess_capacity_kw: breakdown.bess,
v3_capacity_fidelity: capacityFidelityFromRedis === 'HIGH_FIDELITY'
},
pVppKw: pVppKw.toNumber(),
timestamp: new Date().toISOString()
}
Expand Down
28 changes: 28 additions & 0 deletions services/04-market-gateway/WEEKLY_REPORT_APRIL_2026.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# L4 Market Gateway Weekly Report - April 2026

## L4 Health & Dependency Report

The L4 Market Gateway (v3.7.0) remains in **Healthy** status. Over the past week, critical updates in L1, L2, and L3 have enhanced the platform's high-fidelity data capabilities, which directly impact L4's bidding precision and auditability.

* **L1 Physics Engine (v10.1.0):** Deployed **Confidence Scoring (0.0-1.0)** and **Sentinel Streak Tracking**. L4 now utilizes these scores in its `BiddingOptimizer` and `broadcastMarketPrice` logic to adjust bidding strategy and ensure ML-ready data streams for L11.
* **L2 Grid Signal (v2.4.4):** Enforced **Resource-Aware Safety** (10% BESS variance threshold). L4 regional grid locks are now synchronized with these stricter thresholds, protecting stationary storage assets from excessive cycling during market events.
* **L3 VPP Aggregator (v3.3.0):** Refactored **Regional Capacity Tracking** to provide granular **EV vs BESS breakdowns**. This is a major dependency shift; L4 must now adapt its `getAggregatedCapacity` logic to consume the new `vpp:capacity:regional:high_fidelity` structure to support resource-specific bidding in Phase 6.

## Backlog Updates

| Task ID | Description | Priority | Status |
|:---:|:---|:---:|:---|
| **L4-AUDIT-HF** | Integrate L3 v3.3.0 high-fidelity capacity breakdown (EV/BESS) into Bidding Audit logs. | **P0** | IN_PROGRESS |
| **L4-BESS-OPT** | Research Phase: Implement resource-aware bidding strategies (separate degradation costs for BESS). | **P1** | PLANNED |
| **L4-L11-READY** | Finalize LMP data export formats for L11 ML Engine demand forecasting training. | **P2** | ACTIVE |

## Engineering Execution

This week, we are finalizing the integration of high-fidelity capacity data from L3.

1. **High-Fidelity Capacity Integration:** Updated `BiddingOptimizer.js` to prioritize the `vpp:capacity:regional:high_fidelity` Redis key.
2. **Audit Enhancement:** Expanded the `audit` context in FIX message generation to include specific `ev_capacity_kw` and `bess_capacity_kw` metrics, ensuring 100% compliance with FIX-PROT-AUDIT standards for Phase 6.
3. **Test Coverage:** Updated the L4 test suite to simulate the new L3 data structures and verify the resource-aware logic.

---
*“Verify the Physics. Bid the Future.”*
45 changes: 45 additions & 0 deletions services/04-market-gateway/optimizer.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -303,4 +303,49 @@ describe('BiddingOptimizer', () => {

consoleSpy.mockRestore();
});

test('should prioritize L3 v3.3.0 high-fidelity capacity breakdown', async () => {
const hfData = JSON.stringify({
'CAISO': { total: 2000, ev: 1500, bess: 500, is_high_fidelity: true }
});

mockRedisClient.get.mockImplementation((key) => {
if (key === 'vpp:capacity:regional:high_fidelity') return Promise.resolve(hfData);
return Promise.resolve(null);
});

mockPricingService.getDayAheadForecast.mockResolvedValue([
{ location: 'LOC1', price_per_mwh: 100.00, timestamp: new Date() }
]);

const { bids, audit } = await optimizer.generateDayAheadBids('CAISO');

expect(bids[0]).toContain('38=2.00'); // Total 2000kW = 2.0MW
expect(audit.capacity_fidelity).toBe('HIGH_FIDELITY');
expect(audit.audit_context.ev_capacity_kw).toBe(1500);
expect(audit.audit_context.bess_capacity_kw).toBe(500);
expect(audit.audit_context.v3_capacity_fidelity).toBe(true);
});

test('should correctly fall back to legacy regional data if high-fidelity is missing', async () => {
const legacyData = JSON.stringify({
'PJM': 750 // 0.75 MW
});

mockRedisClient.get.mockImplementation((key) => {
if (key === 'vpp:capacity:regional:high_fidelity') return Promise.resolve(null);
if (key === 'vpp:capacity:regional') return Promise.resolve(legacyData);
return Promise.resolve(null);
});

mockPricingService.getDayAheadForecast.mockResolvedValue([
{ location: 'LOC1', price_per_mwh: 100.00, timestamp: new Date() }
]);

const { bids, audit } = await optimizer.generateDayAheadBids('PJM');

expect(bids[0]).toContain('38=0.75');
expect(audit.audit_context.ev_capacity_kw).toBe(750);
expect(audit.audit_context.bess_capacity_kw).toBe(0);
});
});