# Capacity Measurement Operation

Măsurarea şi calculul capacităţii unei baterii din sesiunile de încărcare.

## Responsabilități

- Determinarea capacităţii reale a unei baterii (mAh)
- Validarea completitudinii datelor (0% → 100%)
- Calculul din energie + voltaje nominal
- Tracking capacity degradation (baterie veche)

## Concepte

### Capacity vs Energy

- **Capacity (mAh)**: Cantitate de sarcină. Ex: 3000 mAh
- **Energy (Wh)**: Capacitate × voltaj nominal. Ex: 3000mAh × 3.7V = 11.1Wh

Formula:
```
Capacity (mAh) = Energy (Wh) × 1000 / Nominal Voltage (V)
```

Exemplu:
```
Energy = 11.1 Wh
Nominal voltage = 3.7V (typical Li-ion)
Capacity = 11.1 × 1000 / 3.7 = 3000 mAh
```

## Invarianţi

- **MUST**: Capacity > 0 mAh (nu poate fi 0 sau negativ)
- **MUST**: Capacity ≤ 10000 mAh pentru device-uri obişnuite
- **MUST**: Sesiune validă pentru capacity learning trebuie: battery 0% → 100%
- **MUST**: Nominal voltage trebuie specificat (nu se calculează)
- **MUST**: Capacity learning doar din **complete discharge cycles**
- **SHOULD**: Capacity măsurat ≥ 80% din rated capacity (bateria e "OK")
- **MAY**: Capacity < 80% = aged battery, notify user

## Types of sessions

### Full discharge (valid pentru learning)

```
Battery: 100% → ... → 0%
Device: ON throughout
Meter: record-ează energie totală

⟹ Capacity = Energy / Nominal_Voltage
```

**Invarianţi:**
- **MUST**: Start @ ~100%, end @ ~0%
- **MUST**: Fără interruption (meter disconnect = invalid)
- **MUST**: Device rămâne ON (nu suspend mid-session)

### Partial charge

```
Battery: 20% → 50%
Device: OFF (charging)

⟹ Cannot infer capacity (unknown starting level)
```

**MUST**: Ignore pentru capacity learning

### Trickle charge (edge case)

```
Battery: 95% → 100%
Current: <50mA (charging optimizer active)
Duration: >2 hours

⟹ Energy negligible, skip
```

## Algorithm

### Capacity learning from discharge

```swift
func learnCapacityFromDischarge(session: ChargeRecord) -> Double? {
    // 1. Validate session
    guard isCompleteDischargeSession(session) else { return nil }
    
    // 2. Extract energy
    let energyWh = session.totalEnergy
    
    // 3. Get nominal voltage
    let nominalVoltageV = device.nominalBatteryVoltage // 3.7, 5.0, etc
    
    // 4. Calculate capacity
    let capacityMah = energyWh * 1000 / nominalVoltageV
    
    // 5. Validate result
    guard capacityMah > 0 && capacityMah <= 10000 else {
        logWarning("Capacity outside bounds: \(capacityMah) mAh")
        return nil
    }
    
    return capacityMah
}

func isCompleteDischargeSession(_ session: ChargeRecord) -> Bool {
    // Check battery levels
    guard let startBattery = session.startBatteryPercent,
          let endBattery = session.endBatteryPercent else {
        return false
    }
    
    // Range must span ~100%
    let range = startBattery - endBattery
    guard range >= 90 else {
        logWarning("Incomplete discharge: \(range)% only")
        return false
    }
    
    // Device must be ON during discharge
    guard session.isCompletelyRecorded else {
        logWarning("Session incomplete (meter disconnect)")
        return false
    }
    
    return true
}
```

### Weighted moving average

Pentru stabilitate, combinaţi multiple measurements:

```swift
func estimateCapacity(from sessions: [ChargeRecord]) -> Double? {
    let validSessions = sessions.compactMap { session in
        learnCapacityFromDischarge(session)
    }
    
    guard !validSessions.isEmpty else { return nil }
    
    // Weight by recency: newer sessions count more
    let weights = validSessions.indices.map { i in
        Double(i + 1) / Double(validSessions.count)
    }
    
    let weightedSum = zip(validSessions, weights)
        .map { capacity, weight in capacity * weight }
        .reduce(0, +)
    
    let capacityMah = weightedSum / weights.reduce(0, +)
    
    return capacityMah
}
```

