# 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 { 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 { 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 |