200 lines
6.7 KiB
Markdown
200 lines
6.7 KiB
Markdown
# 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 |
|