feat(components): add ThermoState generators and Eurovent backend demo
This commit is contained in:
355
README_STORY_1_3.md
Normal file
355
README_STORY_1_3.md
Normal file
@@ -0,0 +1,355 @@
|
||||
# Entropyk - Story 1.3: Port and Connection System
|
||||
|
||||
## 🎯 Ce qui a été implémenté
|
||||
|
||||
Cette story implémente le système de ports et connexions pour les composants thermodynamiques avec le **Type-State pattern** pour la sécurité à la compilation.
|
||||
|
||||
### Fonctionnalités principales
|
||||
|
||||
- ✅ `Port<State>` - Structure générique avec états `Disconnected` et `Connected`
|
||||
- ✅ `FluidId` - Identification des fluides pour validation de compatibilité
|
||||
- ✅ `ConnectionError` - Gestion d'erreurs avec `thiserror`
|
||||
- ✅ Validation des connexions (compatibilité fluide, continuité pression/enthalpie)
|
||||
- ✅ Extension du trait `Component` avec `get_ports()`
|
||||
|
||||
## 🚀 Instructions de test
|
||||
|
||||
### 1. Prérequis
|
||||
|
||||
```bash
|
||||
# Vérifier que Rust est installé
|
||||
rustc --version
|
||||
cargo --version
|
||||
```
|
||||
|
||||
### 2. Cloner/Naviguer dans le projet
|
||||
|
||||
```bash
|
||||
cd /Users/sepehr/dev/Entropyk
|
||||
```
|
||||
|
||||
### 3. Compiler le projet
|
||||
|
||||
```bash
|
||||
# Compiler tout le workspace
|
||||
cargo build --workspace
|
||||
|
||||
# Compiler en mode release (optimisé)
|
||||
cargo build --workspace --release
|
||||
```
|
||||
|
||||
### 4. Exécuter tous les tests
|
||||
|
||||
```bash
|
||||
# Tests complets du workspace
|
||||
cargo test --workspace
|
||||
|
||||
# Tests avec sortie détaillée
|
||||
cargo test --workspace -- --nocapture
|
||||
|
||||
# Tests du crate components uniquement
|
||||
cargo test -p entropyk-components
|
||||
|
||||
# Tests du crate core uniquement
|
||||
cargo test -p entropyk-core
|
||||
```
|
||||
|
||||
### 5. Vérifier la qualité du code
|
||||
|
||||
```bash
|
||||
# Clippy (linting strict)
|
||||
cargo clippy --workspace -- -D warnings
|
||||
|
||||
# Formatage du code
|
||||
cargo fmt --workspace
|
||||
|
||||
# Vérifier la documentation
|
||||
cargo doc --workspace --open
|
||||
```
|
||||
|
||||
## 🧪 Tests manuels
|
||||
|
||||
### Test 1: Création et connexion de ports
|
||||
|
||||
Créez un fichier `test_ports.rs` à la racine du projet :
|
||||
|
||||
```rust
|
||||
use entropyk_components::port::{Port, Disconnected, Connected, FluidId, ConnectionError};
|
||||
use entropyk_core::{Pressure, Enthalpy};
|
||||
|
||||
fn main() -> Result<(), ConnectionError> {
|
||||
println!("=== Test de création de ports ===");
|
||||
|
||||
// Créer deux ports déconnectés
|
||||
let port1 = Port::new(
|
||||
FluidId::new("R134a"),
|
||||
Pressure::from_bar(1.0),
|
||||
Enthalpy::from_joules_per_kg(400_000.0)
|
||||
);
|
||||
|
||||
let port2 = Port::new(
|
||||
FluidId::new("R134a"),
|
||||
Pressure::from_bar(1.0),
|
||||
Enthalpy::from_joules_per_kg(400_000.0)
|
||||
);
|
||||
|
||||
println!("Port 1 créé: fluid={:?}, pressure={:.2} Pa, enthalpy={:.2} J/kg",
|
||||
port1.fluid_id(),
|
||||
port1.pressure().to_pascals(),
|
||||
port1.enthalpy().to_joules_per_kg()
|
||||
);
|
||||
|
||||
println!("Port 2 créé: fluid={:?}, pressure={:.2} Pa, enthalpy={:.2} J/kg",
|
||||
port2.fluid_id(),
|
||||
port2.pressure().to_pascals(),
|
||||
port2.enthalpy().to_joules_per_kg()
|
||||
);
|
||||
|
||||
// Connecter les ports
|
||||
println!("\n=== Connexion des ports ===");
|
||||
let (mut connected1, mut connected2) = port1.connect(port2)?;
|
||||
|
||||
println!("✅ Ports connectés avec succès!");
|
||||
println!("Connected 1: pressure={:.2} Pa, enthalpy={:.2} J/kg",
|
||||
connected1.pressure().to_pascals(),
|
||||
connected1.enthalpy().to_joules_per_kg()
|
||||
);
|
||||
|
||||
// Modifier les valeurs
|
||||
println!("\n=== Modification des valeurs ===");
|
||||
connected1.set_pressure(Pressure::from_bar(1.5));
|
||||
connected1.set_enthalpy(Enthalpy::from_joules_per_kg(450_000.0));
|
||||
|
||||
println!("Port 1 modifié: pressure={:.2} Pa, enthalpy={:.2} J/kg",
|
||||
connected1.pressure().to_pascals(),
|
||||
connected1.enthalpy().to_joules_per_kg()
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
```
|
||||
|
||||
Pour l'exécuter, ajoutez ce binary dans `Cargo.toml`:
|
||||
|
||||
```toml
|
||||
[[bin]]
|
||||
name = "test_ports"
|
||||
path = "test_ports.rs"
|
||||
```
|
||||
|
||||
Puis:
|
||||
```bash
|
||||
cargo run --bin test_ports
|
||||
```
|
||||
|
||||
### Test 2: Test d'erreurs
|
||||
|
||||
```rust
|
||||
use entropyk_components::port::{Port, FluidId, ConnectionError};
|
||||
use entropyk_core::{Pressure, Enthalpy};
|
||||
|
||||
fn main() {
|
||||
println!("=== Test des erreurs de connexion ===\n");
|
||||
|
||||
// Test 1: Fluides incompatibles
|
||||
println!("Test 1: Fluides incompatibles");
|
||||
let port1 = Port::new(
|
||||
FluidId::new("R134a"),
|
||||
Pressure::from_pascals(100_000.0),
|
||||
Enthalpy::from_joules_per_kg(400_000.0)
|
||||
);
|
||||
let port2 = Port::new(
|
||||
FluidId::new("Water"),
|
||||
Pressure::from_pascals(100_000.0),
|
||||
Enthalpy::from_joules_per_kg(400_000.0)
|
||||
);
|
||||
|
||||
match port1.connect(port2) {
|
||||
Err(ConnectionError::IncompatibleFluid { from, to }) => {
|
||||
println!("✅ Erreur capturée: Cannot connect {} to {}", from, to);
|
||||
}
|
||||
_ => println!("❌ Erreur non capturée!"),
|
||||
}
|
||||
|
||||
// Test 2: Pression différente
|
||||
println!("\nTest 2: Pression différente");
|
||||
let port3 = Port::new(
|
||||
FluidId::new("R134a"),
|
||||
Pressure::from_pascals(100_000.0),
|
||||
Enthalpy::from_joules_per_kg(400_000.0)
|
||||
);
|
||||
let port4 = Port::new(
|
||||
FluidId::new("R134a"),
|
||||
Pressure::from_pascals(200_000.0),
|
||||
Enthalpy::from_joules_per_kg(400_000.0)
|
||||
);
|
||||
|
||||
match port3.connect(port4) {
|
||||
Err(ConnectionError::PressureMismatch { from_pressure, to_pressure }) => {
|
||||
println!("✅ Erreur capturée: Pressure mismatch {} vs {}",
|
||||
from_pressure, to_pressure);
|
||||
}
|
||||
_ => println!("❌ Erreur non capturée!"),
|
||||
}
|
||||
|
||||
// Test 3: Connexion réussie
|
||||
println!("\nTest 3: Connexion réussie");
|
||||
let port5 = Port::new(
|
||||
FluidId::new("R134a"),
|
||||
Pressure::from_pascals(100_000.0),
|
||||
Enthalpy::from_joules_per_kg(400_000.0)
|
||||
);
|
||||
let port6 = Port::new(
|
||||
FluidId::new("R134a"),
|
||||
Pressure::from_pascals(100_000.0),
|
||||
Enthalpy::from_joules_per_kg(400_000.0)
|
||||
);
|
||||
|
||||
match port5.connect(port6) {
|
||||
Ok(_) => println!("✅ Connexion réussie!"),
|
||||
Err(_) => println!("❌ Connexion échouée!"),
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Test 3: Vérification Type-State (doit échouer à la compilation)
|
||||
|
||||
Créez ce fichier pour vérifier que la sécurité à la compilation fonctionne:
|
||||
|
||||
```rust
|
||||
use entropyk_components::port::{Port, Disconnected};
|
||||
use entropyk_core::{Pressure, Enthalpy, FluidId};
|
||||
|
||||
fn main() {
|
||||
// Créer un port déconnecté
|
||||
let port: Port<Disconnected> = Port::new(
|
||||
FluidId::new("R134a"),
|
||||
Pressure::from_bar(1.0),
|
||||
Enthalpy::from_joules_per_kg(400_000.0)
|
||||
);
|
||||
|
||||
// Cette ligne doit provoquer une erreur de compilation:
|
||||
// error[E0599]: no method named `pressure` found for struct `Port<Disconnected>`
|
||||
// let _p = port.pressure();
|
||||
|
||||
println!("Si vous décommentez la ligne ci-dessus, la compilation échouera!");
|
||||
println!("Cela prouve que le Type-State pattern fonctionne.");
|
||||
}
|
||||
```
|
||||
|
||||
## 📊 Structure des fichiers
|
||||
|
||||
```
|
||||
entropyk/
|
||||
├── Cargo.toml # Workspace root
|
||||
├── crates/
|
||||
│ ├── components/ # Crate components (modifié)
|
||||
│ │ ├── Cargo.toml # + Dépendances ajoutées
|
||||
│ │ └── src/
|
||||
│ │ ├── lib.rs # + Trait Component étendu
|
||||
│ │ └── port.rs # NOUVEAU: Implémentation ports
|
||||
│ └── core/ # Crate core (existant)
|
||||
│ └── src/
|
||||
│ └── types.rs # Pressure, Enthalpy, etc.
|
||||
└── _bmad-output/
|
||||
└── implementation-artifacts/
|
||||
└── 1-3-port-and-connection-system.md # Story document
|
||||
```
|
||||
|
||||
## 🔍 Points clés de l'implémentation
|
||||
|
||||
### Type-State Pattern
|
||||
|
||||
```rust
|
||||
// Disconnected et Connected sont des marqueurs de type vides
|
||||
pub struct Disconnected;
|
||||
pub struct Connected;
|
||||
|
||||
// Port est générique sur l'état
|
||||
pub struct Port<State> {
|
||||
fluid_id: FluidId,
|
||||
pressure: Pressure,
|
||||
enthalpy: Enthalpy,
|
||||
_state: PhantomData<State>, // Marqueur zéro-cost
|
||||
}
|
||||
|
||||
// Seuls les ports Disconnected peuvent être connectés
|
||||
impl Port<Disconnected> {
|
||||
pub fn connect(self, other: Port<Disconnected>)
|
||||
-> Result<(Port<Connected>, Port<Connected>), ConnectionError> {
|
||||
// Validation et connexion...
|
||||
}
|
||||
}
|
||||
|
||||
// Seuls les ports Connected exposent les méthodes de lecture/écriture
|
||||
impl Port<Connected> {
|
||||
pub fn pressure(&self) -> Pressure { self.pressure }
|
||||
pub fn set_pressure(&mut self, pressure: Pressure) { self.pressure = pressure }
|
||||
}
|
||||
```
|
||||
|
||||
### Validation des connexions
|
||||
|
||||
```rust
|
||||
pub fn connect(self, other: Port<Disconnected>)
|
||||
-> Result<(Port<Connected>, Port<Connected>), ConnectionError>
|
||||
{
|
||||
// 1. Validation du fluide
|
||||
if self.fluid_id != other.fluid_id {
|
||||
return Err(ConnectionError::IncompatibleFluid { ... });
|
||||
}
|
||||
|
||||
// 2. Validation de la continuité de pression
|
||||
if pressure_diff > 1e-6 {
|
||||
return Err(ConnectionError::PressureMismatch { ... });
|
||||
}
|
||||
|
||||
// 3. Validation de la continuité d'enthalpie
|
||||
if enthalpy_diff > 1e-6 {
|
||||
return Err(ConnectionError::EnthalpyMismatch { ... });
|
||||
}
|
||||
|
||||
// Création des ports connectés avec valeurs moyennées
|
||||
Ok((connected1, connected2))
|
||||
}
|
||||
```
|
||||
|
||||
## 🐛 Dépannage
|
||||
|
||||
### Erreur: `unresolved import entropyk_core`
|
||||
|
||||
Solution: Vérifier que la dépendance est bien dans `crates/components/Cargo.toml`:
|
||||
|
||||
```toml
|
||||
[dependencies]
|
||||
entropyk-core = { path = "../core" }
|
||||
```
|
||||
|
||||
### Erreur: `approx crate not found`
|
||||
|
||||
Solution: Vérifier la dev-dependency:
|
||||
|
||||
```toml
|
||||
[dev-dependencies]
|
||||
approx = "0.5"
|
||||
```
|
||||
|
||||
### Tests qui échouent
|
||||
|
||||
```bash
|
||||
# Nettoyer et reconstruire
|
||||
cargo clean
|
||||
cargo test --workspace
|
||||
```
|
||||
|
||||
## 📚 Ressources
|
||||
|
||||
- [Rust Type-State Pattern](https://rust-unofficial.github.io/patterns/patterns/behavioural/phantom-types.html)
|
||||
- [thiserror documentation](https://docs.rs/thiserror/)
|
||||
- [approx documentation](https://docs.rs/approx/) (pour les assertions flottantes)
|
||||
|
||||
---
|
||||
|
||||
**Date d'implémentation**: 2026-02-14
|
||||
**Story**: 1.3 - Port and Connection System
|
||||
**Statut**: ✅ Complété et testé
|
||||
Reference in New Issue
Block a user