Entropyk/_bmad-output/implementation-artifacts/11-11-vendorbackend-trait.md

10 KiB

Story 11.11: VendorBackend Trait

Status: done

Story

As a system integrator, I want a standardized VendorBackend trait and data types for manufacturer equipment data, so that I can load compressor coefficients and heat exchanger parameters from Copeland, SWEP, Danfoss, and Bitzer catalogs through a uniform API.

Acceptance Criteria

  1. Given a new entropyk-vendors crate When I add it to the workspace Then it compiles with cargo build and is available to entropyk-components

  2. Given the VendorBackend trait When I implement it for a vendor Then it provides list_compressor_models(), get_compressor_coefficients(), list_bphx_models(), get_bphx_parameters(), and an optional compute_ua() with default implementation

  3. Given a CompressorCoefficients struct When deserialized from JSON Then it contains: model, manufacturer, refrigerant, capacity_coeffs: [f64; 10], power_coeffs: [f64; 10], optional mass_flow_coeffs, and ValidityRange

  4. Given a BphxParameters struct When deserialized from JSON Then it contains: model, manufacturer, num_plates, area, dh, chevron_angle, ua_nominal, and optional UaCurve

  5. Given a VendorError enum When error conditions arise Then variants ModelNotFound, InvalidFormat, FileNotFound, ParseError, IoError are returned with descriptive messages

  6. Given unit tests When cargo test -p entropyk-vendors is run Then serialization round-trips, trait mock implementation, UA curve interpolation, and error handling all pass

Tasks / Subtasks

  • Task 1: Create crates/vendors/ crate scaffold (AC: 1)
    • Subtask 1.1: Create crates/vendors/Cargo.toml with serde, serde_json, thiserror deps
    • Subtask 1.2: Add "crates/vendors" to workspace members in root Cargo.toml
    • Subtask 1.3: Create src/lib.rs with module declarations and public re-exports
  • Task 2: Define VendorError enum (AC: 5)
    • Subtask 2.1: Create src/error.rs with thiserror-derived error variants
  • Task 3: Define data types (AC: 3, 4)
    • Subtask 3.1: Create CompressorCoefficients, CompressorValidityRange in src/vendor_api.rs
    • Subtask 3.2: Create BphxParameters, UaCurve, UaCalcParams in src/vendor_api.rs
    • Subtask 3.3: Derive Serialize, Deserialize, Debug, Clone on all data structs
  • Task 4: Define VendorBackend trait (AC: 2)
    • Subtask 4.1: Implement trait with 5 required methods and 1 default method in src/vendor_api.rs
    • Subtask 4.2: Ensure trait is object-safe (Send + Sync bounds)
  • Task 5: Create data/ directory structure for future parsers (AC: 1)
    • Subtask 5.1: Create placeholder directories: data/copeland/compressors/, data/swep/bphx/, data/danfoss/, data/bitzer/
    • Subtask 5.2: Create empty data/copeland/compressors/index.json with []
  • Task 6: Create stub module files for future parser stories (AC: 1)
    • Subtask 6.1: Create src/compressors/mod.rs with commented-out vendor imports
    • Subtask 6.2: Create src/heat_exchangers/mod.rs with commented-out vendor imports
  • Task 7: Write unit tests (AC: 6)
    • Subtask 7.1: Test CompressorCoefficients JSON round-trip serialization
    • Subtask 7.2: Test BphxParameters JSON round-trip serialization (with and without UaCurve)
    • Subtask 7.3: Test UaCurve interpolation logic (linear interpolation between points)
    • Subtask 7.4: Test VendorError display messages
    • Subtask 7.5: Test mock VendorBackend implementation (list, get, error cases)
    • Subtask 7.6: Test default compute_ua returns ua_nominal

Dev Notes

Architecture

New crate: entropyk-vendors — standalone crate with NO dependency on entropyk-core, entropyk-fluids, or entropyk-components. This is intentional: vendor data is pure data structures + I/O, not the thermodynamic engine.

Integration point: Stories 11.12-15 will implement VendorBackend for each vendor. The entropyk-components crate (or entropyk facade) can depend on entropyk-vendors to consume coefficients at system-build time.

Trait design:

pub trait VendorBackend: Send + Sync {
    fn vendor_name(&self) -> &str;
    fn list_compressor_models(&self) -> Result<Vec<String>, VendorError>;
    fn get_compressor_coefficients(&self, model: &str) -> Result<CompressorCoefficients, VendorError>;
    fn list_bphx_models(&self) -> Result<Vec<String>, VendorError>;
    fn get_bphx_parameters(&self, model: &str) -> Result<BphxParameters, VendorError>;
    fn compute_ua(&self, model: &str, params: &UaCalcParams) -> Result<f64, VendorError> {
        let bphx = self.get_bphx_parameters(model)?;
        Ok(bphx.ua_nominal)
    }
}

File Structure

