# Code Review Report – Story 2.7: Incompressible Fluids Support

**Date:** 2026-02-15  
**Story:** 2-7-incompressible-fluids-support  
**Status avant review:** review  
**Fichiers concernés:** `crates/fluids/src/incompressible.rs` (nouveau), `crates/fluids/src/lib.rs` (modifié)

---

## Git vs Story

- **Story File List:** incompressible.rs (new), lib.rs (modified)
- **Git:** `crates/fluids/` non suivi (??) – pas de commit
- **Écart:** Aucun – la story ne prétend pas que types.rs a été modifié dans le File List final

---

## Résumé des findings

| Sévérité | Nombre |
|----------|--------|
| HIGH     | 1      |
| MEDIUM   | 4      |
| LOW      | 3      |
| **Total**| **8**  |

---

## 🔴 HIGH

### 1. AC #2 non conforme – tolérance 1 % au lieu de 0.1 %

**AC #2:** « Results match reference data (IAPWS-IF97 for water, ASHRAE for glycol) within 0.1% »

**Implémentation:** Les tests utilisent une tolérance de 1 % (`< 0.01`), pas 0.1 % (`< 0.001`).

**Fichier:** `crates/fluids/src/incompressible.rs`  
**Lignes:** 355–356, 371

```rust
assert!((rho_20 - 998.2).abs() / 998.2 < 0.01, "rho_20={}", rho_20);  // 1% ≠ 0.1%
assert!((cp - 4182.0).abs() / 4182.0 < 0.01, "Cp={}", cp);            // 1% ≠ 0.1%
```

**Correction:** Remplacer `0.01` par `0.001` dans les assertions de précision, ou ajuster les modèles pour atteindre 0.1 % si nécessaire.

---

## 🟡 MEDIUM

### 2. Tests requis manquants (story § Testing Requirements)

**Story:** Les tests suivants sont listés comme requis mais absents :

- `test_water_enthalpy_reference` – h(T) par rapport à 0°C
- `test_glycol_concentration_effect` – propriétés vs concentration (seul EG30 > water est testé)
- `test_glycol_out_of_range` – température hors plage pour glycol
- `test_humid_air_psychrometrics` – enthalpie vs formule psychrométrique
- `test_performance_vs_coolprop` – benchmark 1000× (optionnel selon story)

**Fichier:** `crates/fluids/src/incompressible.rs`  
**Lignes:** 302–437 (section tests)

**Correction:** Ajouter au minimum `test_water_enthalpy_reference`, `test_glycol_concentration_effect` et `test_glycol_out_of_range`.

---

### 3. Phase incorrecte pour HumidAir

**Implémentation:** `phase()` retourne `Phase::Liquid` pour tous les fluides, y compris HumidAir.

**Problème:** HumidAir est un mélange gazeux, pas un liquide.

**Fichier:** `crates/fluids/src/incompressible.rs`  
**Lignes:** 280–286

```rust
fn phase(&self, fluid: FluidId, _state: ThermoState) -> FluidResult<Phase> {
    if IncompFluid::from_fluid_id(&fluid).is_some() {
        Ok(Phase::Liquid)  // HumidAir devrait être Vapor
    } else {
        Err(FluidError::UnknownFluid { fluid: fluid.0 })
    }
}
```

**Correction:** Retourner `Phase::Vapor` pour HumidAir, `Phase::Liquid` pour les autres.

---

### 4. Pas de validation NaN/Inf pour la température

**Implémentation:** Aucune vérification que `t_k` est fini (non NaN, non Inf).

**Problème:** Si `t_k = f64::NAN`, les comparaisons `t_k < min_t || t_k > max_t` sont fausses, la validation passe et les polynômes renvoient NaN.

**Fichier:** `crates/fluids/src/incompressible.rs`  
**Lignes:** 129–132, 161–166, 218–225

**Correction:** Ajouter en début de chaque `property_*` :

```rust
if !t_k.is_finite() {
    return Err(FluidError::InvalidState {
        reason: format!("Temperature {} K is not finite", t_k),
    });
}
```