## API Public

### Proprietăţi

| Proprietate | Tip | Descriere |
|---|---|---|
| `nominalBatteryVoltage` | Double | V (ex: 3.7) |
| `measuredCapacity` | Double? | mAh din learning |
| `ratedCapacity` | Double? | mAh din device spec |
| `capacity_Health` | Double | measured / rated (%) |
| `lastCapacityMeasurement` | Date? | When learned |

### Metode

```swift
// Learning
func learnCapacity(from session: ChargeRecord) -> Double?
func learnCapacity(from sessions: [ChargeRecord]) -> Double?

// Validation
func isValidCapacityMeasurement(_ mah: Double) -> Bool
func isCompleteDischargeSession(_ session: ChargeRecord) -> Bool
func estimateSessionCompleteness(_ session: ChargeRecord) -> Double

// Health
func batteryHealth() -> Double // measured / rated
func isAgedBattery() -> Bool // health < 80%
```

## Comportamente critice

### Incompletă sesiune → skip learning

```
Device: 30% → 15%
Meter: record-ează 5 Wh
⟹ Skip (partial discharge, nu putem infera capacity)

Device: 100% → 5% (but meter lost signal @ 20%)
⟹ Skip (incomplete recording)
```

**MUST**: Validate completeness înainte de learning

### Multiple discharge cycles

```
Session 1: 100% → 0%, measured 12 Wh → 3200 mAh
Session 2: 100% → 0%, measured 11.5 Wh → 3100 mAh
Session 3: 100% → 0%, measured 11.2 Wh → 3000 mAh
⟹ Average: 3100 mAh (weighted toward recent)
```

**SHOULD**: Combinaţi sesiuni (moving average)
**MUST**: Keep history pentru trend analysis

### Battery aging

```
Device created: 2024-01-01, rated 3000 mAh
Measured capacities:
  - 2024-01-15: 2950 mAh (98% health)
  - 2024-06-15: 2700 mAh (90% health)
  - 2025-01-15: 2400 mAh (80% health) ← aged
⟹ Notify user: "Battery health below 80%"
```

**SHOULD**: Track degradation curve
**SHOULD**: Notify @ 80% health
**MAY**: Suggest battery replacement @ 70%

## Testare

### Unit tests

```swift
test_learnCapacity_ValidDischargeSession()
test_learnCapacity_PartialDischarge_Ignored()
test_learnCapacity_IncompleteRecording_Ignored()
test_isCompleteDischargeSession_ValidRange()
test_isCompleteDischargeSession_MinRange()
test_estimateCapacity_MultiplesSessions()
test_estimateCapacity_WeightedByRecency()
test_capacityMustBePositive()
test_capacityMustBeBounded()
test_batteryHealth_Calculation()
test_isAgedBattery_Threshold()
```

### Integration tests

- [ ] Single complete discharge 100%→0%: capacity measured
- [ ] Partial discharge: capacity NOT measured
- [ ] Multiple sessions: weighted average
- [ ] Aged battery detected @ 80%
- [ ] Capacity persistent în Core Data
- [ ] Battery health calculated correctly

## Edge cases

### Rapid discharge (high power draw)

```
Battery: 100% → 0% in 30 minutes
Power draw: 50W constant
Energy: large, measured accurately
⟹ Normal discharge, capacity = energy / voltage
```

**SHOULD**: Accept și use pentru learning

### Trickle charge (low power)

```
Battery: 95% → 100%
Charging current: 10mA (very low)
Duration: 4 hours
Energy: negligible (~0.2Wh)
⟹ Skip (too little data)
```

**MUST**: Ignore (capacity = energy/voltage → false low)

### Voltage variance

```
Nominal voltage: 3.7V
But actual range: 3.2V (low) → 4.2V (full)
Energy measured: 11Wh
Capacity calculation: depends on which voltage used
```

**SHOULD**: Use nominal (convention)
**SHOULD**: Log warning dacă voltage deviates

## Dependencies

- [Charging Monitoring](./ChargingMonitoring.md): session data
- [Charge Curve Storage](./ChargeCurveStorage.md): historical curves
- Core Data: `ChargeSession.startBatteryPercent`, `endBatteryPercent`

## References

- [Powerbank Category](../Powerbank%20Category.md) — battery reporting modes
- Scientific: https://en.wikipedia.org/wiki/Battery_capacity