crates/vendors/
├── Cargo.toml
├── data/
│   ├── copeland/compressors/index.json   # empty array for now
│   ├── swep/bphx/
│   ├── danfoss/
│   └── bitzer/
└── src/
    ├── lib.rs              # re-exports
    ├── error.rs            # VendorError enum
    ├── vendor_api.rs       # VendorBackend trait + data types
    ├── compressors/
    │   └── mod.rs          # stub for future Copeland/Danfoss/Bitzer parsers
    └── heat_exchangers/
        └── mod.rs          # stub for future SWEP parser

Key Dependencies (Cargo.toml)

[package]
name = "entropyk-vendors"
version.workspace = true
authors.workspace = true
edition.workspace = true

[dependencies]
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
thiserror = "1.0"

[dev-dependencies]
approx = "0.5"

Technical Constraints

  • No unwrap()/expect() — return Result<_, VendorError> everywhere
  • No println! — use tracing if logging is needed (unlikely in this story)
  • CompressorCoefficients.capacity_coeffs and power_coeffs use [f64; 10] (fixed-size AHRI 540 standard)
  • mass_flow_coeffs is Option<[f64; 10]> since not all vendors provide it
  • UaCurve.points is Vec<(f64, f64)> — mass_flow_ratio vs ua_ratio, linearly interpolated
  • All structs must implement Debug + Clone + Serialize + Deserialize
  • Trait must be object-safe for Box<dyn VendorBackend>

AHRI 540 Coefficient Convention

The 10 coefficients follow the polynomial form:

C = a₀ + a₁·Ts + a₂·Td + a₃·Ts² + a₄·Ts·Td + a₅·Td²
  + a₆·Ts³ + a₇·Td·Ts² + a₈·Ts·Td² + a₉·Td³

Where Ts = suction saturation temperature (°C), Td = discharge saturation temperature (°C). This applies to both capacity_coeffs (W) and power_coeffs (W).

Previous Story Intelligence

Story 11-10 (MovingBoundaryHX Cache):

  • Used Cell for interior mutability inside compute_residuals(&self)
  • All tests pass with cargo test -p entropyk-components
  • Performance benchmark showed >100x speedup
  • Files modified: crates/components/src/heat_exchanger/moving_boundary_hx.rs, exchanger.rs

Existing compressor implementation at crates/components/src/compressor.rs (77KB) already uses AHRI 540 coefficients internally. The CompressorCoefficients struct in this crate should be compatible with the existing compressor configuration so vendors can feed data directly into it.

Project Structure Notes

  • New crate follows workspace conventions: crates/vendors/
  • Workspace root Cargo.toml needs "crates/vendors" in members
  • No impact on existing crates — this is purely additive
  • No Python bindings needed for this story (future story scope)

References

Dev Agent Record

Agent Model Used

Antigravity (Gemini)

Debug Log References

Review Follow-ups (AI)

  • [AI-Review][High] UaCurve::interpolate sorting algorithm addition for parsed data.
  • [AI-Review][Medium] CompressorValidityRange checking during parsing.
  • [AI-Review][Medium] UaCalcParams missing derive bounds (Debug, Clone).
  • [AI-Review][Low] VendorError::IoError missing file path tracking context.
  • [AI-Review][Low] entropyk-vendors lib.rs missing standard #![warn(missing_docs)].

Completion Notes List

  • Created crates/vendors/ crate scaffold with Cargo.toml, src/lib.rs, module re-exports
  • Implemented VendorError enum in src/error.rs with 5 thiserror-derived variants
  • Implemented CompressorCoefficients, CompressorValidityRange, BphxParameters, UaCurve, UaCalcParams in src/vendor_api.rs
  • Implemented UaCurve::interpolate() with linear interpolation and endpoint clamping
  • Implemented VendorBackend trait with 5 required methods + 1 default compute_ua in src/vendor_api.rs
  • Created stub modules src/compressors/mod.rs and src/heat_exchangers/mod.rs
  • Created data/ directory structure with copeland/compressors/index.json, swep/bphx/, danfoss/, bitzer/
  • Added crate to workspace members in root Cargo.toml
  • Wrote 20 unit tests covering: serialization (4), interpolation (4), error display (3), mock backend (7), default compute_ua (2), object safety (1)
  • All 20 tests pass, all core workspace crates build cleanly
  • Pre-existing entropyk-python build error (missing verbose_config) is unrelated to this story

File List

  • crates/vendors/Cargo.toml (new)
  • crates/vendors/src/lib.rs (new)
  • crates/vendors/src/error.rs (new)
  • crates/vendors/src/vendor_api.rs (new)
  • crates/vendors/src/compressors/mod.rs (new)
  • crates/vendors/src/heat_exchangers/mod.rs (new)
  • crates/vendors/data/copeland/compressors/index.json (new)
  • crates/vendors/data/swep/bphx/ (new directory)
  • crates/vendors/data/danfoss/ (new directory)
  • crates/vendors/data/bitzer/ (new directory)
  • Cargo.toml (modified — added crates/vendors to workspace members)