# Charge Curve Isolation Operation

Izolarea porţiunii relevante a curbei de încărcare: eliminarea zgomotului pre/post-charging.

## Responsabilități

- Detectarea punctului de start real al încărcării (0 watts baseline)
- Detectarea punctului de end real al încărcării (100% sau limit)
- Eliminarea zgomotului (ambient AC, meter offset)
- Determinarea timpului efectiv de charging

## Problema

**Raw data from meter:**
```
00:00 - 0.0W (pre-charge noise)
00:30 - 0.5W (background draw)
01:00 - 15.0W ← REAL START
01:30 - 18.5W
02:00 - 16.2W
...
05:00 - 2.5W (charging optimizer active)
05:30 - 1.0W
06:00 - 0.2W ← REAL END
06:15 - 0.0W (post-charge, device removed)
```

**Sarcini:**
1. Find real start (@ 01:00)
2. Find real end (@ 06:00)
3. Extract interval [01:00, 06:00]
4. Ignore pre/post noise

## Invarianţi

- **MUST**: `real_start_time > measurement_start_time` (or equal)
- **MUST**: `real_end_time <= measurement_end_time` (or equal)
- **MUST**: `real_start < real_end`
- **MUST**: Threshold determination trebuie consistent (nu random)
- **SHOULD**: Isolated curve ≥ 90% din total recorded measurements
- **MAY**: Isolated curve poate fi < 10% din total (very short charge)

## Detection algorithm

### Power threshold method

```swift
func findChargeStart(measurements: [Measurement]) -> Int? {
    let powerThreshold = 5.0 // Watts
    let minConsecutive = 3 // Measure must sustain > threshold for 3 samples
    
    var sustainCount = 0
    for (i, measurement) in measurements.enumerated() {
        if measurement.power > powerThreshold {
            sustainCount += 1
            if sustainCount >= minConsecutive {
                return i - minConsecutive + 1
            }
        } else {
            sustainCount = 0
        }
    }
    return nil
}

func findChargeEnd(measurements: [Measurement]) -> Int? {
    let powerThreshold = 1.0 // Watts (lower at end, trickle charge)
    let minConsecutive = 5 // Stay below threshold for 5 samples
    
    var sustainCount = 0
    for i in stride(from: measurements.count - 1, through: 0, by: -1) {
        let measurement = measurements[i]
        if measurement.power < powerThreshold {
            sustainCount += 1
            if sustainCount >= minConsecutive {
                return i + 1
            }
        } else {
            sustainCount = 0
        }
    }
    return nil
}
```

**Thresholds:**
- Start: 5W (must exceed noise level)
- End: 1W (lower, allows trickle detection)
- Sustain: 3 samples @start, 5 @end (hysteresis)

### Battery level method (fallback)

Dacă dispositiv raportează procent:

```swift
func findChargeStartByBattery(measurements: [Measurement]) -> Int? {
    // Find first battery level jump
    for i in 1..<measurements.count {
        let prev = measurements[i-1]
        let curr = measurements[i]
        
        let batteryChange = (curr.batteryPercent ?? 0) - (prev.batteryPercent ?? 0)
        if batteryChange > 1.0 {  // >1% jump = real charge
            return i - 1
        }
    }
    return nil
}

func findChargeEndByBattery(measurements: [Measurement]) -> Int? {
    // Find last battery level change
    for i in stride(from: measurements.count - 1, through: 1, by: -1) {
        let prev = measurements[i-1]
        let curr = measurements[i]
        
        let batteryChange = (curr.batteryPercent ?? 0) - (prev.batteryPercent ?? 0)
        if batteryChange > 0.5 {  // Still charging
            return i
        }
    }
    return nil
}
```

### Combined method (best)

```swift
func isolateChargeRange(measurements: [Measurement]) -> (start: Int, end: Int)? {
    // Primary: use power threshold
    guard var start = findChargeStart(measurements: measurements),
          var end = findChargeEnd(measurements: measurements) else {
        // Fallback: use battery level
        guard var start = findChargeStartByBattery(measurements: measurements),
              var end = findChargeEndByBattery(measurements: measurements) else {
            return nil
        }
        return (start, end)
    }
    
    // Validate result
    guard start < end && (end - start) >= 10 else {
        return nil  // Too short (< 10 seconds @ 1Hz)
    }
    
    return (start, end)
}
```

## API Public

### Metode

```swift
// Detection
func findChargeStart(measurements: [Measurement]) -> Int?
func findChargeEnd(measurements: [Measurement]) -> Int?
func isolateChargeRange(measurements: [Measurement]) -> (start: Int, end: Int)?

// Extraction
func extractIsolatedCurve(measurements: [Measurement]) 
    -> [Measurement]?

// Validation
func isValidChargeRange(start: Int, end: Int, total: Int) -> Bool
func estimateNoisePercentage(measurements: [Measurement]) -> Double
```