---

### 5. `critical_point` pour fluides non supportés

**Implémentation:** `critical_point()` retourne toujours `NoCriticalPoint` pour tout `FluidId`.

**Problème:** Pour un fluide non supporté (ex. `"R134a"`), le backend retourne `NoCriticalPoint` alors que R134a a un point critique. Cohérence avec `property()` qui retourne `UnknownFluid`.

**Fichier:** `crates/fluids/src/incompressible.rs`  
**Lignes:** 271–273

**Correction:** Vérifier si le fluide est supporté et retourner `UnknownFluid` si non :

```rust
fn critical_point(&self, fluid: FluidId) -> FluidResult<CriticalPoint> {
    if IncompFluid::from_fluid_id(&fluid).is_none() {
        return Err(FluidError::UnknownFluid { fluid: fluid.0 });
    }
    Err(FluidError::NoCriticalPoint { fluid: fluid.0 })
}
```

---

## 🟢 LOW

### 6. `ValidRange` défini mais non utilisé

**Implémentation:** `ValidRange` est défini et exporté, mais le code utilise des tuples `(min_t, max_t)` via `valid_temp_range()`.

**Fichier:** `crates/fluids/src/incompressible.rs`  
**Lignes:** 79–93

**Correction:** Soit utiliser `ValidRange` dans les méthodes `property_*`, soit le retirer s’il n’est pas nécessaire.

---

### 7. Formule de viscosité glycol redondante

**Implémentation:** `conc_factor = (1.0 + 10.0 * concentration).ln().exp()` est équivalent à `1.0 + 10.0 * concentration`.

**Fichier:** `crates/fluids/src/incompressible.rs`  
**Ligne:** 198

**Correction:** Simplifier en `let conc_factor = 1.0 + 10.0 * concentration;`

---

### 8. Story File List vs Technical Requirements

**Story:** « Modified files » inclut `types.rs` (Add IncompressibleFluid enum to FluidId), mais le File List final ne mentionne que `incompressible.rs` et `lib.rs`.

**Constat:** L’implémentation a mis `IncompFluid` dans `incompressible.rs` au lieu de `types.rs`. C’est cohérent et acceptable.

**Correction:** Mettre à jour la section « Modified files » de la story pour retirer `types.rs` et refléter la structure réelle.

---

## Synthèse

| # | Sévérité | Description |
|---|----------|-------------|
| 1 | HIGH     | Tolérance tests 1 % au lieu de 0.1 % (AC #2) |
| 2 | MEDIUM   | Tests requis manquants |
| 3 | MEDIUM   | Phase HumidAir = Liquid au lieu de Vapor |
| 4 | MEDIUM   | Pas de validation NaN/Inf pour t_k |
| 5 | MEDIUM   | `critical_point` ne distingue pas fluide inconnu |
| 6 | LOW      | `ValidRange` non utilisé |
| 7 | LOW      | Formule viscosité glycol redondante |
| 8 | LOW      | Incohérence story File List vs Technical Requirements |

---

## Recommandation

- Corriger les points HIGH et MEDIUM avant de passer la story en `done`.
- Les points LOW peuvent être traités dans un suivi ultérieur.

---

## Corrections appliquées (2026-02-15)

| # | Correction |
|---|------------|
| 1 | Tolérance 0.01→0.001 ; polynôme densité eau recalibré (1001.7 - 0.107*T - 0.00333*T²) |
| 2 | Tests ajoutés : test_water_enthalpy_reference, test_glycol_concentration_effect, test_glycol_out_of_range, test_humid_air_psychrometrics, test_phase_humid_air_is_vapor, test_critical_point_unknown_fluid, test_nan_temperature_rejected |
| 3 | phase() retourne Phase::Vapor pour HumidAir |
| 4 | Validation t_k.is_finite() dans property_water, property_glycol, property_humid_air |
| 5 | critical_point() retourne UnknownFluid pour fluides non supportés |
| 7 | conc_factor simplifié : (1+10*c).ln().exp() → 1+10*c |
