Skip to content

Commit 4e73abe

Browse files
committed
fix: pricing tests
1 parent 4d33dc3 commit 4e73abe

1 file changed

Lines changed: 83 additions & 6 deletions

File tree

‎scheduler/pricing_test.go‎

Lines changed: 83 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,14 @@ package scheduler
22

33
import (
44
"context"
5+
"fmt"
56
"io"
67
"log"
8+
"math"
79
"net/http"
810
"net/http/httptest"
911
"os"
12+
"strings"
1013
"testing"
1114
"time"
1215
)
@@ -179,10 +182,13 @@ func TestGetCurrentPrice_InvalidLocation(t *testing.T) {
179182
// TestStoreMarketDataXML_RefreshesMPCAndNextDayPrices validates that uploading
180183
// market data XML invalidates cached prices, merges next day data, and re-runs MPC.
181184
func TestStoreMarketDataXML_RefreshesMPCAndNextDayPrices(t *testing.T) {
182-
xmlData, err := os.ReadFile("../test_data/Energy_Prices_202609012200-202609022200.xml")
183-
if err != nil {
184-
t.Fatalf("Failed to read test data file: %v", err)
185-
}
185+
// The document must cover the current wall clock: buildMPCForecast walks
186+
// forward from time.Now() and silently drops every slot that has no price,
187+
// so a fixture pinned to a hard-coded date yields an empty forecast (and
188+
// zero MPC decisions) on any other day. Generate the prices relative to
189+
// today instead of reading a static file.
190+
dayStart := time.Now().UTC().Truncate(24 * time.Hour)
191+
xmlData := dayAheadPricesXML(dayStart, 48*time.Hour)
186192

187193
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
188194
w.Header().Set("Content-Type", "application/xml")
@@ -205,9 +211,12 @@ func TestStoreMarketDataXML_RefreshesMPCAndNextDayPrices(t *testing.T) {
205211
scheduler := NewMinerScheduler(config, logger)
206212

207213
ctx := context.Background()
208-
dateKey := "2026-09-02"
209214

210-
err = scheduler.StoreMarketDataXML(dateKey, xmlData)
215+
// The XML cache is keyed on the current date in the configured location
216+
// (see entsoe.DownloadPublicationMarketData), so the key must track today.
217+
dateKey := dayStart.Format("2006-01-02")
218+
219+
err := scheduler.StoreMarketDataXML(dateKey, xmlData)
211220
if err != nil {
212221
t.Fatalf("StoreMarketDataXML failed: %v", err)
213222
}
@@ -223,12 +232,80 @@ func TestStoreMarketDataXML_RefreshesMPCAndNextDayPrices(t *testing.T) {
223232
t.Fatal("Expected non-nil marketData")
224233
}
225234

235+
// Sanity-check that the generated document really does price the current
236+
// moment; otherwise a zero-decision result below would be ambiguous.
237+
if _, ok := marketData.LookupPriceByTime(time.Now()); !ok {
238+
t.Fatal("generated market data does not cover the current time")
239+
}
240+
226241
decisions := scheduler.GetMPCDecisions()
227242
if len(decisions) == 0 {
228243
t.Errorf("Expected MPC decisions to be generated after uploading XML, got 0")
229244
}
230245
}
231246

247+
// dayAheadPricesXML builds a minimal but valid ENTSO-E A44 day-ahead price
248+
// document covering [start, start+duration) at 15-minute resolution.
249+
//
250+
// Prices follow a simple repeating daily shape so the MPC has something with
251+
// enough spread to act on; the exact values are irrelevant to the assertions.
252+
func dayAheadPricesXML(start time.Time, duration time.Duration) []byte {
253+
const resolution = 15 * time.Minute
254+
start = start.UTC()
255+
end := start.Add(duration)
256+
points := int(duration / resolution)
257+
258+
var b strings.Builder
259+
const tsFormat = "2006-01-02T15:04Z"
260+
261+
fmt.Fprintf(&b, `<?xml version="1.0" encoding="UTF-8"?>
262+
<Publication_MarketDocument xmlns="urn:iec62325.351:tc57wg16:451-3:publicationdocument:7:3">
263+
<mRID>test-%s</mRID>
264+
<revisionNumber>1</revisionNumber>
265+
<type>A44</type>
266+
<createdDateTime>%s</createdDateTime>
267+
<period.timeInterval>
268+
<start>%s</start>
269+
<end>%s</end>
270+
</period.timeInterval>
271+
<TimeSeries>
272+
<mRID>1</mRID>
273+
<businessType>A62</businessType>
274+
<in_Domain.mRID codingScheme="A01">10YLV-1001A00074</in_Domain.mRID>
275+
<out_Domain.mRID codingScheme="A01">10YLV-1001A00074</out_Domain.mRID>
276+
<currency_Unit.name>EUR</currency_Unit.name>
277+
<price_Measure_Unit.name>MWH</price_Measure_Unit.name>
278+
<curveType>A03</curveType>
279+
<Period>
280+
<timeInterval>
281+
<start>%s</start>
282+
<end>%s</end>
283+
</timeInterval>
284+
<resolution>PT15M</resolution>
285+
`, start.Format("20060102"), start.Format(time.RFC3339),
286+
start.Format(tsFormat), end.Format(tsFormat),
287+
start.Format(tsFormat), end.Format(tsFormat))
288+
289+
for i := range points {
290+
// Cheap overnight, expensive in the evening peak — a spread the
291+
// optimizer can actually exploit.
292+
hour := float64((i*int(resolution/time.Minute)/60)%24) + 0.0
293+
price := 100.0 + 80.0*math.Sin((hour-6)/24*2*math.Pi)
294+
fmt.Fprintf(&b, ` <Point>
295+
<position>%d</position>
296+
<price.amount>%.2f</price.amount>
297+
</Point>
298+
`, i+1, price)
299+
}
300+
301+
b.WriteString(` </Period>
302+
</TimeSeries>
303+
</Publication_MarketDocument>
304+
`)
305+
306+
return []byte(b.String())
307+
}
308+
232309
// TestMarketDataDownloadHandler validates the GET /api/market-data/download endpoint
233310
func TestMarketDataDownloadHandler(t *testing.T) {
234311
xmlData, err := os.ReadFile("../test_data/Energy_Prices_202609012200-202609022200.xml")

0 commit comments

Comments
 (0)