### Rezultat

```swift
struct IsolatedChargeData {
    let originalMeasurements: [Measurement]
    let isolatedMeasurements: [Measurement]
    let startIndex: Int
    let endIndex: Int
    let realStartTime: Date
    let realEndTime: Date
    let noisePercentagePre: Double
    let noisePercentagePost: Double
    
    var effectiveChargeTime: TimeInterval {
        realEndTime.timeIntervalSince(realStartTime)
    }
}
```

## Comportamente critice

### Threshold sensitivity

**Problem: threshold prea mare**
```
Threshold: 10W
Reality: charger 12W on iPhone 15
Result: detectează corect ✓
But: low-power charger 5W → missed (fails)
```

**Problem: threshold prea mic**
```
Threshold: 0.5W
Reality: background AC noise 1.5W
Result: detectează noise, invalid start (fails)
```

**Solution:** Adaptive threshold:
```swift
let baselineNoise = measurements.prefix(10).map { $0.power }.max() ?? 0
let powerThreshold = baselineNoise * 1.5 + 2.0  // 50% margin + 2W
```

### Short charges

```
Charge duration: 30 seconds (e.g., quick top-up)
Measurements: 30 samples @ 1Hz
Noise isolation: 3 samples start + 5 samples end = 8 samples
Remaining: 22 samples (73% valid)
⟹ Valid
```

**SHOULD**: Accept short charges
**MUST**: Still isolate pre/post

### Noisy environment

```
Pre-charge noise: 2W baseline (poor outlet)
Charge power: 18W
Power jump: 16W (obvious)
⟹ Easy to detect

Vs.

Pre-charge noise: 8W (strong interference)
Charge power: 10W
Power jump: 2W (ambiguous!)
⟹ Hard, need fallback (battery level)
```

**SHOULD**: Use combined method (power + battery)
**MUST**: Fall back if ambiguous

### Charging profile changes

```
Curve:
- 0-1h: 20W (fast charge phase)
- 1-2h: 10W (medium phase)
- 2-3h: 3W (taper phase)
- 3h+: 0.5W (trickle/done)

Isolated region: 0h-3h (valid)
Threshold: 1W (catches all phases)
⟹ Correct
```

**MUST**: Detect end even with tapering

## Testare

### Unit tests

```swift
test_findChargeStart_ValidThreshold()
test_findChargeStart_NoiseIgnored()
test_findChargeEnd_ValidThreshold()
test_findChargeEnd_WithTrickleCharge()
test_isolateChargeRange_ValidData()
test_isolateChargeRange_TooShortIgnored()
test_isolateChargeRange_HighNoise_FallbackToBattery()
test_isValidChargeRange_StartBeforeEnd()
test_isValidChargeRange_MinimumDuration()
test_estimateNoisePercentage()
test_extractIsolatedCurve_PreservesMeasurements()
```

### Integration tests

- [ ] Raw data: 6h recording, 15 min real charge → correctly isolated
- [ ] High pre-charge noise (8W) → still detected
- [ ] Low power charger (5W) → detected
- [ ] Multiple charge phases → end detected at trickle
- [ ] Isolated curve used for energy calculation

## Edge cases

### No charge (meter ON, device not charging)

```
Measurements: all ~0W
Start detection: fails (no power jump)
End detection: fails (no charging detected)
Result: (nil, nil)
⟹ Skip session (valid, no charging occurred)
```

**MUST**: Handle gracefully

### Meter added after charge started

```
Recording: 03:00-07:00 (4 hours)
Real charge: 01:00-06:00 (5 hours, but missed first 2h)
Measurements: 10W constant (already in charge phase)
Start: 03:00 (best guess)
End: 06:00 (detected from power drop)
⟹ Partial isolation OK
```

**SHOULD**: Use what data we have
**MAY**: Mark as "incomplete start"

### USB power delivery with negotiation

```
Time 00:00: Meter ON, no device
Time 00:10: Device plugged in, negotiation 100ms
Time 00:12: Charging starts (power jump 20W)
Samples: ?, ?, [19.5W, 19.8W, ...] ← isolated start
⟹ Correct
```

**SHOULD**: OK, negotiation time negligible

## Dependencies

- [Charging Monitoring](./ChargingMonitoring.md): input measurements
- [Charge Curve Storage](./ChargeCurveStorage.md): store isolated data
- Core Data: battery levels (fallback)

## Notes

- Power threshold: device/charger dependent, may need tuning
- Battery level fallback: only if power method ambiguous
- Resolution: 1Hz (1000ms), ~1W precision typical
- Legat: [Charge Session Integrity](../Charge%20Session%20Integrity%20and%20Conflict%20Healing.md)
