# Plan de Correction Post-Audit de Cohérence **Date:** 2026-02-22 **Auteur:** Audit Architecture Rust & BMAD **Priorité:** CRITIQUE **Statut:** À implémenter --- ## Résumé Exécutif Cet audit a identifié **12 écarts de cohérence** répartis en 3 catégories de priorité. Les problèmes les plus critiques concernent l'Epic 7 (Validation) où l'implémentation incomplète des méthodes `energy_transfers()` et `port_enthalpies()` empêche une validation thermodynamique correcte. **Impact métier:** Sans ces corrections, un système thermodynamique contenant un détendeur, des jonctions ou des conditions aux limites NE sera PAS correctement validé, ce qui peut masquer des erreurs physiques graves. --- ## Catalogue des User Stories de Correction ### Epic 9: Correction de Cohérence (Nouvel Epic) #### Story 9.1: Unification des Types Duplicats - CircuitId **Priorité:** P1-CRITIQUE **Estimation:** 2h **Dépendances:** Aucune **Story:** > En tant que développeur Rust, > Je veux un type `CircuitId` unique et cohérent, > Afin d'éviter les erreurs de compilation lors de l'utilisation conjointe des modules solver et components. **Problème actuel:** ```rust // crates/solver/src/system.rs:31 pub struct CircuitId(pub u8); // Représentation numérique // crates/components/src/state_machine.rs:332 pub struct CircuitId(String); // Représentation textuelle ``` **Solution proposée:** 1. Garder `CircuitId(u8)` dans `crates/core/src/types.rs` pour performance 2. Ajouter `impl From<&str> for CircuitId` avec hash interne 3. Supprimer `CircuitId` de `state_machine.rs` 4. Ré-exporter depuis `entropyk_core` **Fichiers à modifier:** - `crates/core/src/types.rs` (ajout) - `crates/solver/src/system.rs` (suppression, ré-import) - `crates/components/src/state_machine.rs` (suppression, ré-import) - `crates/entropyk/src/lib.rs` (mise à jour exports) **Critères d'acceptation:** - [ ] Un seul `CircuitId` dans la codebase - [ ] Conversion `From<&str>` et `From` disponibles - [ ] `cargo test --workspace` passe - [ ] `cargo clippy -- -D warnings` passe --- #### Story 9.2: Unification des Types Duplicats - FluidId **Priorité:** P1-CRITIQUE **Estimation:** 2h **Dépendances:** Aucune **Story:** > En tant que développeur Rust, > Je veux un type `FluidId` unique avec API cohérente, > Afin d'éviter la confusion entre `fluids::FluidId` et `components::port::FluidId`. **Problème actuel:** ```rust // crates/fluids/src/types.rs:35 pub struct FluidId(pub String); // Champ public // crates/components/src/port.rs:137 pub struct FluidId(String); // Champ privé, méthode as_str() ``` **Solution proposée:** 1. Garder `FluidId` dans `crates/fluids/src/types.rs` comme source unique 2. Exposer `as_str()` et garder champ public pour compatibilité 3. Supprimer `FluidId` de `port.rs` 4. Ré-importer depuis `entropyk_fluids` **Fichiers à modifier:** - `crates/fluids/src/types.rs` (ajout méthode `as_str()`) - `crates/components/src/port.rs` (suppression, ré-import) - `crates/components/src/lib.rs` (mise à jour exports) **Critères d'acceptation:** - [ ] Un seul `FluidId` dans la codebase - [ ] Méthode `as_str()` disponible - [ ] Champ `0` accessible pour compatibilité - [ ] `cargo test --workspace` passe --- #### Story 9.3: Complétion Epic 7 - ExpansionValve Energy Methods **Priorité:** P1-CRITIQUE **Estimation:** 3h **Dépendances:** Story 9.2 **Story:** > En tant que moteur de simulation thermodynamique, > Je veux que `ExpansionValve` implémente `energy_transfers()` et `port_enthalpies()`, > Afin que le bilan énergétique soit correctement validé pour les cycles frigorifiques. **Problème actuel:** - `ExpansionValve` implémente seulement `port_mass_flows()` - Le détendeur est **ignoré** dans `check_energy_balance()` - Or, c'est un composant critique de tout cycle frigorifique **Solution proposée:** ```rust // Dans crates/components/src/expansion_valve.rs impl Component for ExpansionValve { // ... existing code ... fn port_enthalpies( &self, _state: &SystemState, ) -> Result, ComponentError> { // Retourne les enthalpies des ports (ordre: inlet, outlet) Ok(vec![ self.port_inlet.enthalpy(), self.port_outlet.enthalpy(), ]) } fn energy_transfers(&self, _state: &SystemState) -> Option<(Power, Power)> { // Détendeur isenthalpique: Q=0, W=0 // (Pas de transfert thermique, pas de travail) Some((Power::from_watts(0.0), Power::from_watts(0.0))) } } ``` **Fichiers à modifier:** - `crates/components/src/expansion_valve.rs` **Critères d'acceptation:** - [ ] `energy_transfers()` retourne `(Power(0), Power(0))` - [ ] `port_enthalpies()` retourne `[h_in, h_out]` - [ ] Test unitaire `test_expansion_valve_energy_balance` passe - [ ] `check_energy_balance()` ne skip plus `ExpansionValve` --- #### Story 9.4: Complétion Epic 7 - RefrigerantSource/RefrigerantSink Energy Methods **Priorité:** P1-CRITIQUE **Estimation:** 3h **Dépendances:** Story 9.2 **Story:** > En tant que moteur de simulation thermodynamique, > Je veux que `RefrigerantSource` et `RefrigerantSink` implémentent `energy_transfers()` et `port_enthalpies()`, > Afin que les conditions aux limites soient correctement prises en compte dans le bilan énergétique. **Problème actuel:** - `RefrigerantSource` et `RefrigerantSink` implémentent seulement `port_mass_flows()` - Ces composants sont ignorés dans la validation **Solution proposée:** ```rust // Dans crates/components/src/refrigerant_boundary.rs impl Component for RefrigerantSource { // ... existing code ... fn port_enthalpies( &self, _state: &SystemState, ) -> Result, ComponentError> { Ok(vec![self.port.enthalpy()]) } fn energy_transfers(&self, _state: &SystemState) -> Option<(Power, Power)> { // Source: pas de transfert actif, le fluide "apparaît" avec son enthalpie Some((Power::from_watts(0.0), Power::from_watts(0.0))) } } impl Component for RefrigerantSink { // ... existing code ... fn port_enthalpies( &self, _state: &SystemState, ) -> Result, ComponentError> { Ok(vec![self.port.enthalpy()]) } fn energy_transfers(&self, _state: &SystemState) -> Option<(Power, Power)> { // Sink: pas de transfert actif Some((Power::from_watts(0.0), Power::from_watts(0.0))) } } ``` **Fichiers à modifier:** - `crates/components/src/refrigerant_boundary.rs` **Critères d'acceptation:** - [ ] `RefrigerantSource` et `RefrigerantSink` implémentent les 3 méthodes - [ ] Tests unitaires associés passent - [ ] `check_energy_balance()` ne skip plus ces composants --- #### Story 9.5: Complétion Epic 7 - FlowSplitter/FlowMerger Energy Methods **Priorité:** P1-CRITIQUE **Estimation:** 4h **Dépendances:** Story 9.2 **Story:** > En tant que moteur de simulation thermodynamique, > Je veux que `FlowSplitter` et `FlowMerger` implémentent `energy_transfers()` et `port_enthalpies()`, > Afin que les jonctions soient correctement prises en compte dans le bilan énergétique. **Problème actuel:** - `FlowSplitter` et `FlowMerger` implémentent seulement `port_mass_flows()` - Les jonctions sont ignorées dans la validation **Solution proposée:** ```rust // Dans crates/components/src/flow_junction.rs impl Component for FlowSplitter { // ... existing code ... fn port_enthalpies( &self, _state: &SystemState, ) -> Result, ComponentError> { // Ordre: inlet, puis outlets let mut enthalpies = vec![self.port_inlet.enthalpy()]; for outlet in &self.ports_outlet { enthalpies.push(outlet.enthalpy()); } Ok(enthalpies) } fn energy_transfers(&self, _state: &SystemState) -> Option<(Power, Power)> { // Jonction adiabatique: Q=0, W=0 Some((Power::from_watts(0.0), Power::from_watts(0.0))) } } impl Component for FlowMerger { // ... existing code ... fn port_enthalpies( &self, _state: &SystemState, ) -> Result, ComponentError> { // Ordre: inlets, puis outlet let mut enthalpies = Vec::new(); for inlet in &self.ports_inlet { enthalpies.push(inlet.enthalpy()); } enthalpies.push(self.port_outlet.enthalpy()); Ok(enthalpies) } fn energy_transfers(&self, _state: &SystemState) -> Option<(Power, Power)> { // Jonction adiabatique: Q=0, W=0 Some((Power::from_watts(0.0), Power::from_watts(0.0))) } } ``` **Fichiers à modifier:** - `crates/components/src/flow_junction.rs` **Critères d'acceptation:** - [ ] `FlowSplitter` et `FlowMerger` implémentent les 3 méthodes - [ ] Tests unitaires associés passent - [ ] `check_energy_balance()` ne skip plus ces composants --- #### Story 9.6: Amélioration Logging Validation Énergie **Priorité:** P2-IMPORTANTE **Estimation:** 1h **Dépendances:** Stories 9.3, 9.4, 9.5 **Story:** > En tant que développeur debuggant une simulation, > Je veux un avertissement explicite quand des composants sont ignorés dans la validation énergétique, > Afin d'identifier rapidement les implémentations manquantes. **Problème actuel:** ```rust // crates/solver/src/system.rs:1873-1879 _ => { components_skipped += 1; tracing::debug!( // ← Niveau DEBUG, pas WARNING node_index = node_idx.index(), "Component lacks full energy transfer or enthalpy data - skipping energy balance check" ); } ``` **Solution proposée:** ```rust _ => { components_skipped += 1; tracing::warn!( node_index = node_idx.index(), component_type = std::any::type_name_of_val(component), "Component lacks energy_transfers() or port_enthalpies() - SKIPPED in energy balance validation" ); } ``` **Fichiers à modifier:** - `crates/solver/src/system.rs` **Critères d'acceptation:** - [ ] Logging au niveau WARN (pas DEBUG) - [ ] Inclut le type du composant dans le message - [ ] Test que le warning est bien émis --- #### Story 9.7: Refactoring Solver - Scinder solver.rs **Priorité:** P3-AMÉLIORATION **Estimation:** 4h **Dépendances:** Aucune **Story:** > En tant que développeur maintenant le code, > Je veux que les stratégies de solver soient dans des fichiers séparés, > Afin d'améliorer la maintenabilité du code. **Problème actuel:** - `solver.rs` fait ~2800 lignes - Architecture.md spécifie `strategies/newton_raphson.rs`, `strategies/sequential_substitution.rs`, `strategies/fallback.rs` **Solution proposée:** ``` crates/solver/src/ ├── lib.rs ├── solver.rs # Trait Solver, SolverStrategy enum ├── strategies/ │ ├── mod.rs │ ├── newton_raphson.rs │ ├── sequential_substitution.rs │ └── fallback.rs ├── system.rs ├── jacobian.rs └── ... ``` **Fichiers à créer/modifier:** - `crates/solver/src/strategies/mod.rs` (nouveau) - `crates/solver/src/strategies/newton_raphson.rs` (nouveau) - `crates/solver/src/strategies/sequential_substitution.rs` (nouveau) - `crates/solver/src/strategies/fallback.rs` (nouveau) - `crates/solver/src/solver.rs` (réduit) - `crates/solver/src/lib.rs` (mise à jour exports) **Critères d'acceptation:** - [ ] Chaque fichier < 500 lignes - [ ] `cargo test --workspace` passe - [ ] API publique inchangée --- #### Story 9.8: Création SystemState Struct Dédié **Priorité:** P3-AMÉLIORATION **Estimation:** 6h **Dépendances:** Stories 9.1, 9.2 **Story:** > En tant que développeur Rust, > Je veux un struct `SystemState` dédié au lieu d'un type alias, > Afin d'avoir une validation du layout et une meilleure sémantique. **Problème actuel:** ```rust // crates/components/src/lib.rs:180 pub type SystemState = Vec; ``` **Solution proposée:** ```rust // crates/core/src/state.rs (nouveau fichier) /// État du système thermodynamique. /// /// Layout: [P_edge0, h_edge0, P_edge1, h_edge1, ...] /// - P: Pression en Pascals /// - h: Enthalpie en J/kg #[derive(Debug, Clone)] pub struct SystemState { data: Vec, edge_count: usize, } impl SystemState { pub fn new(edge_count: usize) -> Self { Self { data: vec![0.0; edge_count * 2], edge_count, } } pub fn pressure(&self, edge_idx: usize) -> Option { self.data.get(edge_idx * 2).map(|&p| Pressure::from_pascals(p)) } pub fn enthalpy(&self, edge_idx: usize) -> Option { self.data.get(edge_idx * 2 + 1).map(|&h| Enthalpy::from_joules_per_kg(h)) } pub fn set_pressure(&mut self, edge_idx: usize, p: Pressure) { if let Some(slot) = self.data.get_mut(edge_idx * 2) { *slot = p.to_pascals(); } } pub fn set_enthalpy(&mut self, edge_idx: usize, h: Enthalpy) { if let Some(slot) = self.data.get_mut(edge_idx * 2 + 1) { *slot = h.to_joules_per_kg(); } } pub fn as_slice(&self) -> &[f64] { &self.data } pub fn as_mut_slice(&mut self) -> &mut [f64] { &mut self.data } } ``` **Critères d'acceptation:** - [ ] Struct `SystemState` avec méthodes d'accès typées - [ ] Migration progressive (compatibilité avec `AsRef<[f64]>`) - [ ] Tests unitaires pour accès par edge --- ## Plan d'Exécution Recommandé ### Sprint 1: Corrections Critiques (Semaine 1) | Jour | Story | Durée | |------|-------|-------| | Lundi AM | 9.1 CircuitId Unification | 2h | | Lundi PM | 9.2 FluidId Unification | 2h | | Mardi AM | 9.3 ExpansionValve Energy | 3h | | Mardi PM | 9.4 RefrigerantSource/RefrigerantSink Energy | 3h | | Mercredi AM | 9.5 FlowSplitter/FlowMerger Energy | 4h | | Mercredi PM | 9.6 Logging Improvement | 1h | | Jeudi | Tests d'intégration complets | 4h | | Vendredi | Code review & documentation | 4h | ### Sprint 2: Améliorations (Semaine 2) | Jour | Story | Durée | |------|-------|-------| | Lundi-Mardi | 9.7 Solver Refactoring | 4h | | Mercredi-Vendredi | 9.8 SystemState Struct | 6h | --- ## Tests de Validation Finale Après implémentation de toutes les stories, exécuter: ```bash # 1. Tests unitaires complets cargo test --workspace # 2. Tests d'intégration thermodynamique cargo test --test refrigeration_cycle_integration cargo test --test mass_balance_integration # 3. Validation clippy stricte cargo clippy -- -D warnings # 4. Benchmark performance cargo bench # 5. Test de simulation complète cargo run --example simple_cycle ``` --- ## Métriques de Succès | Métrique | Avant | Après | |----------|-------|-------| | Types duplicats | 2 (`CircuitId`, `FluidId`) | 0 | | Composants sans `energy_transfers()` | 5 | 0 | | Composants sans `port_enthalpies()` | 5 | 0 | | Lignes dans `solver.rs` | ~2800 | ~500 | | Couverture validation énergie | Partielle | Complète | --- ## Annexes ### A. Liste Complète des Composants et Méthodes | Composant | `port_mass_flows()` | `port_enthalpies()` | `energy_transfers()` | |-----------|---------------------|---------------------|----------------------| | Compressor | ✅ | ✅ | ✅ | | ExpansionValve | ✅ | ❌ → ✅ | ❌ → ✅ | | Pipe | ✅ | ✅ | ✅ | | Pump | ✅ | ✅ | ✅ | | Fan | ✅ | ✅ | ✅ | | RefrigerantSource | ✅ | ❌ → ✅ | ❌ → ✅ | | RefrigerantSink | ✅ | ❌ → ✅ | ❌ → ✅ | | FlowSplitter | ✅ | ❌ → ✅ | ❌ → ✅ | | FlowMerger | ✅ | ❌ → ✅ | ❌ → ✅ | | HeatExchanger | ✅ | ✅ | ✅ | | Evaporator | ✅ | ✅ | ✅ | | Condenser | ✅ | ✅ | ✅ | | Economizer | ✅ | ✅ | ✅ | | EvaporatorCoil | ✅ | ✅ | ✅ | | CondenserCoil | ✅ | ✅ | ✅ | ### B. Références Architecture - [Architecture.md - Component Model](../planning-artifacts/architecture.md#Component-Model) - [Architecture.md - Error Handling](../planning-artifacts/architecture.md#Error-Handling-Strategy) - [Architecture.md - Project Structure](../planning-artifacts/architecture.md#Project-Structure) - [PRD - FR35-FR39 Validation Requirements](../planning-artifacts/prd.md#Validation) --- *Document généré par audit automatique - 2026-02-22*