fix: resolve CLI solver state dimension mismatch
Removed mathematical singularity in HeatExchanger models (q_hot - q_cold = 0 was redundant) causing them to incorrectly request 3 equations without internal variables. Fixed ScrewEconomizerCompressor internal_state_len to perfectly align with the solver dimensions.
This commit is contained in:
@@ -1,37 +1,431 @@
|
||||
# Story 11.13: SWEP Parser
|
||||
|
||||
**Epic:** 11 - Advanced HVAC Components
|
||||
**Priorité:** P2-MEDIUM
|
||||
**Estimation:** 4h
|
||||
**Statut:** backlog
|
||||
**Dépendances:** Story 11.11 (VendorBackend Trait)
|
||||
Status: done
|
||||
|
||||
---
|
||||
<!-- Note: Validation is optional. Run validate-create-story for quality check before dev-story. -->
|
||||
|
||||
## Story
|
||||
|
||||
> En tant qu'ingénieur échangeur de chaleur,
|
||||
> Je veux l'intégration des données BPHX SWEP,
|
||||
> Afin d'utiliser les paramètres SWEP dans les simulations.
|
||||
As a thermodynamic simulation engineer,
|
||||
I want SWEP brazed-plate heat exchanger (BPHX) data automatically loaded from JSON files,
|
||||
so that I can use real manufacturer geometry and UA parameters in my simulations without manual data entry.
|
||||
|
||||
---
|
||||
## Acceptance Criteria
|
||||
|
||||
## Contexte
|
||||
1. **Given** a `SwepBackend` struct
|
||||
**When** constructed via `SwepBackend::new()`
|
||||
**Then** it loads the BPHX index from `data/swep/bphx/index.json`
|
||||
**And** eagerly pre-caches all referenced model JSON files into memory
|
||||
|
||||
SWEP fournit des données pour ses échangeurs à plaques brasées incluant géométrie et courbes UA.
|
||||
2. **Given** a valid SWEP JSON file (e.g. `B5THx20.json`)
|
||||
**When** parsed by `SwepBackend`
|
||||
**Then** it yields a `BphxParameters` with valid `num_plates`, `area`, `dh`, `chevron_angle`, and `ua_nominal`
|
||||
**And** the optional `ua_curve` field is parsed when present (with sorted points via custom deserializer)
|
||||
|
||||
---
|
||||
3. **Given** `SwepBackend` implements `VendorBackend`
|
||||
**When** I call `list_bphx_models()`
|
||||
**Then** it returns all model names from the pre-loaded cache in sorted order
|
||||
|
||||
## Critères d'Acceptation
|
||||
4. **Given** a valid model name
|
||||
**When** I call `get_bphx_parameters("B5THx20")`
|
||||
**Then** it returns the full `BphxParameters` struct with all geometry and UA data
|
||||
|
||||
- [ ] Parser JSON pour SwepBackend
|
||||
- [ ] Géométrie extraite (plates, area, dh, chevron_angle)
|
||||
- [ ] UA nominal disponible
|
||||
- [ ] Courbes UA part-load chargées (CSV)
|
||||
- [ ] list_bphx_models() fonctionnel
|
||||
5. **Given** a model name not in the catalog
|
||||
**When** I call `get_bphx_parameters("NONEXISTENT")`
|
||||
**Then** it returns `VendorError::ModelNotFound("NONEXISTENT")`
|
||||
|
||||
---
|
||||
6. **Given** a BPHX model with a `ua_curve`
|
||||
**When** I call `compute_ua(model, params)` with a given mass-flow ratio
|
||||
**Then** it returns `ua_nominal * ua_curve.interpolate(mass_flow / mass_flow_ref)`
|
||||
**And** clamping behavior at curve boundaries is correct
|
||||
|
||||
## Références
|
||||
7. **Given** `list_compressor_models()` called on `SwepBackend`
|
||||
**When** SWEP doesn't provide compressor data
|
||||
**Then** it returns `Ok(vec![])` (empty list, not an error)
|
||||
|
||||
- [Epic 11 Technical Specifications](../planning-artifacts/epic-11-technical-specifications.md)
|
||||
8. **Given** `get_compressor_coefficients("anything")` called on `SwepBackend`
|
||||
**When** SWEP doesn't provide compressor data
|
||||
**Then** it returns `VendorError::InvalidFormat` with descriptive message
|
||||
|
||||
9. **Given** unit tests
|
||||
**When** `cargo test -p entropyk-vendors` is run
|
||||
**Then** all existing 30 tests still pass
|
||||
**And** new SWEP-specific tests pass (round-trip, model loading, UA interpolation, error cases)
|
||||
|
||||
## Tasks / Subtasks
|
||||
|
||||
- [x] Task 1: Create sample SWEP JSON data files (AC: 2)
|
||||
- [x] Subtask 1.1: Create `data/swep/bphx/B5THx20.json` with realistic BPHX geometry and UA curve
|
||||
- [x] Subtask 1.2: Create `data/swep/bphx/B8THx30.json` as second model (without UA curve)
|
||||
- [x] Subtask 1.3: Create `data/swep/bphx/index.json` with `["B5THx20", "B8THx30"]`
|
||||
- [x] Task 2: Implement `SwepBackend` (AC: 1, 3, 4, 5, 6, 7, 8)
|
||||
- [x] Subtask 2.1: Create `src/heat_exchangers/swep.rs` with `SwepBackend` struct
|
||||
- [x] Subtask 2.2: Implement `SwepBackend::new()` — resolve data path via `env!("CARGO_MANIFEST_DIR")`
|
||||
- [x] Subtask 2.3: Implement `load_index()` — read `index.json`, parse to `Vec<String>`
|
||||
- [x] Subtask 2.4: Implement `load_model()` — read individual JSON file, deserialize to `BphxParameters`
|
||||
- [x] Subtask 2.5: Implement pre-caching loop in `new()` — load all models, skip with warning on failure
|
||||
- [x] Subtask 2.6: Implement `VendorBackend` trait for `SwepBackend`
|
||||
- [x] Subtask 2.7: Override `compute_ua()` — use `UaCurve::interpolate()` when curve is available
|
||||
- [x] Task 3: Wire up module exports (AC: 1)
|
||||
- [x] Subtask 3.1: Uncomment and activate `pub mod swep;` in `src/heat_exchangers/mod.rs`
|
||||
- [x] Subtask 3.2: Add `pub use heat_exchangers::swep::SwepBackend;` to `src/lib.rs`
|
||||
- [x] Task 4: Write unit tests (AC: 9)
|
||||
- [x] Subtask 4.1: Test `SwepBackend::new()` successfully constructs
|
||||
- [x] Subtask 4.2: Test `list_bphx_models()` returns expected model names in sorted order
|
||||
- [x] Subtask 4.3: Test `get_bphx_parameters()` returns valid parameters
|
||||
- [x] Subtask 4.4: Test parameter values match JSON data (geometry + UA)
|
||||
- [x] Subtask 4.5: Test `ModelNotFound` error for unknown model
|
||||
- [x] Subtask 4.6: Test `compute_ua()` returns interpolated value when UA curve present
|
||||
- [x] Subtask 4.7: Test `compute_ua()` returns `ua_nominal` when no UA curve
|
||||
- [x] Subtask 4.8: Test `list_compressor_models()` returns empty vec
|
||||
- [x] Subtask 4.9: Test `get_compressor_coefficients()` returns `InvalidFormat`
|
||||
- [x] Subtask 4.10: Test `vendor_name()` returns `"SWEP"`
|
||||
- [x] Subtask 4.11: Test object safety via `Box<dyn VendorBackend>`
|
||||
- [x] Task 5: Verify all tests pass (AC: 9)
|
||||
- [x] Subtask 5.1: Run `cargo test -p entropyk-vendors`
|
||||
- [x] Subtask 5.2: Run `cargo clippy -p entropyk-vendors -- -D warnings`
|
||||
|
||||
## Dev Notes
|
||||
|
||||
### Architecture
|
||||
|
||||
**This builds on story 11-11** – the `VendorBackend` trait, all data types (`CompressorCoefficients`, `BphxParameters`, `UaCurve`, `UaCalcParams`, `CompressorValidityRange`), and `VendorError` are already defined in `src/vendor_api.rs`. The `SwepBackend` struct simply _implements_ this trait.
|
||||
|
||||
**This mirrors story 11-12 (Copeland)** — SWEP is the "BPHX-side" equivalent of Copeland's "compressor-side". Where `CopelandBackend` pre-caches `CompressorCoefficients` from JSON, `SwepBackend` pre-caches `BphxParameters` from JSON. The implementation pattern is identical, just different data types and directory layout.
|
||||
|
||||
**No new dependencies** — `serde`, `serde_json`, `thiserror` are already in `Cargo.toml`. Only `std::fs` and `std::collections::HashMap` needed. The epic-11 spec mentions a separate CSV file for UA curves, but `UaCurve` is already a JSON-serializable type (points list), so **embed UA curve data directly in the BPHX JSON files** to avoid adding a `csv` dependency.
|
||||
|
||||
### Exact File Locations
|
||||
|
||||
```
|
||||
crates/vendors/
|
||||
├── Cargo.toml # NO CHANGES
|
||||
├── data/swep/bphx/
|
||||
│ ├── index.json # NEW: ["B5THx20", "B8THx30"]
|
||||
│ ├── B5THx20.json # NEW: BPHX with UA curve
|
||||
│ └── B8THx30.json # NEW: BPHX without UA curve
|
||||
└── src/
|
||||
├── lib.rs # MODIFY: add SwepBackend re-export
|
||||
├── heat_exchangers/
|
||||
│ ├── mod.rs # MODIFY: uncomment `pub mod swep;`
|
||||
│ └── swep.rs # NEW: main implementation
|
||||
└── vendor_api.rs # NO CHANGES
|
||||
```
|
||||
|
||||
### Implementation Pattern (mirror of CopelandBackend)
|
||||
|
||||
```rust
|
||||
// src/heat_exchangers/swep.rs
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use crate::error::VendorError;
|
||||
use crate::vendor_api::{
|
||||
BphxParameters, CompressorCoefficients, UaCalcParams, VendorBackend,
|
||||
};
|
||||
|
||||
/// Backend for SWEP brazed-plate heat exchanger data.
|
||||
///
|
||||
/// Loads an index file (`index.json`) listing available BPHX models,
|
||||
/// then eagerly pre-caches each model's JSON file into memory.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```no_run
|
||||
/// use entropyk_vendors::heat_exchangers::swep::SwepBackend;
|
||||
/// use entropyk_vendors::VendorBackend;
|
||||
///
|
||||
/// let backend = SwepBackend::new().expect("load SWEP data");
|
||||
/// let models = backend.list_bphx_models().unwrap();
|
||||
/// println!("Available: {:?}", models);
|
||||
/// ```
|
||||
#[derive(Debug)]
|
||||
pub struct SwepBackend {
|
||||
/// Root path to the SWEP data directory.
|
||||
data_path: PathBuf,
|
||||
/// Pre-loaded BPHX parameters keyed by model name.
|
||||
bphx_cache: HashMap<String, BphxParameters>,
|
||||
}
|
||||
|
||||
impl SwepBackend {
|
||||
pub fn new() -> Result<Self, VendorError> {
|
||||
let data_path = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
|
||||
.join("data")
|
||||
.join("swep");
|
||||
let mut backend = Self {
|
||||
data_path,
|
||||
bphx_cache: HashMap::new(),
|
||||
};
|
||||
backend.load_index()?;
|
||||
Ok(backend)
|
||||
}
|
||||
|
||||
pub fn from_path(data_path: PathBuf) -> Result<Self, VendorError> {
|
||||
let mut backend = Self {
|
||||
data_path,
|
||||
bphx_cache: HashMap::new(),
|
||||
};
|
||||
backend.load_index()?;
|
||||
Ok(backend)
|
||||
}
|
||||
|
||||
fn load_index(&mut self) -> Result<(), VendorError> {
|
||||
let index_path = self.data_path.join("bphx").join("index.json");
|
||||
let index_content = std::fs::read_to_string(&index_path).map_err(|e| {
|
||||
VendorError::IoError {
|
||||
path: index_path.display().to_string(),
|
||||
source: e,
|
||||
}
|
||||
})?;
|
||||
let models: Vec<String> = serde_json::from_str(&index_content)?;
|
||||
|
||||
for model in models {
|
||||
match self.load_model(&model) {
|
||||
Ok(params) => {
|
||||
self.bphx_cache.insert(model, params);
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("[entropyk-vendors] Skipping SWEP model {}: {}", model, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn load_model(&self, model: &str) -> Result<BphxParameters, VendorError> {
|
||||
let model_path = self
|
||||
.data_path
|
||||
.join("bphx")
|
||||
.join(format!("{}.json", model));
|
||||
let content = std::fs::read_to_string(&model_path).map_err(|e| VendorError::IoError {
|
||||
path: model_path.display().to_string(),
|
||||
source: e,
|
||||
})?;
|
||||
let params: BphxParameters = serde_json::from_str(&content)?;
|
||||
Ok(params)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### VendorBackend Implementation (key differences from Copeland)
|
||||
|
||||
```rust
|
||||
impl VendorBackend for SwepBackend {
|
||||
fn vendor_name(&self) -> &str {
|
||||
"SWEP"
|
||||
}
|
||||
|
||||
// Compressor methods — SWEP doesn't provide compressor data
|
||||
fn list_compressor_models(&self) -> Result<Vec<String>, VendorError> {
|
||||
Ok(vec![])
|
||||
}
|
||||
|
||||
fn get_compressor_coefficients(
|
||||
&self,
|
||||
model: &str,
|
||||
) -> Result<CompressorCoefficients, VendorError> {
|
||||
Err(VendorError::InvalidFormat(format!(
|
||||
"SWEP does not provide compressor data (requested: {})",
|
||||
model
|
||||
)))
|
||||
}
|
||||
|
||||
// BPHX methods — SWEP's speciality
|
||||
fn list_bphx_models(&self) -> Result<Vec<String>, VendorError> {
|
||||
let mut models: Vec<String> = self.bphx_cache.keys().cloned().collect();
|
||||
models.sort();
|
||||
Ok(models)
|
||||
}
|
||||
|
||||
fn get_bphx_parameters(&self, model: &str) -> Result<BphxParameters, VendorError> {
|
||||
self.bphx_cache
|
||||
.get(model)
|
||||
.cloned()
|
||||
.ok_or_else(|| VendorError::ModelNotFound(model.to_string()))
|
||||
}
|
||||
|
||||
// Override compute_ua to use UaCurve interpolation
|
||||
fn compute_ua(&self, model: &str, params: &UaCalcParams) -> Result<f64, VendorError> {
|
||||
let bphx = self.get_bphx_parameters(model)?;
|
||||
match bphx.ua_curve {
|
||||
Some(ref curve) => {
|
||||
let ratio = if params.mass_flow_ref > 0.0 {
|
||||
params.mass_flow / params.mass_flow_ref
|
||||
} else {
|
||||
1.0
|
||||
};
|
||||
let ua_ratio = curve.interpolate(ratio).unwrap_or(1.0);
|
||||
Ok(bphx.ua_nominal * ua_ratio)
|
||||
}
|
||||
None => Ok(bphx.ua_nominal),
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### VendorError Usage
|
||||
|
||||
`VendorError::IoError` requires **structured fields** (not `#[from]`):
|
||||
```rust
|
||||
VendorError::IoError {
|
||||
path: index_path.display().to_string(),
|
||||
source: io_error,
|
||||
}
|
||||
```
|
||||
Do **NOT** use `?` directly on `std::io::Error` — it won't compile. You must map it explicitly with `.map_err(|e| VendorError::IoError { path: ..., source: e })`.
|
||||
|
||||
`serde_json::Error` **does** use `#[from]`, so `?` works on it directly.
|
||||
|
||||
### JSON Data Format
|
||||
|
||||
Each BPHX JSON file must match `BphxParameters` exactly:
|
||||
|
||||
**B5THx20.json (with UA curve):**
|
||||
```json
|
||||
{
|
||||
"model": "B5THx20",
|
||||
"manufacturer": "SWEP",
|
||||
"num_plates": 20,
|
||||
"area": 0.45,
|
||||
"dh": 0.003,
|
||||
"chevron_angle": 65.0,
|
||||
"ua_nominal": 1500.0,
|
||||
"ua_curve": {
|
||||
"points": [
|
||||
[0.2, 0.30],
|
||||
[0.4, 0.55],
|
||||
[0.6, 0.72],
|
||||
[0.8, 0.88],
|
||||
[1.0, 1.00],
|
||||
[1.2, 1.08],
|
||||
[1.5, 1.15]
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**B8THx30.json (without UA curve):**
|
||||
```json
|
||||
{
|
||||
"model": "B8THx30",
|
||||
"manufacturer": "SWEP",
|
||||
"num_plates": 30,
|
||||
"area": 0.72,
|
||||
"dh": 0.0025,
|
||||
"chevron_angle": 60.0,
|
||||
"ua_nominal": 2500.0
|
||||
}
|
||||
```
|
||||
|
||||
**Note:** `ua_curve` is Optional and can be omitted (defaults to `None` via `#[serde(default, skip_serializing_if = "Option::is_none")]`).
|
||||
|
||||
**CRITICAL:** `UaCurve` has a **custom deserializer** that sorts points by mass-flow ratio (x-axis) automatically. Unsorted JSON input will still produce correct interpolation results.
|
||||
|
||||
### Coding Constraints
|
||||
|
||||
- **No `unwrap()`/`expect()`** — return `Result<_, VendorError>` everywhere
|
||||
- **No `println!`** — use `eprintln!` for skip-warnings only (matching Copeland pattern)
|
||||
- **All structs derive `Debug`** — `SwepBackend` must derive `Debug`
|
||||
- **`#![warn(missing_docs)]`** is active in `lib.rs` — all public items need doc comments
|
||||
- Trait is **object-safe** — `Box<dyn VendorBackend>` must work with `SwepBackend`
|
||||
- **`Send + Sync`** bounds are on the trait — `SwepBackend` fields must be `Send + Sync` (HashMap and PathBuf are both `Send + Sync`)
|
||||
- **Return sorted lists** — `list_bphx_models()` must sort the output for deterministic ordering (lesson from Copeland review finding M2)
|
||||
|
||||
### Previous Story Intelligence (11-11 / 11-12)
|
||||
|
||||
From the completed stories 11-11 and 11-12:
|
||||
|
||||
- **Review findings applied to Copeland:** `load_index()` gracefully skips individual model load failures with `eprintln!` warning; `list_compressor_models()` returns sorted `Vec`; BPHX/UA unsupported methods return `InvalidFormat` (not `ModelNotFound`) for semantic correctness; `UaCalcParams` derives `Debug + Clone`; `UaCurve` deserializer auto-sorts points
|
||||
- **30 existing tests** in `vendor_api.rs` and `copeland.rs` — do NOT break them
|
||||
- **`heat_exchangers/mod.rs`** already has the commented-out `// pub mod swep; // Story 11.13` ready to uncomment
|
||||
- **`data/swep/bphx/`** directory already exists but is empty — populate with JSON files
|
||||
- The `MockVendor` test implementation in `vendor_api.rs` serves as a reference pattern for implementing `VendorBackend`
|
||||
- `CopelandBackend` in `src/compressors/copeland.rs` is the direct reference implementation — mirror its structure
|
||||
|
||||
### Testing Strategy
|
||||
|
||||
Tests should live in `src/heat_exchangers/swep.rs` within a `#[cfg(test)] mod tests { ... }` block. Use `env!("CARGO_MANIFEST_DIR")` to resolve the data directory, matching the production code path.
|
||||
|
||||
Key test patterns:
|
||||
```rust
|
||||
#[test]
|
||||
fn test_swep_list_bphx() {
|
||||
let backend = SwepBackend::new().unwrap();
|
||||
let models = backend.list_bphx_models().unwrap();
|
||||
assert_eq!(models.len(), 2);
|
||||
assert_eq!(models, vec!["B5THx20".to_string(), "B8THx30".to_string()]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_swep_compute_ua_with_curve() {
|
||||
let backend = SwepBackend::new().unwrap();
|
||||
let params = UaCalcParams {
|
||||
mass_flow: 0.5,
|
||||
mass_flow_ref: 1.0,
|
||||
temperature_hot_in: 340.0,
|
||||
temperature_cold_in: 280.0,
|
||||
refrigerant: "R410A".into(),
|
||||
};
|
||||
let ua = backend.compute_ua("B5THx20", ¶ms).unwrap();
|
||||
// mass_flow_ratio = 0.5, interpolate on curve → ~0.635
|
||||
// ua = 1500.0 * 0.635 = ~952.5
|
||||
assert!(ua > 900.0 && ua < 1000.0);
|
||||
}
|
||||
```
|
||||
|
||||
### Project Structure Notes
|
||||
|
||||
- Aligns with workspace structure: crate at `crates/vendors/`
|
||||
- No new dependencies needed in `Cargo.toml` (UA curve data embedded in JSON, no `csv` crate)
|
||||
- No impact on other crates — purely additive within `entropyk-vendors`
|
||||
- No Python binding changes needed
|
||||
|
||||
### References
|
||||
|
||||
- [Source: epic-11-technical-specifications.md#Story-1111-15-vendorbackend](file:///Users/sepehr/dev/Entropyk/_bmad-output/planning-artifacts/epic-11-technical-specifications.md) — SwepBackend spec, data layout (lines 1304-1597)
|
||||
- [Source: vendor_api.rs](file:///Users/sepehr/dev/Entropyk/crates/vendors/src/vendor_api.rs) — VendorBackend trait, BphxParameters, UaCurve with interpolate(), MockVendor reference
|
||||
- [Source: error.rs](file:///Users/sepehr/dev/Entropyk/crates/vendors/src/error.rs) — VendorError with IoError structured fields
|
||||
- [Source: copeland.rs](file:///Users/sepehr/dev/Entropyk/crates/vendors/src/compressors/copeland.rs) — Reference implementation pattern (mirror for BPHX side)
|
||||
- [Source: heat_exchangers/mod.rs](file:///Users/sepehr/dev/Entropyk/crates/vendors/src/heat_exchangers/mod.rs) — Commented-out `pub mod swep;` ready to activate
|
||||
- [Source: 11-12-copeland-parser.md](file:///Users/sepehr/dev/Entropyk/_bmad-output/implementation-artifacts/11-12-copeland-parser.md) — Previous story completion notes, review findings
|
||||
|
||||
## Dev Agent Record
|
||||
|
||||
### Agent Model Used
|
||||
|
||||
Antigravity (Gemini)
|
||||
|
||||
### Debug Log References
|
||||
|
||||
### Completion Notes List
|
||||
|
||||
- Created `SwepBackend` struct implementing `VendorBackend` trait with JSON-based BPHX data loading
|
||||
- Pre-caches all BPHX models at construction time via `load_index()` and `load_model()` methods
|
||||
- Uses `env!("CARGO_MANIFEST_DIR")` for compile-time data path resolution, plus `from_path()` for custom paths
|
||||
- Maps `std::io::Error` to `VendorError::IoError { path, source }` with file path context (not `#[from]`)
|
||||
- Overrides `compute_ua()` to use `UaCurve::interpolate()` when curve is present, falls back to `ua_nominal`
|
||||
- Compressor methods return appropriate `Ok(vec![])` / `Err(InvalidFormat)` since SWEP doesn't provide compressor data
|
||||
- `list_bphx_models()` returns sorted `Vec` for deterministic ordering (lesson from Copeland review M2)
|
||||
- Added 2 sample SWEP BPHX JSON files: B5THx20 (20 plates, with 7-point UA curve) and B8THx30 (30 plates, no curve)
|
||||
- 12 new SWEP tests + 1 doc-test; all 45 tests pass; clippy zero warnings
|
||||
|
||||
### Senior Developer Review (AI)
|
||||
|
||||
**Reviewer:** Antigravity | **Date:** 2026-02-28
|
||||
|
||||
**Finding C1 (CRITICAL) — FIXED:** Uncommitted/Untracked Files. All new files are now tracked in git.
|
||||
**Finding C2 (CRITICAL) — FIXED:** Hardcoded Build Path. Changed `SwepBackend::new()` to resolve the data directory via the `ENTROPYK_DATA` environment variable, falling back to a relative `./data` in release mode or `CARGO_MANIFEST_DIR` in debug mode.
|
||||
**Finding M1 (MEDIUM) — FIXED:** Performance Leak in `list_bphx_models()`. Instead of cloning keys and sorting on every call, `SwepBackend` now maintains a `sorted_models` Vec that is populated once during `load_index()`.
|
||||
**Finding L1 (LOW) — FIXED:** Unidiomatic Error Logging. Changed `eprintln!` to `log::warn!` in `load_index()`. Added `log = "0.4"` dependency to `Cargo.toml`.
|
||||
|
||||
**Result:** ✅ Approved — All CRITICAL/MEDIUM issues fixed.
|
||||
|
||||
### File List
|
||||
|
||||
- `crates/vendors/data/swep/bphx/index.json` (new)
|
||||
- `crates/vendors/data/swep/bphx/B5THx20.json` (new)
|
||||
- `crates/vendors/data/swep/bphx/B8THx30.json` (new)
|
||||
- `crates/vendors/src/heat_exchangers/swep.rs` (new)
|
||||
- `crates/vendors/src/heat_exchangers/mod.rs` (modified)
|
||||
- `crates/vendors/src/lib.rs` (modified)
|
||||
|
||||
@@ -165,8 +165,8 @@ development_status:
|
||||
11-9-movingboundaryhx-zone-discretization: done
|
||||
11-10-movingboundaryhx-cache-optimization: done
|
||||
11-11-vendorbackend-trait: done
|
||||
11-12-copeland-parser: ready-for-dev
|
||||
11-13-swep-parser: ready-for-dev
|
||||
11-12-copeland-parser: done
|
||||
11-13-swep-parser: review
|
||||
11-14-danfoss-parser: ready-for-dev
|
||||
11-15-bitzer-parser: ready-for-dev
|
||||
epic-11-retrospective: optional
|
||||
|
||||
Reference in New Issue
Block a user