# Story 10.2: RefrigerantSource et RefrigerantSink

**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 `RefrigerantSource` et `RefrigerantSink` implémentent le trait `Component`,  
> Afin de pouvoir définir des conditions aux limites pour les fluides frigorigènes avec titre.

---

## Contexte

Les fluides frigorigènes (R410A, R134a, CO2, etc.) nécessitent des conditions aux limites spécifiques:

- Possibilité de spécifier le **titre** (vapor quality) au lieu de l'enthalpie
- Validation que le fluide est bien un réfrigérant
- Support des propriétés thermodynamiques via CoolProp

---

## Spécifications Techniques

### RefrigerantSource

```rust
/// Source pour fluides frigorigènes compressibles.
///
/// Impose une pression et une enthalpie (ou titre) fixées sur le port de sortie.
#[derive(Debug, Clone)]
pub struct RefrigerantSource {
    /// Identifiant du fluide frigorigène (ex: "R410A", "R134a", "CO2")
    fluid_id: String,
    /// Pression de set-point [Pa]
    p_set: Pressure,
    /// Enthalpie de set-point [J/kg]
    h_set: Enthalpy,
    /// Titre optionnel (vapor quality, 0-1)
    vapor_quality: Option<VaporQuality>,
    /// Débit massique optionnel [kg/s]
    mass_flow: Option<MassFlow>,
    /// Port de sortie connecté
    outlet: ConnectedPort,
}

impl RefrigerantSource {
    /// Crée une source réfrigérant avec pression et enthalpie fixées.
    pub fn new(
        fluid_id: impl Into<String>,
        pressure: Pressure,
        enthalpy: Enthalpy,
        outlet: ConnectedPort,
    ) -> Result<Self, ComponentError>;
    
    /// Crée une source réfrigérant avec pression et titre fixés.
    /// L'enthalpie est calculée automatiquement via CoolProp.
    pub fn with_vapor_quality(
        fluid_id: impl Into<String>,
        pressure: Pressure,
        vapor_quality: VaporQuality,
        outlet: ConnectedPort,
    ) -> Result<Self, ComponentError>;
    
    /// Définit le débit massique imposé.
    pub fn set_mass_flow(&mut self, mass_flow: MassFlow);
}
```

### RefrigerantSink

```rust
/// Puits pour fluides frigorigènes compressibles.
///
/// Impose une contre-pression fixe sur le port d'entrée.
#[derive(Debug, Clone)]
pub struct RefrigerantSink {
    /// Identifiant du fluide frigorigène
    fluid_id: String,
    /// Contre-pression [Pa]
    p_back: Pressure,
    /// Enthalpie de retour optionnelle [J/kg]
    h_back: Option<Enthalpy>,
    /// Port d'entrée connecté
    inlet: ConnectedPort,
}

impl RefrigerantSink {
    /// Crée un puits réfrigérant avec contre-pression fixe.
    pub fn new(
        fluid_id: impl Into<String>,
        pressure: Pressure,
        inlet: ConnectedPort,
    ) -> Result<Self, ComponentError>;
    
    /// Définit une enthalpie de retour fixe.
    pub fn set_return_enthalpy(&mut self, enthalpy: Enthalpy);
}
```

---

## Implémentation du Trait Component

```rust
impl Component for RefrigerantSource {
    fn n_equations(&self) -> usize { 2 }
    
    fn compute_residuals(&self, _state: &SystemState, residuals: &mut ResidualVector) 
        -> Result<(), ComponentError> 
    {
        residuals[0] = self.outlet.pressure().to_pascals() - self.p_set.to_pascals();
        residuals[1] = self.outlet.enthalpy().to_joules_per_kg() - self.h_set.to_joules_per_kg();
        Ok(())
    }
    
    fn energy_transfers(&self, _state: &SystemState) -> Option<(Power, Power)> {
        Some((Power::from_watts(0.0), Power::from_watts(0.0)))
    }
    
    fn port_enthalpies(&self, _state: &SystemState) -> Result<Vec<Enthalpy>, ComponentError> {
        Ok(vec![self.h_set])
    }
    
    fn port_mass_flows(&self, _state: &SystemState) -> Result<Vec<MassFlow>, ComponentError> {
        match self.mass_flow {
            Some(mdot) => Ok(vec![MassFlow::from_kg_per_s(-mdot.to_kg_per_s())]),
            None => Ok(vec![]),
        }
    }
}
```

---

## Fichiers à Créer/Modifier

| Fichier | Action |
|---------|--------|
| `crates/components/src/flow_boundary/mod.rs` | Créer module avec ré-exports |
| `crates/components/src/flow_boundary/refrigerant.rs` | Créer `RefrigerantSource`, `RefrigerantSink` |
| `crates/components/src/lib.rs` | Exporter les nouveaux types |

---

## Critères d'Acceptation

- [ ] `RefrigerantSource::new()` crée une source avec P et h fixées
- [ ] `RefrigerantSource::with_vapor_quality()` calcule l'enthalpie depuis le titre
- [ ] `RefrigerantSink::new()` crée un puits avec contre-pression
- [ ] `energy_transfers()` retourne `(Power(0), Power(0))`
- [ ] `port_enthalpies()` retourne `[h_set]`
- [ ] `port_mass_flows()` retourne le débit si spécifié
- [ ] Validation que le fluide est un réfrigérant valide
- [ ] Tests unitaires complets

---

## Tests Requis

```rust
#[cfg(test)]
mod tests {
    #[test]
    fn test_refrigerant_source_new() { /* ... */ }
    
    #[test]
    fn test_refrigerant_source_with_vapor_quality() { /* ... */ }
    
    #[test]
    fn test_refrigerant_source_energy_transfers_zero() { /* ... */ }
    
    #[test]
    fn test_refrigerant_source_port_enthalpies() { /* ... */ }
    
    #[test]
    fn test_refrigerant_sink_new() { /* ... */ }
    
    #[test]
    fn test_refrigerant_sink_with_return_enthalpy() { /* ... */ }
}
```

---

## Références

- [Architecture Document](../../plans/boundary-condition-refactoring-architecture.md)
- [Story 10-1: Nouveaux types physiques](./10-1-new-physical-types.md)
