551 lines
16 KiB
Markdown
551 lines
16 KiB
Markdown
# 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<u8>` 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<Connected> {
|
|
// ... existing code ...
|
|
|
|
fn port_enthalpies(
|
|
&self,
|
|
_state: &SystemState,
|
|
) -> Result<Vec<entropyk_core::Enthalpy>, 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 - FlowSource/FlowSink Energy Methods
|
|
|
|
**Priorité:** P1-CRITIQUE
|
|
**Estimation:** 3h
|
|
**Dépendances:** Story 9.2
|
|
|
|
**Story:**
|
|
> En tant que moteur de simulation thermodynamique,
|
|
> Je veux que `FlowSource` et `FlowSink` 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:**
|
|
- `FlowSource` et `FlowSink` implémentent seulement `port_mass_flows()`
|
|
- Ces composants sont ignorés dans la validation
|
|
|
|
**Solution proposée:**
|
|
|
|
```rust
|
|
// Dans crates/components/src/flow_boundary.rs
|
|
|
|
impl Component for FlowSource {
|
|
// ... existing code ...
|
|
|
|
fn port_enthalpies(
|
|
&self,
|
|
_state: &SystemState,
|
|
) -> Result<Vec<entropyk_core::Enthalpy>, 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 FlowSink {
|
|
// ... existing code ...
|
|
|
|
fn port_enthalpies(
|
|
&self,
|
|
_state: &SystemState,
|
|
) -> Result<Vec<entropyk_core::Enthalpy>, 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/flow_boundary.rs`
|
|
|
|
**Critères d'acceptation:**
|
|
- [ ] `FlowSource` et `FlowSink` 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<Vec<entropyk_core::Enthalpy>, 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<Vec<entropyk_core::Enthalpy>, 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<f64>;
|
|
```
|
|
|
|
**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<f64>,
|
|
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<Pressure> {
|
|
self.data.get(edge_idx * 2).map(|&p| Pressure::from_pascals(p))
|
|
}
|
|
|
|
pub fn enthalpy(&self, edge_idx: usize) -> Option<Enthalpy> {
|
|
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 FlowSource/FlowSink 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 | ✅ | ✅ | ✅ |
|
|
| FlowSource | ✅ | ❌ → ✅ | ❌ → ✅ |
|
|
| FlowSink | ✅ | ❌ → ✅ | ❌ → ✅ |
|
|
| 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*
|