Optimizer: fix timestamp generation (#28677)
This commit is contained in:
parent
764aa45d8c
commit
bb07ec0d4d
4 changed files with 45 additions and 23 deletions
|
|
@ -218,9 +218,9 @@ export default defineComponent({
|
|||
type: Array as PropType<BatteryDetail[]>,
|
||||
required: true,
|
||||
},
|
||||
timestamp: {
|
||||
type: String,
|
||||
default: "",
|
||||
timestamps: {
|
||||
type: Array as PropType<string[]>,
|
||||
default: () => [],
|
||||
},
|
||||
currency: {
|
||||
type: String as PropType<CURRENCY>,
|
||||
|
|
@ -262,13 +262,16 @@ export default defineComponent({
|
|||
return (seconds / 3600).toFixed(2);
|
||||
},
|
||||
formatHour(index: number): string {
|
||||
// Show label only every 4th slot (every hour for 15-minute slots)
|
||||
if (index % 4 !== 0) {
|
||||
if (this.timestamps.length <= index) {
|
||||
return "";
|
||||
}
|
||||
const startTime = new Date(this.timestamp);
|
||||
const currentTime = new Date(startTime.getTime() + index * 15 * 60 * 1000); // Add 15-minute intervals
|
||||
return currentTime.getHours().toString();
|
||||
// Show label on fresh hour or on first quarter hour for slot 0
|
||||
const ts = new Date(this.timestamps[index]);
|
||||
const minutes = ts.getMinutes();
|
||||
if (minutes === 0 || (index === 0 && minutes < 15)) {
|
||||
return ts.getHours().toString();
|
||||
}
|
||||
return "";
|
||||
},
|
||||
getBatteryTitle(index: number): string {
|
||||
const detail = this.batteryDetails[index];
|
||||
|
|
|
|||
|
|
@ -82,7 +82,7 @@
|
|||
<TimeSeriesDataTable
|
||||
:evopt="evopt"
|
||||
:battery-details="evopt.details.batteryDetails"
|
||||
:timestamp="evopt.details.timestamp[0]"
|
||||
:timestamps="evopt.details.timestamp"
|
||||
:currency="currency"
|
||||
:battery-colors="batteryColors"
|
||||
:dimmed-battery-colors="dimmedBatteryColors"
|
||||
|
|
|
|||
|
|
@ -144,7 +144,8 @@ func (site *Site) optimizerUpdate(battery []types.Measurement) error {
|
|||
return fmt.Errorf("not enough slots for optimization: %d (grid=%d, feedIn=%d, solar=%d)", minLen, len(grid), len(feedIn), len(solar))
|
||||
}
|
||||
|
||||
dt := timeSteps(minLen)
|
||||
now := time.Now()
|
||||
dt := timeSteps(minLen, now)
|
||||
firstSlotDuration := time.Duration(dt[0]) * time.Second
|
||||
|
||||
site.log.DEBUG.Printf("optimizer: optimizing %d slots until %v: grid=%d, feedIn=%d, solar=%d, first slot: %v",
|
||||
|
|
@ -190,7 +191,7 @@ func (site *Site) optimizerUpdate(battery []types.Measurement) error {
|
|||
pa := lo.Min(req.TimeSeries.PN) * eta * 0.99
|
||||
|
||||
details := requestDetails{
|
||||
Timestamps: asTimestamps(dt),
|
||||
Timestamps: asTimestamps(dt, now),
|
||||
}
|
||||
|
||||
if site.circuit != nil {
|
||||
|
|
@ -588,10 +589,6 @@ func solarRatesToEnergy(rr api.Rates) (api.Rates, error) {
|
|||
return res, nil
|
||||
}
|
||||
|
||||
func endOfHour(ts time.Time) time.Time {
|
||||
return ts.Truncate(time.Hour).Add(time.Hour)
|
||||
}
|
||||
|
||||
func currentRates(tariff api.Tariff) api.Rates {
|
||||
if tariff == nil {
|
||||
return nil
|
||||
|
|
@ -609,12 +606,11 @@ func currentRates(tariff api.Tariff) api.Rates {
|
|||
})
|
||||
}
|
||||
|
||||
func timeSteps(minLen int) []int {
|
||||
func timeSteps(minLen int, now time.Time) []int {
|
||||
res := make([]int, 0, minLen)
|
||||
|
||||
bos := time.Now().Truncate(tariff.SlotDuration)
|
||||
eos := bos.Add(tariff.SlotDuration)
|
||||
if d := time.Until(eos); d > time.Second && d < tariff.SlotDuration {
|
||||
eos := now.Truncate(tariff.SlotDuration).Add(tariff.SlotDuration)
|
||||
if d := eos.Sub(now); d > time.Second && d < tariff.SlotDuration {
|
||||
res = append(res, int(d.Seconds()))
|
||||
}
|
||||
|
||||
|
|
@ -625,13 +621,16 @@ func timeSteps(minLen int) []int {
|
|||
return res
|
||||
}
|
||||
|
||||
func asTimestamps(dt []int) []time.Time {
|
||||
func asTimestamps(dt []int, now time.Time) []time.Time {
|
||||
res := make([]time.Time, 0, len(dt))
|
||||
eoh := endOfHour(time.Now())
|
||||
res = append(res, eoh.Add(-time.Duration(dt[0])*time.Second))
|
||||
|
||||
eos := now.Truncate(tariff.SlotDuration).Add(tariff.SlotDuration)
|
||||
res = append(res, eos.Add(-time.Duration(dt[0])*time.Second))
|
||||
|
||||
for i := range len(dt) - 1 {
|
||||
res = append(res, eoh.Add(time.Duration(dt[i+1])*time.Second))
|
||||
res = append(res, res[i].Add(time.Duration(dt[i])*time.Second))
|
||||
}
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -111,6 +111,26 @@ func TestLoadpointProfile(t *testing.T) {
|
|||
require.Equal(t, []float64{250, 250, 250, 250, 250, 250, 250, 50}, loadpointProfile(lp, 8))
|
||||
}
|
||||
|
||||
func TestAsTimestamps(t *testing.T) {
|
||||
// now is 10 minutes into a 15-minute slot
|
||||
now := time.Date(2025, 1, 1, 12, 10, 0, 0, time.UTC)
|
||||
|
||||
// dt[0]=300 means first event is 300s (5min) before end of current slot
|
||||
// dt[1..] just mark subsequent slot boundaries
|
||||
dt := []int{60 * 5, 60 * 15, 60 * 15}
|
||||
|
||||
got := asTimestamps(dt, now)
|
||||
|
||||
// current slot: 12:00–12:15
|
||||
// first timestamp: 12:15 - 5min = 12:10
|
||||
// subsequent: 12:15, 12:30
|
||||
assert.Equal(t, []time.Time{
|
||||
time.Date(2025, 1, 1, 12, 10, 0, 0, time.UTC),
|
||||
time.Date(2025, 1, 1, 12, 15, 0, 0, time.UTC),
|
||||
time.Date(2025, 1, 1, 12, 30, 0, 0, time.UTC),
|
||||
}, got)
|
||||
}
|
||||
|
||||
func TestBatteryForecastTotals(t *testing.T) {
|
||||
site := new(Site)
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue