USB-Meter / Documentation / API Reference / ConsumptionMeasurement.md
Newer Older
521 lines | 12.949kb
Bogdan Timofte authored 2 weeks ago
1
# Consumption Measurement Operation
2

            
3
Măsurarea consumului pe o perioadă arbitrară, cu statistici şi predicţii bazate pe interval selectat.
4

            
5
## Responsabilități
6

            
7
- Măsurare consum liber (orice interval, nu doar sesiuni complete)
8
- Afişare statistici pe interval selectat
9
- Generare predicţii (extrapolări pe bază de trend)
10
- Tăiere curbei ("tail trimming") pentru a selecta porţiunea relevantă
11
- Salvează statisticile, NU datele măsurătorilor brute
12

            
13
## Diferenţa faţă de Charging Monitoring
14

            
15
| Aspect | Charging Monitoring | Consumption Measurement |
16
|---|---|---|
17
| Interval | Fixed (start → end charge) | Arbitrary (user-selected) |
18
| Storage | Sesiune completă + measurements | Statistics only |
19
| UI | Timeline sesiune | Graph with selection |
20
| Predicţii | Capacity learning | Energy extrapolation |
21

            
22
## Concepte
23

            
24
### Consumption = Energy over time
25

            
26
```
27
Energy (Wh) = ∫ P(t) dt
28
            = ∑ (V × A × Δt)
29

            
30
Power (W) = V × A (instantaneous)
31
Average Power (W) = Total Energy / Duration
32
```
33

            
34
## Invarianţi
35

            
36
- **MUST**: `startTime < endTime` (interval valid)
37
- **MUST**: Interval ≥ 10 secunde (minim useful)
38
- **MUST**: Interval ≤ 30 zile (limit practic pentru extrapolări)
39
- **MUST**: Statisticile trebuie salvate, măsurătorile brute se discard
40
- **MUST**: Fără măsurători individuale în persistență, doar agregări
41
- **SHOULD**: Interval tipic: 1 minut — 24 ore
42
- **MAY**: Predicţii valide doar pe ±1 interval (ex: dacă measured 1h, predict next 1h)
43

            
44
## Lifecycle
45

            
46
### 1. Selection interval (user interaction)
47

            
48
Utilizatorul deschide graficul şi selectează interval:
49

            
50
```
51
User taps start position în grafic
52
User drags to end position
53
System highlights selected region
54
User taps "Measure consumption"
55
```
56

            
57
**Rezultat:**
58
```swift
59
struct ConsumptionSelection {
60
    let startTime: Date
61
    let endTime: Date
62
    let meter: Meter
63
    let device: ChargedDevice?  // Optional, may be unknown
64
}
65
```
66

            
67
**MUST:** Times trebuie din măsurători existente (bounded)
68

            
69
### 2. Compute statistics
70

            
71
```swift
72
func computeConsumptionStats(
73
    measurements: [Measurement],
74
    from startTime: Date,
75
    to endTime: Date
76
) -> ConsumptionStats {
77
    // 1. Filter measurements în interval
78
    let filtered = measurements.filter { m in
79
        m.timestamp >= startTime && m.timestamp <= endTime
80
    }
81

            
82
    guard !filtered.isEmpty else {
83
        return .empty
84
    }
85

            
86
    // 2. Calculate energy
87
    let energy = calculateEnergy(filtered)
88

            
89
    // 3. Calculate time-based metrics
90
    let duration = endTime.timeIntervalSince(startTime)
91
    let averagePower = energy * 3600 / duration  // Convert Wh to W
92

            
93
    // 4. Power statistics
94
    let powers = filtered.map { $0.power }
95
    let peakPower = powers.max() ?? 0
96
    let minPower = powers.min() ?? 0
97
    let stdDev = calculateStdDev(powers)
98

            
99
    // 5. Voltage/Current stats
100
    let voltages = filtered.map { $0.voltage }
101
    let currents = filtered.map { $0.current }
102

            
103
    return ConsumptionStats(
104
        energy: energy,
105
        duration: duration,
106
        averagePower: averagePower,
107
        peakPower: peakPower,
108
        minPower: minPower,
109
        stdDevPower: stdDev,
110
        sampleCount: filtered.count,
111
        startVoltage: filtered.first?.voltage,
112
        endVoltage: filtered.last?.voltage,
113
        averageVoltage: voltages.average(),
114
        averageCurrent: currents.average()
115
    )
116
}
117
```
118

            
119
### 3. Generate predictions
120

            
121
```swift
122
func predictFutureConsumption(
123
    stats: ConsumptionStats,
124
    duration: TimeInterval
125
) -> ConsumptionPrediction {
126
    // Linear extrapolation
127
    let predictedEnergy = stats.averagePower * duration / 3600
128

            
129
    // Confidence bounds (±20% for stability)
130
    let confidence = 0.8
131
    let lowerBound = predictedEnergy * (1 - (1 - confidence))
132
    let upperBound = predictedEnergy * (1 + (1 - confidence))
133

            
134
    return ConsumptionPrediction(
135
        energy: predictedEnergy,
136
        lowerBound: lowerBound,
137
        upperBound: upperBound,
138
        confidence: confidence,
139
        method: "linear"
140
    )
141
}
142
```
143

            
144
**Metode:**
145
- **Linear**: `E_predicted = P_avg × t` (simple, stable)
146
- **Polynomial**: `E_predicted = a + b×t + c×t²` (for trends)
147
- **Exponential**: (rarely used, for battery degradation)
148

            
149
**MUST:** Confidence ≤ 1.0 (0-100%)
150
**SHOULD:** Linear enough for 1 interval ahead
151
**MAY:** Polynomial dacă > 2 intervals
152

            
153
### 4. Tail trimming (user action)
154

            
155
Utilizatorul poate "tăia coada" pentru a refina selecţia:
156

            
157
```
158
Initial selection: 00:00 - 10:00 (10h)
159
User drags start: 00:30 - 10:00 (9.5h)
160
User drags end: 00:30 - 09:30 (9h)
161
Result: statistics recalculate pe [00:30, 09:30]
162
```
163

            
164
**Interactiv:**
165
- Slider @ start time
166
- Slider @ end time
167
- Real-time stats update
168

            
169
**MUST:** Recalculate stats on every trim
170
**SHOULD:** Debounce updates (100ms)
171
**MUST:** Store final selection, not intermediate states
172

            
173
### 5. Save consumption record
174

            
175
**NU salvează:** Măsurătorile individuale
176

            
177
**SALVEAZĂ:**
178
```swift
179
struct ConsumptionRecord {
180
    let id: UUID
181
    let meterID: UUID
182
    let deviceID: UUID?
183
    let startTime: Date
184
    let endTime: Date
185

            
186
    // Statistics (aggregated)
187
    let totalEnergy: Double  // Wh
188
    let averagePower: Double  // W
189
    let peakPower: Double
190
    let minPower: Double
191
    let stdDevPower: Double
192
    let sampleCount: Int
193

            
194
    // Metadata
195
    let createdAt: Date
196
    let notes: String?  // User annotation
197
}
198
```
199

            
200
**NOT saved:**
201
- ❌ Individual measurements (voltage, current per sample)
202
- ❌ Timestamps of each sample
203
- ❌ Raw power values
204

            
205
**REASON:** Storage efficiency + privacy
206

            
207
## API Public
208

            
209
### Selection & Stats
210

            
211
```swift
212
// User selects interval from graph
213
func selectConsumptionInterval(
214
    start: Date,
215
    end: Date,
216
    meter: Meter
217
) -> ConsumptionStats?
218

            
219
// Refine selection via tail trimming
220
func trimConsumptionInterval(
221
    from newStart: Date,
222
    to newEnd: Date
223
) -> ConsumptionStats?
224

            
225
// Get current statistics
226
func currentStats() -> ConsumptionStats?
227
```
228

            
229
### Predictions
230

            
231
```swift
232
// Predict next interval
233
func predictConsumption(
234
    for duration: TimeInterval
235
) -> ConsumptionPrediction?
236

            
237
// Extrapolate to full day/week
238
func extrapolateToDay() -> ConsumptionPrediction?
239
func extrapolateToWeek() -> ConsumptionPrediction?
240
```
241

            
242
### Persistence
243

            
244
```swift
245
// Save aggregated stats only
246
func saveConsumptionRecord(_ record: ConsumptionRecord) -> Bool
247

            
248
// Load history
249
func loadConsumptionRecords(for meter: Meter) -> [ConsumptionRecord]
250

            
251
// Discard raw data (keep stats)
252
func discardMeasurements(before: Date) -> Int
253
```
254

            
255
## Comportamente critice
256

            
257
### Interval validation
258

            
259
```
260
User selects: 10:00 AM - 10:00 AM (zero duration)
261
⟹ Error: "Minimum 10 seconds"
262

            
263
User selects: 01-May - 01-June (31 days)
264
⟹ Warning: "Extrapolation unreliable >7 days"
265
```
266

            
267
**MUST:** duration ≥ 10s
268
**SHOULD:** Warn if duration > 7 days
269
**SHOULD:** Cap predictions to ±1 interval
270

            
271
### No tail = invalid
272

            
273
```
274
Selection: 10:00-11:00
275
User never presses "Save"
276
App terminates
277
⟹ Selection lost (no persistence of in-progress)
278
```
279

            
280
**MUST:** NU salvează interim selections
281
**SHOULD:** Keep in-memory during session
282
**MUST:** Explicit save() required
283

            
284
### Power variability
285

            
286
```
287
Measuring USB charger:
288
Min power: 0.1W (idle)
289
Max power: 18W (fast charge)
290
StdDev: high (~5W)
291

            
292
Prediction: linear avg = 8W
293
Confidence: 0.7 (high variance)
294
```
295

            
296
**SHOULD:** Calculate stdDev
297
**SHOULD:** Reflect confidence (lower if high variance)
298
**MAY:** Suggest "Power unstable, prediction unreliable"
299

            
300
### Measurement gaps
301

            
302
```
303
Recording: 10:00-11:00
304
Meter disconnect: 10:30-10:45
305
Data available: 10:00-10:30 (30min) + 10:45-11:00 (15min)
306
User selects: 10:00-11:00 (but only 45min data)
307

            
308
Stats: calculate on available data
309
Warning: "15 minutes missing"
310
```
311

            
312
**MUST:** Alert user to gaps
313
**SHOULD:** Calculate on available data only
314
**MAY:** Adjust confidence (lower if gaps > 10%)
315

            
316
## Statistici generate
317

            
318
### Basic
319

            
320
| Metric | Unit | Formula |
321
|---|---|---|
322
| Total Energy | Wh | ∑(V × A × Δt) |
323
| Duration | seconds | endTime - startTime |
324
| Average Power | W | Energy × 3600 / Duration |
325
| Peak Power | W | max(P(t)) |
326
| Min Power | W | min(P(t)) |
327

            
328
### Advanced
329

            
330
| Metric | Unit | Descriere |
331
|---|---|---|
332
| StdDev Power | W | Variabilitate în putere |
333
| Avg Voltage | V | Media tensiune |
334
| Avg Current | A | Media curent |
335
| Sample Count | # | Measurement count |
336
| Confidence | % | Reliability predicţie |
337

            
338
## Predicţii
339

            
340
### Linear extrapolation
341

            
342
```
343
Measured: P_avg = 10W over 1 hour
344
Predict next 1h: E = 10W × 1h = 10Wh
345
Bounds: ±(1-confidence)% = ±20% = [8Wh, 12Wh]
346
```
347

            
348
### Time scaling
349

            
350
```
351
Measured: 5W average over 10 minutes
352
Predict over 1 hour: 5W × 6 = 30Wh
353
Predict over 24h: 5W × 144 = 720Wh
354

            
355
But confidence decreases with time:
356
- 1×interval: 80% confidence
357
- 2×interval: 60% confidence
358
- 5×interval: 40% confidence
359
- 10+×interval: 20% confidence
360
```
361

            
362
**MUST:** Confidence degrades dengan scaling
363
**SHOULD:** Warn if confidence < 50%
364

            
365
## Testare
366

            
367
### Unit tests
368

            
369
```swift
370
test_selectConsumptionInterval_ValidDates()
371
test_selectConsumptionInterval_MinimumDuration()
372
test_trimConsumptionInterval_UpdatesStats()
373
test_computeConsumptionStats_ValidData()
374
test_computeConsumptionStats_EmptyRange()
375
test_calculateAveragePower()
376
test_predictConsumption_Linear()
377
test_predictConsumption_ConfidenceDegrades()
378
test_extrapolateToDay_WithinBounds()
379
test_recordDoesNotStoreMeasurements()
380
test_discardMeasurements_KeepsStats()
381
```
382

            
383
### Integration tests
384

            
385
- [ ] Select 1h interval → get stats
386
- [ ] Trim tail → stats recalculate
387
- [ ] Predict next 1h → confidence 80%
388
- [ ] Predict next 24h → confidence drops to 40%
389
- [ ] Save record → measurements discarded, stats kept
390
- [ ] Load record → only stats available
391
- [ ] Measurement gap detected → warning shown
392
- [ ] High power variance → confidence reduced
393

            
394
## Edge cases
395

            
396
### Very short interval
397

            
398
```
399
User selects: 10:00:00 - 10:00:05 (5 seconds)
400
⟹ Error: below 10s minimum
401
```
402

            
403
**MUST:** Reject
404

            
405
### Very long interval
406

            
407
```
408
User selects: 01-Jan - 31-Dec (365 days)
409
⟹ Warning: "Extrapolation unreliable beyond 7 days"
410
Confidence: 20%
411
```
412

            
413
**SHOULD:** Accept but warn
414
**SHOULD:** Reduce confidence
415

            
416
### Single measurement
417

            
418
```
419
Interval: 10:00:00 - 10:00:01
420
Data points: 1 (only start)
421
Energy: 0 (insufficient interval)
422
⟹ Error: "Need minimum 10 measurements"
423
```
424

            
425
**MUST:** Require ≥ 10 samples
426

            
427
### No voltage/current data
428

            
429
```
430
Meter type: Unknown or not reporting
431
Power available: yes
432
Voltage/Current: null
433

            
434
Stats: calculate energy from power only
435
Avg Voltage: null
436
Avg Current: null
437
```
438

            
439
**SHOULD:** Handle gracefully
440
**MAY:** Show "Limited data available"
441

            
442
## Storage model
443

            
444
### What IS saved
445

            
446
```json
447
{
448
  "id": "UUID",
449
  "meterID": "UUID",
450
  "startTime": "2026-05-23T10:00:00Z",
451
  "endTime": "2026-05-23T11:00:00Z",
452
  "totalEnergy": 12.5,
453
  "averagePower": 12.5,
454
  "peakPower": 18.0,
455
  "minPower": 1.2,
456
  "stdDevPower": 4.5,
457
  "sampleCount": 3600,
458
  "createdAt": "2026-05-23T11:02:00Z"
459
}
460
```
461

            
462
### What is NOT saved
463

            
464
```
465
❌ individual measurements
466
❌ voltage samples
467
❌ current samples
468
❌ power curve
469
❌ battery levels
470
❌ meter metadata
471
```
472

            
473
## Dependenţe
474

            
475
- [Charging Monitoring](./ChargingMonitoring.md): access to meter data
476
- [Charge Curve Storage](./ChargeCurveStorage.md): query full measurements
477
- Core Data: ConsumptionRecord entity
478
- UI: Graph view with interval selection
479

            
480
## UI Components
481

            
482
### Graph with selection
483

            
484
```
485
┌─────────────────────────────────┐
486
│  Power (W)                      │
487
│  20 ├──────┐                    │
488
│     │ ████ │  ← user selection  │
489
│  10 ├──████└────┐               │
490
│     │          │               │
491
│   0 └──────────┴───────────────┘
492
│     10:00   10:30    11:00
493
│     ↑start           ↑end
494
│     (draggable)      (draggable)
495
└─────────────────────────────────┘
496
```
497

            
498
### Stats display
499

            
500
```
501
Selected Interval: 10:00 — 10:30 (30 min)
502
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
503
Energy:         6.3 Wh
504
Average Power:  12.6 W
505
Peak Power:     18.0 W
506
Min Power:      1.2 W
507
Variance:       ±4.5 W
508
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
509
Prediction (next 30 min):
510
  Energy:       6.3 Wh (confidence: 80%)
511
  Range:        5.0 — 7.6 Wh
512
```
513

            
514
## Notes
515

            
516
- **Not a charge session**: No device pairing, no capacity learning
517
- **Independent operation**: Coexists with ChargeInsights
518
- **Low storage footprint**: Only aggregates, not raw curves
519
- **Privacy-friendly**: Discards detailed measurements
520
- **Real-time**: Updates on interval selection/trimming
521
- Related: [Charging Monitoring](./ChargingMonitoring.md) for full sessions