@@ -16,8 +16,9 @@ const (
16
16
headerFields = 9
17
17
headerDateFormat = "02/01/2006" // 31/12/2021
18
18
dataDateFormat = "2006-01-02T15:04:05-07:00" // 2021-12-31T00:30:00+01:00
19
- steppingHalfHour = 30 * time .Minute
20
- steppingHour = time .Hour
19
+ noSteppingValue = - 1
20
+ dataStartLine = 4
21
+ defaultStepping = 30 * time .Minute
21
22
)
22
23
23
24
type CSVHeader struct {
@@ -30,6 +31,7 @@ type CSVHeader struct {
30
31
type point struct {
31
32
Time time.Time
32
33
Value float64 // kW
34
+ Conso float64 // kWh
33
35
}
34
36
35
37
func parseFile (path string ) (header CSVHeader , data []point , err error ) {
@@ -49,24 +51,10 @@ func parseFile(path string) (header CSVHeader, data []point, err error) {
49
51
return
50
52
}
51
53
// Parse data
52
- if data , err = parseData (cr ); err != nil {
54
+ if data , err = parseData (cr , header . Step ); err != nil {
53
55
err = fmt .Errorf ("failed to parse data: %w" , err )
54
56
return
55
57
}
56
- // Try to guess stepping if missing (saw in the wild)
57
- if header .Step == - 1 {
58
- for index , point := range data {
59
- if point .Time .Minute () == 30 {
60
- header .Step = steppingHalfHour
61
- break
62
- }
63
- if index > 1 {
64
- // 2 records processed and 30min step not encountered, guessing it is 60min step
65
- header .Step = steppingHour
66
- break
67
- }
68
- }
69
- }
70
58
return
71
59
}
72
60
@@ -101,25 +89,21 @@ func parseHeader(cr *csv.Reader) (header CSVHeader, err error) {
101
89
return
102
90
}
103
91
step_str := records [8 ]
104
- if step_str == "" {
105
- // step empty
106
- header .Step = - 1
107
- } else {
92
+ if step_str != "" {
108
93
var step int
109
94
if step , err = strconv .Atoi (step_str ); err != nil {
110
95
err = fmt .Errorf ("non integer stepping found: %s" , err )
111
96
return
112
- } else if header .Step != 30 && header .Step != 60 {
113
- err = fmt .Errorf ("unexpected stepping found: %d" , header .Step )
114
- return
115
- } else {
116
- header .Step = time .Duration (step ) * time .Minute
117
97
}
98
+ header .Step = time .Duration (step ) * time .Minute
99
+ } else {
100
+ // step empty
101
+ header .Step = noSteppingValue
118
102
}
119
103
return
120
104
}
121
105
122
- func parseData (cr * csv.Reader ) (data []point , err error ) {
106
+ func parseData (cr * csv.Reader , stepping time. Duration ) (data []point , err error ) {
123
107
// nb of records changes for data
124
108
cr .FieldsPerRecord = 2
125
109
// remove data header
@@ -134,9 +118,14 @@ func parseData(cr *csv.Reader) (data []point, err error) {
134
118
line int
135
119
recordTime time.Time
136
120
recordValue int
121
+ computedkWh float64
137
122
)
138
- data = make ([]point , 0 , 365 * 24 * 2 ) // most people will analyse a full year (make more sense for tempo)
139
- for line = 4 ; ; line ++ {
123
+ if stepping == noSteppingValue {
124
+ data = make ([]point , 0 , 365 * 24 * (time .Hour / defaultStepping ))
125
+ } else {
126
+ data = make ([]point , 0 , 365 * 24 * (time .Hour / stepping ))
127
+ }
128
+ for line = dataStartLine ; ; line ++ {
140
129
// read line
141
130
records , err = cr .Read ()
142
131
if err != nil {
@@ -156,19 +145,34 @@ func parseData(cr *csv.Reader) (data []point, err error) {
156
145
err = fmt .Errorf ("failed to parse record value: %w" , err )
157
146
break
158
147
}
159
- // checks
160
- if recordTime .Minute () != 30 && recordTime .Minute () != 0 {
161
- err = fmt .Errorf ("minutes should always be 00 or 30: %v" , recordTime )
162
- break
163
- }
148
+ // check
164
149
if recordTime .Second () != 0 {
165
150
err = fmt .Errorf ("seconds should always be 00: %v" , recordTime )
166
151
break
167
152
}
153
+ // Determine stepping if needed
154
+ if stepping == noSteppingValue {
155
+ if line > dataStartLine {
156
+ // get previous point and compute stepping
157
+ prevPoint := data [len (data )- 1 ]
158
+ stepping := recordTime .Sub (prevPoint .Time )
159
+ computedkWh = float64 (recordValue ) / 1000 / float64 (time .Hour / stepping )
160
+ if line == dataStartLine + 1 {
161
+ // compute the first point kWh using the same stepping
162
+ firstPoint := data [len (data )- 1 ]
163
+ firstPoint .Conso = firstPoint .Value / 1000 / float64 (time .Hour / stepping )
164
+ data [len (data )- 1 ] = firstPoint
165
+ }
166
+ }
167
+ // else first point, can not compute stepping without a previous point, waiting for second point to retro compute first point
168
+ } else {
169
+ computedkWh = float64 (recordValue ) / 1000 / float64 (time .Hour / stepping )
170
+ }
168
171
// save value
169
172
data = append (data , point {
170
173
Time : recordTime .In (frLocation ), // make sure every date time in this program is in the same loc
171
174
Value : float64 (recordValue ) / 1000 , // convert W to kW
175
+ Conso : computedkWh ,
172
176
})
173
177
}
174
178
if errors .Is (err , io .EOF ) {
0 commit comments