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
-
Given a new
entropyk-vendorscrate When I add it to the workspace Then it compiles withcargo buildand is available toentropyk-components -
Given the
VendorBackendtrait When I implement it for a vendor Then it provideslist_compressor_models(),get_compressor_coefficients(),list_bphx_models(),get_bphx_parameters(), and an optionalcompute_ua()with default implementation -
Given a
CompressorCoefficientsstruct When deserialized from JSON Then it contains:model,manufacturer,refrigerant,capacity_coeffs: [f64; 10],power_coeffs: [f64; 10], optionalmass_flow_coeffs, andValidityRange -
Given a
BphxParametersstruct When deserialized from JSON Then it contains:model,manufacturer,num_plates,area,dh,chevron_angle,ua_nominal, and optionalUaCurve -
Given a
VendorErrorenum When error conditions arise Then variantsModelNotFound,InvalidFormat,FileNotFound,ParseError,IoErrorare returned with descriptive messages -
Given unit tests When
cargo test -p entropyk-vendorsis 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.tomlwithserde,serde_json,thiserrordeps - Subtask 1.2: Add
"crates/vendors"to workspacemembersin rootCargo.toml - Subtask 1.3: Create
src/lib.rswith module declarations and public re-exports
- Subtask 1.1: Create
- Task 2: Define
VendorErrorenum (AC: 5)- Subtask 2.1: Create
src/error.rswith thiserror-derived error variants
- Subtask 2.1: Create
- Task 3: Define data types (AC: 3, 4)
- Subtask 3.1: Create
CompressorCoefficients,CompressorValidityRangeinsrc/vendor_api.rs - Subtask 3.2: Create
BphxParameters,UaCurve,UaCalcParamsinsrc/vendor_api.rs - Subtask 3.3: Derive
Serialize,Deserialize,Debug,Cloneon all data structs
- Subtask 3.1: Create
- Task 4: Define
VendorBackendtrait (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 + Syncbounds)
- Subtask 4.1: Implement trait with 5 required methods and 1 default method in
- 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.jsonwith[]
- Subtask 5.1: Create placeholder directories:
- Task 6: Create stub module files for future parser stories (AC: 1)
- Subtask 6.1: Create
src/compressors/mod.rswith commented-out vendor imports - Subtask 6.2: Create
src/heat_exchangers/mod.rswith commented-out vendor imports
- Subtask 6.1: Create
- Task 7: Write unit tests (AC: 6)
- Subtask 7.1: Test
CompressorCoefficientsJSON round-trip serialization - Subtask 7.2: Test
BphxParametersJSON round-trip serialization (with and withoutUaCurve) - Subtask 7.3: Test
UaCurveinterpolation logic (linear interpolation between points) - Subtask 7.4: Test
VendorErrordisplay messages - Subtask 7.5: Test mock
VendorBackendimplementation (list, get, error cases) - Subtask 7.6: Test default
compute_uareturnsua_nominal
- Subtask 7.1: Test
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()— returnResult<_, VendorError>everywhere - No
println!— usetracingif logging is needed (unlikely in this story) CompressorCoefficients.capacity_coeffsandpower_coeffsuse[f64; 10](fixed-size AHRI 540 standard)mass_flow_coeffsisOption<[f64; 10]>since not all vendors provide itUaCurve.pointsisVec<(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
Cellfor interior mutability insidecompute_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.tomlneeds"crates/vendors"inmembers - No impact on existing crates — this is purely additive
- No Python bindings needed for this story (future story scope)
References
- Source: _bmad-output/planning-artifacts/epic-11-technical-specifications.md#Story-1111-15-vendorbackend — lines 1304-1597
- Source: _bmad-output/project-context.md — Naming conventions, error hierarchy, testing patterns
- Source: crates/components/src/compressor.rs — Existing AHRI 540 implementation for coefficient compatibility
- Source: Cargo.toml — Workspace structure
Dev Agent Record
Agent Model Used
Antigravity (Gemini)
Debug Log References
Review Follow-ups (AI)
- [AI-Review][High]
UaCurve::interpolatesorting algorithm addition for parsed data. - [AI-Review][Medium]
CompressorValidityRangechecking during parsing. - [AI-Review][Medium]
UaCalcParamsmissing derive bounds (Debug, Clone). - [AI-Review][Low]
VendorError::IoErrormissing file path tracking context. - [AI-Review][Low]
entropyk-vendorslib.rs missing standard#![warn(missing_docs)].
Completion Notes List
- Created
crates/vendors/crate scaffold withCargo.toml,src/lib.rs, module re-exports - Implemented
VendorErrorenum insrc/error.rswith 5 thiserror-derived variants - Implemented
CompressorCoefficients,CompressorValidityRange,BphxParameters,UaCurve,UaCalcParamsinsrc/vendor_api.rs - Implemented
UaCurve::interpolate()with linear interpolation and endpoint clamping - Implemented
VendorBackendtrait with 5 required methods + 1 defaultcompute_uainsrc/vendor_api.rs - Created stub modules
src/compressors/mod.rsandsrc/heat_exchangers/mod.rs - Created
data/directory structure withcopeland/compressors/index.json,swep/bphx/,danfoss/,bitzer/ - Added crate to workspace
membersin rootCargo.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-pythonbuild 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 — addedcrates/vendorsto workspace members)