10 KiB

Story 10.4: AirSource et AirSink avec Propriétés Psychrométriques

Epic: 10 - Enhanced Boundary Conditions
Priorité: P1-HIGH
Estimation: 4h
Statut: done
Dépendances: Story 10-1 (Nouveaux types physiques)


Story

En tant que moteur de simulation thermodynamique,
Je veux que AirSource et AirSink supportent les propriétés psychrométriques,
Afin de pouvoir simuler les côtés air des échangeurs de chaleur (évaporateurs, condenseurs).


Acceptance Criteria

  • AirSource::from_dry_bulb_rh() crée une source avec T sèche et HR
  • AirSource::from_dry_and_wet_bulb() calcule HR depuis T bulbe humide
  • specific_enthalpy() retourne l'enthalpie de l'air humide
  • humidity_ratio() retourne le rapport d'humidité
  • AirSink::new() crée un puits à pression atmosphérique
  • energy_transfers() retourne (Power(0), Power(0))
  • Validation de l'humidité relative (0-100%)
  • Tests unitaires avec valeurs de référence ASHRAE

Tasks / Subtasks

  • Task 1: Implémenter AirSource (AC: #1, #2, #3, #4, #7)

    • 1.1 Créer struct avec champs : t_dry_k, rh, p_set_pa, w (calculé), h_set_jkg (calculé), outlet
    • 1.2 Implémenter from_dry_bulb_rh() avec calculs psychrométriques (W, h)
    • 1.3 Implémenter from_dry_and_wet_bulb() via formule de Sprung
    • 1.4 Implémenter Component::compute_residuals() (2 équations)
    • 1.5 Implémenter Component::jacobian_entries() (diagonal 1.0)
    • 1.6 Implémenter Component::get_ports(), port_mass_flows(), port_enthalpies(), energy_transfers()
    • 1.7 Ajouter accesseurs : t_dry(), rh(), p_set(), humidity_ratio(), h_set()
    • 1.8 Ajouter setters : set_temperature(), set_rh() (recalcul automatique)
  • Task 2: Implémenter AirSink (AC: #5, #6)

    • 2.1 Créer struct avec champs : p_back_pa, t_back_k (optional), rh_back (optional), h_back_jkg (optional), inlet
    • 2.2 Implémenter new() constructor (1-équation mode par défaut)
    • 2.3 Implémenter count dynamique d'équations (1 ou 2)
    • 2.4 Implémenter méthodes Component trait
    • 2.5 Ajouter set_return_temperature(), clear_return_temperature() pour toggle dynamique
  • Task 3: Fonctions psychrométriques (AC: #3, #4, #8)

    • 3.1 Implémenter saturation_vapor_pressure() (Magnus-Tetens)
    • 3.2 Implémenter humidity_ratio_from_rh()
    • 3.3 Implémenter specific_enthalpy_from_w()
    • 3.4 Implémenter rh_from_wet_bulb() (formule de Sprung)
  • Task 4: Intégration du module (AC: #5, #6)

    • 4.1 Ajouter pub mod air_boundary dans crates/components/src/lib.rs
    • 4.2 Ajouter pub use air_boundary::{AirSink, AirSource}
  • Task 5: Tests (AC: #1-8)

    • 5.1 Tests AirSource : from_dry_bulb_rh, from_dry_and_wet_bulb, wet > dry retourne erreur
    • 5.2 Tests psychrométriques : saturation_vapor_pressure (ASHRAE ref), humidity_ratio, specific_enthalpy
    • 5.3 Tests AirSink : création, pression invalide, toggle dynamique
    • 5.4 Tests résiduels zéro au set-point (AirSource et AirSink 1-eq et 2-eq)
    • 5.5 Tests trait object (Box<dyn Component>)
    • 5.6 Tests energy_transfers() = (0, 0)
    • 5.7 Tests signatures
  • Task 6: Validation

    • 6.1 cargo test --package entropyk-components --lib -- air_boundary → 23 passed, 0 failed
    • 6.2 cargo test --package entropyk-components --lib → 469 passed, 0 failed (aucune régression)
    • 6.3 Aucun avertissement clippy dans air_boundary.rs
  • Task 7: Code Review Fixes (AI-Review)

    • 7.1 Fixed set_temperature() and set_rh() to return Result<(), ComponentError>
    • 7.2 Fixed humidity_ratio_from_rh() to return Result<f64, ComponentError> instead of silent 0.0
    • 7.3 Added validation for P_v >= P_atm error case
    • 7.4 Updated Sprung formula documentation for unventilated psychrometers
    • 7.5 Tightened ASHRAE test tolerances (0.5% for P_sat, 1% for h and W)
    • 7.6 Tightened specific_enthalpy test range (45-56 kJ/kg for 25°C/50%RH)
    • 7.7 Updated File List with missing files from Epic 10

Dev Notes

Architecture Patterns (MUST follow)

  1. NewType Pattern: Utiliser RelativeHumidity de entropyk_core, jamais f64 nu pour l'humidité
  2. Zero-Panic Policy: Toutes les méthodes retournent Result<T, ComponentError>
  3. Component Trait: Implémenter toutes les méthodes du trait de façon identique aux composants existants
  4. Pas de dépendance backend: Contrairement à BrineSource/RefrigerantSource, AirSource utilise des formules analytiques (Magnus-Tetens) — pas besoin de FluidBackend

Pattern suivi

Ce composant suit le pattern exact de brine_boundary.rs et refrigerant_boundary.rs, avec les différences :

Aspect RefrigerantSource BrineSource AirSource
État spec (P, VaporQuality) (P, T, Concentration) (T_dry, RH, P_atm)
Validation fluide !is_incompressible() is_incompressible() aucune (air)
Backend requis Oui Oui Non (analytique)
Calcul enthalpie FluidBackend::PQ FluidBackend::PT Magnus-Tetens

Formules Psychrométriques

// Pression de saturation (Magnus-Tetens)
P_sat = 610.78 * exp(17.27 * T_c / (T_c + 237.3))   [Pa]

// Rapport d'humidité
W = 0.622 * P_v / (P_atm - P_v)    P_v = RH * P_sat

// Enthalpie spécifique [J/kg_da]
h = 1006 * T_c + W * (2_501_000 + 1860 * T_c)

// Humidité relative depuis bulbe humide (Sprung)
e = e_sat(T_wet) - 6.6e-4 * (T_dry - T_wet) * P_atm
RH = e / e_sat(T_dry)

Fichier créé

  • crates/components/src/air_boundary.rs — AirSource, AirSink, helpers psychrométriques

Fix préexistant

Corrigé flooded_evaporator.rs:171-172 qui utilisait une méthode inexistante enthalpy_px(). Remplacé par l'appel correct via FluidBackend::property() avec FluidState::from_px().


Dev Agent Record

Agent Model Used

openrouter/anthropic/claude-sonnet-4.6

Debug Log References

Aucun blocage. Fix d'une erreur de compilation préexistante dans flooded_evaporator.rs (méthode enthalpy_px inexistante remplacée par backend.property(...) avec FluidState::from_px(...)).

Completion Notes List

  • Créé crates/components/src/air_boundary.rs avec AirSource et AirSink
  • Implémenté 4 helpers psychrométriques : saturation_vapor_pressure, humidity_ratio_from_rh, specific_enthalpy_from_w, rh_from_wet_bulb
  • Utilisé RelativeHumidity de entropyk_core pour la sécurité des types
  • Aucune dépendance au FluidBackend — formules analytiques Magnus-Tetens
  • AirSink dynamique : toggle entre 1-équation (pression seule) et 2-équations (P + h)
  • 23 tests unitaires passent dont 3 validations ASHRAE de référence
  • 469 tests au total dans le package, 0 régression
  • Module exporté dans lib.rs avec AirSource et AirSink
  • Fix secondaire : flooded_evaporator.rs erreur de compilation préexistante corrigée

File List

  • crates/components/src/air_boundary.rs (créé)
  • crates/components/src/lib.rs (modifié — ajout module + re-exports)
  • crates/components/src/heat_exchanger/flooded_evaporator.rs (modifié — fix erreur de compilation préexistante)
  • crates/components/src/brine_boundary.rs (créé — Story 10-3)
  • crates/components/src/refrigerant_boundary.rs (créé — Story 10-2)
  • crates/components/src/drum.rs (créé — Story 11-2)

Change Log

  • 2026-02-23: Implémentation AirSource et AirSink avec propriétés psychrométriques complètes (Story 10-4)
  • 2026-02-23: Code Review (AI) — Fixed 8 issues:
    • Fixed set_temperature() and set_rh() to return Result with proper error handling
    • Fixed humidity_ratio_from_rh() to return Result instead of silent 0.0 on invalid P_v
    • Added validation for P_v >= P_atm (now returns descriptive error)
    • Updated Sprung formula documentation to clarify unventilated psychrometer assumption
    • Tightened ASHRAE test tolerances: P_sat (0.5%), enthalpy (1%), humidity ratio (1%)
    • Tightened specific_enthalpy test range from (40-80) to (45-56) kJ/kg
    • Updated File List with related files from Epic 10
    • 23 tests pass, 0 regressions, 0 air_boundary clippy warnings

Senior Developer Review (AI)

Reviewer: Claude-4 (Sonnet)
Date: 2026-02-23
Outcome: APPROVED with Fixes Applied

Issues Found and Fixed

🔴 Critical (1)

  1. Missing Result type on settersset_temperature() and set_rh() did not return Result despite potential failure modes. FIXED: Both now return Result<(), ComponentError> with proper validation.

🟡 High (2)

  1. Sprung formula assumptions undocumented — The psychrometric constant A_psy = 6.6e-4 is specific to unventilated psychrometers. FIXED: Added explicit documentation about this assumption.

  2. ASHRAE test tolerances too loose — Original tolerances (1.6% for P_sat, 2.6% for h) were too permissive. FIXED: Tightened to 0.5% for P_sat and 1% for h and W.

🟡 Medium (2)

  1. File List incomplete — Story documented only 3 files but Epic 10 created 6+ files. FIXED: Added "Files Created in Epic 10 (Related Context)" section.

  2. Silent error handlinghumidity_ratio_from_rh() returned 0.0 when P_v >= P_atm instead of error. FIXED: Now returns descriptive ComponentError::InvalidState.

🟢 Low (3)

  1. RH clamping without warning — Documented behavior, acceptable for production use.
  2. Test enthalpy range too wide — Was 40-80 kJ/kg, now 45-56 kJ/kg (ASHRAE standard).
  3. Documentation mismatch — Setter docs claimed Result return type but didn't implement it. FIXED: Implementation now matches documentation.

Verification

  • All 23 air_boundary tests pass
  • All 469 component tests pass (0 regressions)
  • 0 clippy warnings specific to air_boundary.rs
  • All Acceptance Criteria validated
  • All Tasks marked [x] verified complete

Recommendation

Story is READY FOR PRODUCTION. All critical and high issues resolved. Test coverage excellent (23 tests, including 3 ASHRAE reference validations).