# Story 10.3: BrineSource et BrineSink avec Support Glycol

**Epic:** 10 - Enhanced Boundary Conditions  
**Priorité:** P0-CRITIQUE  
**Estimation:** 3h  
**Statut:** backlog  
**Dépendances:** Story 10-1 (Nouveaux types physiques)

---

## Story

> En tant que moteur de simulation thermodynamique,  
> Je veux que `BrineSource` et `BrineSink` supportent les mélanges eau-glycol avec concentration,  
> Afin de pouvoir simuler des circuits de caloporteurs avec propriétés thermophysiques correctes.

---

## Contexte

Les caloporteurs liquides (eau, PEG, MEG, saumures) sont utilisés dans:

- Circuits primaire/secondaire de chillers
- Systèmes de chauffage urbain
- Applications basse température avec protection antigel

La **concentration en glycol** affecte:
- Viscosité (perte de charge)
- Chaleur massique (capacité thermique)
- Point de congélation (protection antigel)

---

## Spécifications Techniques

### BrineSource

```rust
/// Source pour fluides caloporteurs liquides (eau, PEG, MEG, saumures).
///
/// Impose une température et une pression fixées sur le port de sortie.
/// La concentration en glycol est prise en compte pour les propriétés.
#[derive(Debug, Clone)]
pub struct BrineSource {
    /// Identifiant du fluide (ex: "Water", "MEG", "PEG")
    fluid_id: String,
    /// Concentration en glycol (% massique, 0 = eau pure)
    concentration: Concentration,
    /// Température de set-point [K]
    t_set: Temperature,
    /// Pression de set-point [Pa]
    p_set: Pressure,
    /// Enthalpie calculée depuis T et concentration [J/kg]
    h_set: Enthalpy,
    /// Débit massique optionnel [kg/s]
    mass_flow: Option<MassFlow>,
    /// Débit volumique optionnel [m³/s]
    volume_flow: Option<VolumeFlow>,
    /// Port de sortie connecté
    outlet: ConnectedPort,
}

impl BrineSource {
    /// Crée une source d'eau pure.
    pub fn water(
        temperature: Temperature,
        pressure: Pressure,
        outlet: ConnectedPort,
    ) -> Result<Self, ComponentError>;
    
    /// Crée une source de mélange eau-glycol.
    pub fn glycol_mixture(
        fluid_id: impl Into<String>,
        concentration: Concentration,
        temperature: Temperature,
        pressure: Pressure,
        outlet: ConnectedPort,
    ) -> Result<Self, ComponentError>;
    
    /// Définit le débit massique imposé.
    pub fn set_mass_flow(&mut self, mass_flow: MassFlow);
    
    /// Définit le débit volumique imposé.
    /// Le débit massique est calculé avec la masse volumique du mélange.
    pub fn set_volume_flow(&mut self, volume_flow: VolumeFlow, density: f64);
}
```

### BrineSink

```rust
/// Puits pour fluides caloporteurs liquides.
#[derive(Debug, Clone)]
pub struct BrineSink {
    /// Identifiant du fluide
    fluid_id: String,
    /// Concentration en glycol
    concentration: Concentration,
    /// Contre-pression [Pa]
    p_back: Pressure,
    /// Température de retour optionnelle [K]
    t_back: Option<Temperature>,
    /// Port d'entrée connecté
    inlet: ConnectedPort,
}

impl BrineSink {
    /// Crée un puits pour eau pure.
    pub fn water(
        pressure: Pressure,
        inlet: ConnectedPort,
    ) -> Result<Self, ComponentError>;
    
    /// Crée un puits pour mélange eau-glycol.
    pub fn glycol_mixture(
        fluid_id: impl Into<String>,
        concentration: Concentration,
        pressure: Pressure,
        inlet: ConnectedPort,
    ) -> Result<Self, ComponentError>;
}
```

---

## Calcul des Propriétés

### Enthalpie depuis Température et Concentration

```rust
/// Calcule l'enthalpie d'un mélange eau-glycol.
///
/// Utilise CoolProp avec la syntaxe de mélange:
/// - Eau pure: "Water"
/// - Mélange MEG: "MEG-MASS%" ou "INCOMP::MEG-MASS%"
fn calculate_enthalpy(
    fluid_id: &str,
    concentration: Concentration,
    temperature: Temperature,
    pressure: Pressure,
) -> Result<Enthalpy, ComponentError> {
    // Pour CoolProp, utiliser:
    // PropsSI("H", "T", T, "P", P, fluid_string)
    // où fluid_string = format!("INCOMP::{}-{}", fluid_id, concentration.to_percent())
}
```

---

## Fichiers à Créer/Modifier

| Fichier | Action |
|---------|--------|
| `crates/components/src/flow_boundary/brine.rs` | Créer `BrineSource`, `BrineSink` |
| `crates/components/src/flow_boundary/mod.rs` | Ajouter ré-exports |

---

## Critères d'Acceptation

- [ ] `BrineSource::water()` crée une source d'eau pure
- [ ] `BrineSource::glycol_mixture()` crée une source avec concentration
- [ ] L'enthalpie est calculée correctement depuis T et concentration
- [ ] `BrineSink::water()` crée un puits pour eau
- [ ] `BrineSink::glycol_mixture()` crée un puits avec concentration
- [ ] `energy_transfers()` retourne `(Power(0), Power(0))`
- [ ] `port_enthalpies()` retourne `[h_set]`
- [ ] Validation de la concentration (0-100%)
- [ ] Tests unitaires avec différents pourcentages de glycol

---

## Tests Requis

```rust
#[cfg(test)]
mod tests {
    #[test]
    fn test_brine_source_water() { /* ... */ }
    
    #[test]
    fn test_brine_source_meg_30_percent() { /* ... */ }
    
    #[test]
    fn test_brine_source_enthalpy_calculation() { /* ... */ }
    
    #[test]
    fn test_brine_source_volume_flow_conversion() { /* ... */ }
    
    #[test]
    fn test_brine_sink_water() { /* ... */ }
    
    #[test]
    fn test_brine_sink_meg_mixture() { /* ... */ }
}
```

---

## Notes d'Implémentation

### Support CoolProp pour Mélanges

CoolProp supporte les mélanges incompressibles via la syntaxe:
```
INCOMP::MEG-30  // MEG à 30% massique
INCOMP::PEG-40  // PEG à 40% massique
```

Vérifier que le backend CoolProp utilisé dans le projet supporte cette syntaxe.

---

## Références

- [Architecture Document](../../plans/boundary-condition-refactoring-architecture.md)
- [Story 10-1: Nouveaux types physiques](./10-1-new-physical-types.md)
- [CoolProp Incompressible Fluids](http://www.coolprop.org/fluid_properties/Incompressibles.html)
