USB-Meter / Documentation / API Reference / ConsumptionMeasurement.md
1 contributor
521 lines | 12.949kb

Consumption Measurement Operation

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

Responsabilități

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

Diferenţa faţă de Charging Monitoring

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

Concepte

Consumption = Energy over time

Energy (Wh) = ∫ P(t) dt
            = ∑ (V × A × Δt)

Power (W) = V × A (instantaneous)
Average Power (W) = Total Energy / Duration

Invarianţi

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

Lifecycle

1. Selection interval (user interaction)

Utilizatorul deschide graficul şi selectează interval:

User taps start position în grafic
User drags to end position
System highlights selected region
User taps "Measure consumption"

Rezultat: swift struct ConsumptionSelection { let startTime: Date let endTime: Date let meter: Meter let device: ChargedDevice? // Optional, may be unknown }

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

2. Compute statistics

func computeConsumptionStats(
    measurements: [Measurement],
    from startTime: Date,
    to endTime: Date
) -> ConsumptionStats {
    // 1. Filter measurements în interval
    let filtered = measurements.filter { m in
        m.timestamp >= startTime && m.timestamp <= endTime
    }
    
    guard !filtered.isEmpty else {
        return .empty
    }
    
    // 2. Calculate energy
    let energy = calculateEnergy(filtered)
    
    // 3. Calculate time-based metrics
    let duration = endTime.timeIntervalSince(startTime)
    let averagePower = energy * 3600 / duration  // Convert Wh to W
    
    // 4. Power statistics
    let powers = filtered.map { $0.power }
    let peakPower = powers.max() ?? 0
    let minPower = powers.min() ?? 0
    let stdDev = calculateStdDev(powers)
    
    // 5. Voltage/Current stats
    let voltages = filtered.map { $0.voltage }
    let currents = filtered.map { $0.current }
    
    return ConsumptionStats(
        energy: energy,
        duration: duration,
        averagePower: averagePower,
        peakPower: peakPower,
        minPower: minPower,
        stdDevPower: stdDev,
        sampleCount: filtered.count,
        startVoltage: filtered.first?.voltage,
        endVoltage: filtered.last?.voltage,
        averageVoltage: voltages.average(),
        averageCurrent: currents.average()
    )
}

3. Generate predictions

func predictFutureConsumption(
    stats: ConsumptionStats,
    duration: TimeInterval
) -> ConsumptionPrediction {
    // Linear extrapolation
    let predictedEnergy = stats.averagePower * duration / 3600
    
    // Confidence bounds (±20% for stability)
    let confidence = 0.8
    let lowerBound = predictedEnergy * (1 - (1 - confidence))
    let upperBound = predictedEnergy * (1 + (1 - confidence))
    
    return ConsumptionPrediction(
        energy: predictedEnergy,
        lowerBound: lowerBound,
        upperBound: upperBound,
        confidence: confidence,
        method: "linear"
    )
}

Metode: - Linear: E_predicted = P_avg × t (simple, stable) - Polynomial: E_predicted = a + b×t + c×t² (for trends) - Exponential: (rarely used, for battery degradation)

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

4. Tail trimming (user action)

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

Initial selection: 00:00 - 10:00 (10h)
User drags start: 00:30 - 10:00 (9.5h)
User drags end: 00:30 - 09:30 (9h)
Result: statistics recalculate pe [00:30, 09:30]

Interactiv: - Slider @ start time - Slider @ end time - Real-time stats update

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

5. Save consumption record

NU salvează: Măsurătorile individuale

SALVEAZĂ: ```swift struct ConsumptionRecord { let id: UUID let meterID: UUID let deviceID: UUID? let startTime: Date let endTime: Date

// Statistics (aggregated)
let totalEnergy: Double  // Wh
let averagePower: Double  // W
let peakPower: Double
let minPower: Double
let stdDevPower: Double
let sampleCount: Int

// Metadata
let createdAt: Date
let notes: String?  // User annotation

} ```

NOT saved: - ❌ Individual measurements (voltage, current per sample) - ❌ Timestamps of each sample - ❌ Raw power values

REASON: Storage efficiency + privacy

API Public

Selection & Stats

// User selects interval from graph
func selectConsumptionInterval(
    start: Date,
    end: Date,
    meter: Meter
) -> ConsumptionStats?

// Refine selection via tail trimming
func trimConsumptionInterval(
    from newStart: Date,
    to newEnd: Date
) -> ConsumptionStats?

// Get current statistics
func currentStats() -> ConsumptionStats?

Predictions

// Predict next interval
func predictConsumption(
    for duration: TimeInterval
) -> ConsumptionPrediction?

// Extrapolate to full day/week
func extrapolateToDay() -> ConsumptionPrediction?
func extrapolateToWeek() -> ConsumptionPrediction?

Persistence

// Save aggregated stats only
func saveConsumptionRecord(_ record: ConsumptionRecord) -> Bool

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

// Discard raw data (keep stats)
func discardMeasurements(before: Date) -> Int

Comportamente critice

Interval validation

User selects: 10:00 AM - 10:00 AM (zero duration)
⟹ Error: "Minimum 10 seconds"

User selects: 01-May - 01-June (31 days)
⟹ Warning: "Extrapolation unreliable >7 days"

MUST: duration ≥ 10s SHOULD: Warn if duration > 7 days SHOULD: Cap predictions to ±1 interval

No tail = invalid

Selection: 10:00-11:00
User never presses "Save"
App terminates
⟹ Selection lost (no persistence of in-progress)

MUST: NU salvează interim selections SHOULD: Keep in-memory during session MUST: Explicit save() required

Power variability

Measuring USB charger:
Min power: 0.1W (idle)
Max power: 18W (fast charge)
StdDev: high (~5W)

Prediction: linear avg = 8W
Confidence: 0.7 (high variance)

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

Measurement gaps

Recording: 10:00-11:00
Meter disconnect: 10:30-10:45
Data available: 10:00-10:30 (30min) + 10:45-11:00 (15min)
User selects: 10:00-11:00 (but only 45min data)

Stats: calculate on available data
Warning: "15 minutes missing"

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

Statistici generate

Basic

Metric Unit Formula
Total Energy Wh ∑(V × A × Δt)
Duration seconds endTime - startTime
Average Power W Energy × 3600 / Duration
Peak Power W max(P(t))
Min Power W min(P(t))

Advanced

Metric Unit Descriere
StdDev Power W Variabilitate în putere
Avg Voltage V Media tensiune
Avg Current A Media curent
Sample Count # Measurement count
Confidence % Reliability predicţie

Predicţii

Linear extrapolation

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

Time scaling

Measured: 5W average over 10 minutes
Predict over 1 hour: 5W × 6 = 30Wh
Predict over 24h: 5W × 144 = 720Wh

But confidence decreases with time:
- 1×interval: 80% confidence
- 2×interval: 60% confidence
- 5×interval: 40% confidence
- 10+×interval: 20% confidence

MUST: Confidence degrades dengan scaling SHOULD: Warn if confidence < 50%

Testare

Unit tests

test_selectConsumptionInterval_ValidDates()
test_selectConsumptionInterval_MinimumDuration()
test_trimConsumptionInterval_UpdatesStats()
test_computeConsumptionStats_ValidData()
test_computeConsumptionStats_EmptyRange()
test_calculateAveragePower()
test_predictConsumption_Linear()
test_predictConsumption_ConfidenceDegrades()
test_extrapolateToDay_WithinBounds()
test_recordDoesNotStoreMeasurements()
test_discardMeasurements_KeepsStats()

Integration tests

  • [ ] Select 1h interval → get stats
  • [ ] Trim tail → stats recalculate
  • [ ] Predict next 1h → confidence 80%
  • [ ] Predict next 24h → confidence drops to 40%
  • [ ] Save record → measurements discarded, stats kept
  • [ ] Load record → only stats available
  • [ ] Measurement gap detected → warning shown
  • [ ] High power variance → confidence reduced

Edge cases

Very short interval

User selects: 10:00:00 - 10:00:05 (5 seconds)
⟹ Error: below 10s minimum

MUST: Reject

Very long interval

User selects: 01-Jan - 31-Dec (365 days)
⟹ Warning: "Extrapolation unreliable beyond 7 days"
Confidence: 20%

SHOULD: Accept but warn SHOULD: Reduce confidence

Single measurement

Interval: 10:00:00 - 10:00:01
Data points: 1 (only start)
Energy: 0 (insufficient interval)
⟹ Error: "Need minimum 10 measurements"

MUST: Require ≥ 10 samples

No voltage/current data

Meter type: Unknown or not reporting
Power available: yes
Voltage/Current: null

Stats: calculate energy from power only
Avg Voltage: null
Avg Current: null

SHOULD: Handle gracefully MAY: Show "Limited data available"

Storage model

What IS saved

{
  "id": "UUID",
  "meterID": "UUID",
  "startTime": "2026-05-23T10:00:00Z",
  "endTime": "2026-05-23T11:00:00Z",
  "totalEnergy": 12.5,
  "averagePower": 12.5,
  "peakPower": 18.0,
  "minPower": 1.2,
  "stdDevPower": 4.5,
  "sampleCount": 3600,
  "createdAt": "2026-05-23T11:02:00Z"
}

What is NOT saved

❌ individual measurements
❌ voltage samples
❌ current samples  
❌ power curve
❌ battery levels
❌ meter metadata

Dependenţe

UI Components

Graph with selection

┌─────────────────────────────────┐
│  Power (W)                      │
│  20 ├──────┐                    │
│     │ ████ │  ← user selection  │
│  10 ├──████└────┐               │
│     │          │               │
│   0 └──────────┴───────────────┘
│     10:00   10:30    11:00
│     ↑start           ↑end
│     (draggable)      (draggable)
└─────────────────────────────────┘

Stats display

Selected Interval: 10:00 — 10:30 (30 min)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Energy:         6.3 Wh
Average Power:  12.6 W
Peak Power:     18.0 W
Min Power:      1.2 W
Variance:       ±4.5 W
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Prediction (next 30 min):
  Energy:       6.3 Wh (confidence: 80%)
  Range:        5.0 — 7.6 Wh

Notes

  • Not a charge session: No device pairing, no capacity learning
  • Independent operation: Coexists with ChargeInsights
  • Low storage footprint: Only aggregates, not raw curves
  • Privacy-friendly: Discards detailed measurements
  • Real-time: Updates on interval selection/trimming
  • Related: Charging Monitoring for full sessions