@@ -82,16 +82,28 @@ type IntegratedData struct {
8282 evdcChargePower float64
8383 loadPower float64
8484 batterySoc float64
85+ timestamp time.Time
86+ sampleCount int // Number of samples integrated
8587}
8688
87- // IntegrateSamples computes integrated power values from collected samples.
88- func (d * DataSamples ) IntegrateSamples (pollInterval time.Duration ) IntegratedData {
89+ // IntegrateSamples computes integrated power values from collected samples up to the specified cutoff time.
90+ // Only samples with timestamp <= cutoffTime are integrated.
91+ // The cutoffTime represents the end of the integration period and is used as the result timestamp.
92+ // Samples are preserved and must be cleared explicitly using ClearBefore() after successful processing.
93+ func (d * DataSamples ) IntegrateSamples (pollInterval time.Duration , cutoffTime time.Time ) IntegratedData {
8994 d .mu .Lock ()
9095 defer d .mu .Unlock ()
9196
9297 var result IntegratedData
98+ result .timestamp = cutoffTime
9399
94100 for _ , sample := range d .samples {
101+ // Only integrate samples that belong to this period
102+ if sample .ts .After (cutoffTime ) {
103+ continue
104+ }
105+
106+ result .sampleCount ++
95107 energyKWh := pollInterval .Seconds () / 3600.0 // Convert to hours
96108
97109 result .pvTotalPower += sample .pvPower * energyKWh
@@ -121,10 +133,25 @@ func (d *DataSamples) IntegrateSamples(pollInterval time.Duration) IntegratedDat
121133 result .loadPower = result .pvTotalPower + result .batteryDischargePower + result .gridImportPower -
122134 result .batteryChargePower - result .gridExportPower - result .evdcChargePower
123135
124- d .samples = d .samples [:0 ]
125136 return result
126137}
127138
139+ // ClearBefore removes all samples with timestamp <= cutoffTime from the collection.
140+ // Should only be called after samples have been successfully processed for that period.
141+ func (d * DataSamples ) ClearBefore (cutoffTime time.Time ) {
142+ d .mu .Lock ()
143+ defer d .mu .Unlock ()
144+
145+ // Filter out samples that should be cleared
146+ filteredSamples := make ([]DataSample , 0 , len (d .samples ))
147+ for _ , sample := range d .samples {
148+ if sample .ts .After (cutoffTime ) {
149+ filteredSamples = append (filteredSamples , sample )
150+ }
151+ }
152+ d .samples = filteredSamples
153+ }
154+
128155// IsEmpty returns true if there are no samples collected.
129156func (d * DataSamples ) IsEmpty () bool {
130157 d .mu .Lock ()
@@ -169,15 +196,27 @@ func (s *MinerScheduler) runDataPoll(samples *DataSamples) error {
169196}
170197
171198func (s * MinerScheduler ) runDataIntegration (samples * DataSamples , pollInterval time.Duration , dataDB * sql.DB , deviceID int , dryRun bool ) error {
172- if samples .IsEmpty () {
173- s .logger .Printf ("Data integration: no samples collected in period" )
199+ // Calculate the period boundary timestamp (end of current integration period)
200+ // This ensures samples are grouped by their integration period
201+ config := s .GetConfig ()
202+ now := time .Now ()
203+ periodEndTime := now .Truncate (config .PVIntegrationPeriod )
204+ if periodEndTime .Before (now .Add (- config .PVIntegrationPeriod )) {
205+ periodEndTime = periodEndTime .Add (config .PVIntegrationPeriod )
206+ }
207+
208+ // Integrate only samples up to the period boundary
209+ data := samples .IntegrateSamples (pollInterval , periodEndTime )
210+
211+ if data .sampleCount == 0 {
212+ s .logger .Printf ("Data integration: no samples collected in period ending at %s" , periodEndTime .Format (time .RFC3339 ))
174213 return nil
175214 }
176215
177- data := samples .IntegrateSamples (pollInterval )
178- timestamp := time .Now ()
216+ timestamp := data .timestamp
179217
180218 if dataDB == nil {
219+ samples .ClearBefore (periodEndTime )
181220 return nil
182221 }
183222
@@ -193,7 +232,6 @@ func (s *MinerScheduler) runDataIntegration(samples *DataSamples, pollInterval t
193232 }
194233
195234 // Calculate costs using current energy prices
196- config := s .GetConfig ()
197235
198236 // Get current spot price for cost calculations
199237 var gridImportCost , gridExportCost float64
@@ -216,7 +254,8 @@ func (s *MinerScheduler) runDataIntegration(samples *DataSamples, pollInterval t
216254
217255 if dryRun {
218256 // DRY-RUN MODE: Log the action without saving to database
219- s .logger .Printf ("Data integration [DRY-RUN]: would save metrics for device_id=%d at %s" , deviceID , timestamp .Format (time .RFC3339 ))
257+ s .logger .Printf ("Data integration [DRY-RUN]: would save metrics for device_id=%d at %s (samples: %d)" ,
258+ deviceID , timestamp .Format (time .RFC3339 ), data .sampleCount )
220259 s .logger .Printf (" PV: %.3f kWh, Grid Import: %.3f kWh (%.3f), Grid Export: %.3f kWh (%.3f)" ,
221260 data .pvTotalPower , data .gridImportPower , gridImportCost , data .gridExportPower , gridExportCost )
222261 s .logger .Printf (" Battery Charge: %.3f kWh, Battery Discharge: %.3f kWh, SOC: %.1f%%" ,
@@ -228,6 +267,7 @@ func (s *MinerScheduler) runDataIntegration(samples *DataSamples, pollInterval t
228267 if weatherSymbol != nil {
229268 s .logger .Printf (" Weather: %s" , * weatherSymbol )
230269 }
270+ samples .ClearBefore (periodEndTime )
231271 } else {
232272 // Insert comprehensive energy flow data
233273 _ , err = dataDB .Exec (
@@ -248,10 +288,14 @@ func (s *MinerScheduler) runDataIntegration(samples *DataSamples, pollInterval t
248288 )
249289 if err != nil {
250290 s .logger .Printf ("Data integration: failed to insert metrics: %v" , err )
251- return nil
291+ return err
252292 }
253293
254- s .logger .Printf ("Data integration: saved metrics for device_id=%d at %s" , deviceID , timestamp .Format (time .RFC3339 ))
294+ // Only clear samples for this period after successful DB insertion
295+ samples .ClearBefore (periodEndTime )
296+
297+ s .logger .Printf ("Data integration: saved metrics for device_id=%d at %s (samples: %d)" ,
298+ deviceID , timestamp .Format (time .RFC3339 ), data .sampleCount )
255299 s .logger .Printf (" PV: %.3f kWh, Grid Import: %.3f kWh (%.3f), Grid Export: %.3f kWh (%.3f)" ,
256300 data .pvTotalPower , data .gridImportPower , gridImportCost , data .gridExportPower , gridExportCost )
257301 s .logger .Printf (" Battery Charge: %.3f kWh, Battery Discharge: %.3f kWh, SOC: %.1f%%" ,
0 commit comments