# Story 11.11: VendorBackend Trait

Status: done

<!-- Note: Validation is optional. Run validate-create-story for quality check before dev-story. -->

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

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

```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

- [Source: _bmad-output/planning-artifacts/epic-11-technical-specifications.md#Story-1111-15-vendorbackend](file:///Users/sepehr/dev/Entropyk/_bmad-output/planning-artifacts/epic-11-technical-specifications.md) — lines 1304-1597
- [Source: _bmad-output/project-context.md](file:///Users/sepehr/dev/Entropyk/_bmad-output/project-context.md) — Naming conventions, error hierarchy, testing patterns
- [Source: crates/components/src/compressor.rs](file:///Users/sepehr/dev/Entropyk/crates/components/src/compressor.rs) — Existing AHRI 540 implementation for coefficient compatibility
- [Source: Cargo.toml](file:///Users/sepehr/dev/Entropyk/Cargo.toml) — Workspace structure

## Dev Agent Record

### Agent Model Used

Antigravity (Gemini)

### Debug Log References

### Review Follow-ups (AI)
- [x] [AI-Review][High] `UaCurve::interpolate` sorting algorithm addition for parsed data.
- [x] [AI-Review][Medium] `CompressorValidityRange` checking during parsing.
- [x] [AI-Review][Medium] `UaCalcParams` missing derive bounds (Debug, Clone).
- [x] [AI-Review][Low] `VendorError::IoError` missing file path tracking context.
- [x] [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)
