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