Capture uncommitted solver robustness work (regularization, domain errors, linear solver lifecycle, tube DP/MSH), web workbench updates, and synced BMAD skills across IDE agent folders before starting BPHX pressure-drop. Co-authored-by: Cursor <cursoragent@cursor.com>
31 KiB
Seasonal & Part-Load Ratings — IPLV / NPLV / ESEER / SCOP / SEER
Modular, data-driven standardized ratings for chillers and heat pumps.
Crate:
entropyk::rating· CLI:entropyk-cli rate
This document is the definitive reference for how Entropyk turns a machine's part-load performance into the standardized seasonal metrics used to qualify HVAC/R equipment, and — most importantly — how those standards are kept modular so that when a norm is revised (AHRI, Eurovent and EN periodically re-fit their coefficients and climate tables) you can adapt without recompiling.
Table of contents
- Concepts & scope
- Design philosophy: standards are data, not code
- Library API (
entropyk::rating) - The
rateCLI command - Custom standard JSON schema
- How to change a standard when the norm changes
- The
scop/seerCLI commands (bin method) - Validation & testing
- Reference tables
- Standards references
1. Concepts & scope
A single full-load efficiency figure (EER or COP at rated conditions) is a poor predictor of real energy use, because equipment spends most of its life at part load. Regulators therefore define seasonal metrics that weight several part-load operating points:
| Metric | Family | Standard | What it weights |
|---|---|---|---|
| IPLV — Integrated Part Load Value | Weighted part-load | AHRI 550/590 | EER at 100/75/50/25 % load |
| NPLV — Non-standard Part Load Value | Weighted part-load | AHRI 550/590 | Same formula as IPLV, taken at non-standard conditions |
| ESEER — European SEER | Weighted part-load | Eurovent | EER at 100/75/50/25 % load, different weights |
| SCOP — Seasonal COP | Temperature-bin | EN 14825 | COP across a climate's hourly temperature bins |
| SEER — Seasonal EER | Temperature-bin / weighted | EN 14825 | EER across the cooling season |
Entropyk models these as two calculation families:
- Weighted part-load (
IPLV,NPLV,ESEER, and weighted SEER variants): a weighted average of efficiencies measured at a small set of load fractions. Implemented byPartLoadStandard. - Temperature-bin (
SCOP, bin-basedSEER): energy summed over a climate's hourly temperature bins,Σ(h·demand) / Σ(h·demand/COP). Implemented byBinClimateStandard+scop.
Purity guarantee. All the metric arithmetic takes already-solved efficiency values as input. Computing the part-load operating points — by re-solving the coupled cycle at each rating condition — is the caller's job (the
rateCLI command does this). This keeps the metric math deterministic and trivially unit-testable, and keeps the physics honest: nothing is imposed, all efficiencies emerge from a genuine coupled solve.
2. Design philosophy: standards are data, not code
Rating standards change. AHRI and Eurovent re-fit their part-load weights; EN 14825 revises its climate bins and reference seasons. If those coefficients were hard-coded arithmetic, every revision would mean a code change, a rebuild, and a re-release.
Instead, a standard in Entropyk is a plain data record:
- A weighted part-load standard is just
{ load_fractions, weights }plus a name and a citation. - A climate standard is just
{ bins }(temperature + annual hours) plus a name and a citation.
Because they are data, a standard can be:
- a built-in preset (a function returning the record — e.g.
PartLoadStandard::ahri_550_590_iplv()), - looked up by name in a small registry (
PartLoadStandard::builtin("iplv")), - or loaded from a JSON file at run time and validated
(
serde_json::from_str::<PartLoadStandard>(...)?.validate()?).
The number of load points is arbitrary (four for AHRI/Eurovent, but any N is accepted), so a future standard with more or fewer points needs no code change.
3. Library API (entropyk::rating)
use entropyk::rating::{
PartLoadStandard, PartLoadEfficiencies, BinClimateStandard,
BinPerformance, TemperatureBin, RatingCondition, RatingError, scop,
};
3.1 PartLoadStandard
The data-driven weighting standard behind IPLV / NPLV / ESEER / weighted SEER.
pub struct PartLoadStandard {
pub name: String, // e.g. "AHRI 550/590 IPLV"
pub reference: String, // citation / provenance (optional in JSON)
pub load_fractions: Vec<f64>, // e.g. [1.0, 0.75, 0.50, 0.25]
pub weights: Vec<f64>, // same length; must sum to 1.0
}
Construction & validation
| Method | Description |
|---|---|
new(name, reference, load_fractions, weights) -> Result<Self, RatingError> |
Build and validate in one step. |
validate() -> Result<(), RatingError> |
Non-empty, equal-length fractions/weights, weights sum to 1.0 within 1e-6. |
len() -> usize / is_empty() -> bool |
Number of load points. |
Integration
| Method | Description |
|---|---|
integrate(&self, efficiencies: &[f64]) -> Result<f64, RatingError> |
Weighted sum Σ eᵢ·wᵢ. efficiencies must match weights in count and order. |
Built-in presets & registry
| Method | Returns |
|---|---|
ahri_550_590_iplv() |
AHRI IPLV/NPLV — weights [0.01, 0.42, 0.45, 0.12]. |
eurovent_eseer() |
Eurovent ESEER — weights [0.03, 0.33, 0.41, 0.23]. |
builtin(id: &str) -> Option<Self> |
Case/separator-insensitive lookup (see ids below). |
builtin_ids() -> &'static [&'static str] |
["iplv", "nplv", "eseer"] (for help text). |
Recognised builtin ids (case-insensitive; spaces, - and / are normalised to
_):
| Id(s) | Standard |
|---|---|
iplv, nplv, ahri, ahri_550_590, ahri_551_591 |
AHRI 550/590 IPLV |
eseer, eurovent |
Eurovent ESEER |
Example — built-in
let iplv = PartLoadStandard::ahri_550_590_iplv();
let value = iplv.integrate(&[4.0, 5.0, 6.0, 5.5]).unwrap();
// value == 0.01*4.0 + 0.42*5.0 + 0.45*6.0 + 0.12*5.5
Example — custom, from JSON, no code change
let json = r#"{
"name": "Custom SEER weighting",
"reference": "EN 14825 moderate cooling season (illustrative)",
"load_fractions": [1.0, 0.74, 0.47, 0.21],
"weights": [0.03, 0.27, 0.41, 0.29]
}"#;
let std: PartLoadStandard = serde_json::from_str(json)?;
std.validate()?;
let seer = std.integrate(&[3.0, 4.0, 5.0, 4.5])?;
3.2 PartLoadEfficiencies
A convenience holder for the classic four-point case, ordered
[100 %, 75 %, 50 %, 25 %].
pub struct PartLoadEfficiencies { pub at_100: f64, pub at_75: f64, pub at_50: f64, pub at_25: f64 }
| Method | Description |
|---|---|
new(at_100, at_75, at_50, at_25) |
Construct. |
as_array() -> [f64; 4] |
[100, 75, 50, 25] order. |
integrate(&self, std: &PartLoadStandard) -> Result<f64, RatingError> |
Modular entry point — apply any four-point standard. |
iplv() -> f64 |
Convenience wrapper (AHRI weights). |
nplv() -> f64 |
Alias of iplv() (identical arithmetic). |
eseer() -> f64 |
Convenience wrapper (Eurovent weights). |
let eff = PartLoadEfficiencies::new(4.0, 5.2, 6.1, 5.4);
let iplv = eff.iplv(); // legacy helper
let iplv2 = eff.integrate(&PartLoadStandard::ahri_550_590_iplv())?; // modular
assert!((iplv - iplv2).abs() < 1e-12);
3.3 BinClimateStandard, BinPerformance and scop
The temperature-bin method behind SCOP (and bin-based SEER).
pub struct TemperatureBin { pub temperature_c: f64, pub hours: f64 }
pub struct BinClimateStandard {
pub name: String,
pub reference: String,
pub bins: Vec<TemperatureBin>,
}
pub struct BinPerformance {
pub bin: TemperatureBin,
pub demand_w: f64, // building demand at this bin temperature [W]
pub cop: f64, // machine COP at this bin temperature
}
pub fn scop(bins: &[BinPerformance]) -> Option<f64>;
BinClimateStandard methods:
| Method | Description |
|---|---|
en_14825_average() |
EN 14825 average heating season (Strasbourg reference, 4910 h). |
builtin(id) -> Option<Self> |
Ids: en_14825_average, average, en14825. |
builtin_ids() -> &'static [&'static str] |
For help text. |
total_hours() -> f64 |
Sum of hours across all bins. |
validate() -> Result<(), RatingError> |
At least one bin. |
scop(...) computes:
SCOP = Σ (hoursⱼ · demandⱼ) / Σ (hoursⱼ · demandⱼ / COPⱼ)
Bins with non-positive hours, demand or COP are ignored. Returns None if no
valid bin remains (total electrical energy would be zero).
Example — SCOP over a climate
let climate = BinClimateStandard::en_14825_average();
let bins: Vec<BinPerformance> = climate.bins.iter().map(|&bin| BinPerformance {
bin,
demand_w: building_demand_at(bin.temperature_c), // your building model
cop: machine_cop_at(bin.temperature_c), // from a coupled solve
}).collect();
let scop = scop(&bins).expect("valid climate");
Swapping the climate is just choosing a different
BinClimateStandard— a preset, or a JSON file with a differentbinstable (warmer/colder season, a revised reference table, or a local TMY-derived climate). Thescoparithmetic never changes.
3.4 RatingCondition
Named full-load rating envelopes (secondary-fluid temperatures) used to define where the 100 % point is measured. Refrigerant regimes emerge from the coupled heat-exchanger solve, so only the secondary conditions are prescribed.
pub struct RatingCondition {
pub name: &'static str,
pub evap_secondary_out_c: f64, // chilled-fluid supply
pub evap_secondary_in_c: f64, // chilled-fluid return
pub cond_secondary_in_c: f64, // condenser/gas-cooler entering
}
Provided constants:
| Constant | Chilled supply/return | Condenser entering |
|---|---|---|
AHRI_550_590_WATER_COOLED |
6.7 / 12.2 °C | 29.4 °C (water) |
AHRI_550_590_AIR_COOLED |
6.7 / 12.2 °C | 35.0 °C (air) |
EN_14511_WATER_COOLED_A |
7.0 / 12.0 °C | 30.0 °C (water) |
EN_14511_AIR_COOLED_A |
7.0 / 12.0 °C | 35.0 °C (air) |
3.5 RatingError
pub enum RatingError {
Empty, // no load points
LengthMismatch { fractions: usize, weights: usize },
WeightsNotNormalized { sum: f64 }, // |Σw − 1| > 1e-6
EfficiencyCountMismatch { expected: usize, got: usize },
}
Implements Display + std::error::Error, so it composes with ? and anyhow.
3.6 Legacy constants
Kept for backward compatibility (the presets are built from them):
| Constant | Value |
|---|---|
STANDARD_LOAD_FRACTIONS |
[1.0, 0.75, 0.50, 0.25] |
IPLV_WEIGHTS |
[0.01, 0.42, 0.45, 0.12] |
ESEER_WEIGHTS |
[0.03, 0.33, 0.41, 0.23] |
EN_14825_AVERAGE_BINS |
26 bins, −10…+15 °C, sum 4910 h |
4. The rate CLI command
rate re-solves a full cycle configuration at each standardized part-load point
and aggregates the resulting EERs into the chosen seasonal metric. Every point is
a genuine coupled solve (condensing/evaporating pressures, capacity and power
all emerge from the heat-exchanger ↔ secondary balance), so the integrated value
is real simulation output — not imposed design points.
# Pretty table to stdout
entropyk-cli rate --config crates/cli/examples/rate_chiller_iplv_ahri.json
# Also write a JSON report
entropyk-cli rate -c rate_config.json -o rate_report.json
# Machine-readable JSON only
entropyk-cli --quiet rate -c rate_config.json
4.1 Configuration schema
{
"base_config": "chiller_r134a_emergent_pressure.json", // required; path to a `run` scenario
// --- standard selection (see precedence below); all optional ---
"metric": "iplv", // built-in enum: "iplv" (default) | "eseer"
"standard_name": "eseer", // built-in id (overrides `metric`)
"standard_file": "my_standard.json", // custom PartLoadStandard JSON (overrides `standard_name`)
"standard": { // inline PartLoadStandard (highest precedence)
"name": "…", "reference": "…",
"load_fractions": [1.0, 0.75, 0.5, 0.25],
"weights": [0.01, 0.42, 0.45, 0.12]
},
// --- the part-load points (usually four) ---
"points": [
{
"load_fraction": 1.00, // required; used to match the standard's fractions
"condenser_secondary_inlet_c": 29.4, // optional overrides applied to the base config
"evaporator_secondary_inlet_c": 12.0,
"condenser_secondary_mass_flow_kg_s": 0.36,
"evaporator_secondary_mass_flow_kg_s": 0.48,
"compressor_speed_hz": 50.0
}
// … 0.75, 0.50, 0.25 …
]
}
base_config is resolved relative to the rating file's directory when not
absolute. standard_file is likewise resolved relative to the rating file.
Per-point overrides are written into the base config's matching components
before re-solving. They map to these component params (see the
run documentation for the full param list):
| Rating override | Component(s) matched | Param set |
|---|---|---|
condenser_secondary_inlet_c |
Condenser |
secondary_inlet_temp_c |
condenser_secondary_mass_flow_kg_s |
Condenser |
secondary_mass_flow_kg_s |
evaporator_secondary_inlet_c |
Evaporator, FloodedEvaporator |
secondary_inlet_temp_c |
evaporator_secondary_mass_flow_kg_s |
Evaporator, FloodedEvaporator |
secondary_mass_flow_kg_s |
compressor_speed_hz |
IsentropicCompressor, Compressor |
speed_hz |
Overrides apply to all matching components (single-circuit chillers, the common case, have exactly one each). Anything not overridden is inherited from the base config, so every point runs the same machine through a different operating envelope.
4.2 Selecting the standard (precedence)
Highest to lowest:
standard— an inlinePartLoadStandardobject.standard_file— a JSONPartLoadStandard, resolved relative to the config.standard_name— a built-in id (iplv,nplv,eseer, …).metric— the enum (iplvdefault, oreseer).
The resolved standard is validated before use; an invalid standard (bad lengths, weights not summing to 1, unknown id) fails fast with a clear error.
4.3 How a part-load point is solved
For each point, in parallel (rayon):
- Clone the base
ScenarioConfig. - Apply overrides to matching components' params.
- Serialize and re-solve via the standard
runpipeline (simulate_from_json). - Extract
EER = performance.cop, plus cooling capacity and power.
Aggregation then matches each of the standard's load_fractions to the nearest
part-load point (within a tolerance of 0.02) and integrates with the standard's
weights. If any required load fraction has no converged point within tolerance,
the integrated value is reported as null while the per-point rows are still
shown.
4.4 Report format
pub struct RateReport {
pub base_config: String,
pub metric: String, // the standard's NAME, e.g. "AHRI 550/590 IPLV"
pub standard_reference: String, // the standard's citation
pub points: Vec<RatePointResult>, // ordered by descending load fraction
pub integrated_value: Option<f64>,
}
pub struct RatePointResult {
pub load_fraction: f64,
pub status: String, // "converged" | "timeout" | "non_converged" | "error"
pub eer: Option<f64>,
pub q_cooling_kw: Option<f64>,
pub power_kw: Option<f64>,
pub error: Option<String>, // present only on failure
}
4.5 Worked examples
Built-in AHRI IPLV — rate_chiller_iplv_ahri.json:
entropyk-cli rate -c crates/cli/examples/rate_chiller_iplv_ahri.json
Base config: chiller_r134a_emergent_pressure.json
Standard: AHRI 550/590 IPLV
Reference: AHRI Standard 550/590 — Integrated Part Load Value
Load[%] Status Q_cool[kW] Power[kW] EER[-]
──────────────────────────────────────────────────────────
100 converged 7.439 1.819 4.090
75 converged 6.363 1.117 5.697
50 converged 4.938 0.542 9.108
25 converged 2.938 0.223 13.183
──────────────────────────────────────────────────────────
AHRI 550/590 IPLV = 8.114
External custom standard —
rate_chiller_custom_standard.json
loads its weights from standard_custom_iplv.json
(standard_file). Same points, revised weights [0.02, 0.40, 0.45, 0.13]:
Standard: Custom IPLV (revised weights)
…
Custom IPLV (revised weights) = 8.173
The only difference between the two runs is the weighting file — the physics (per-point EERs) is identical.
5. Custom standard JSON schema
A PartLoadStandard file (used by standard_file, or inline under standard):
{
"name": "Custom IPLV (revised weights)",
"reference": "Illustrative custom weighting — edit to track a revised norm",
"load_fractions": [1.0, 0.75, 0.50, 0.25],
"weights": [0.02, 0.40, 0.45, 0.13]
}
Rules enforced by validate():
load_fractionsandweightsare non-empty and have the same length.weightssum to 1.0 within1e-6.referenceis optional (defaults to empty).- Any number of points is allowed — the
ratecommand matches them to your configuredpointsby nearestload_fraction.
6. How to change a standard when the norm changes
This is the core requirement the design serves. Three escalating options, none of which require touching the solver or the CLI:
Option A — no rebuild: ship a JSON file.
Edit or add a PartLoadStandard JSON file and point standard_file at it (or
inline it under standard). This is the recommended path for tracking a revised
norm in the field.
// rate_config.json
{ "base_config": "chiller.json", "standard_file": "ahri_550_590_2026.json", "points": [ … ] }
Option B — a new built-in preset (small code change, for standards you ship).
Add a constructor and register it in the lookup, in
crates/entropyk/src/rating.rs:
impl PartLoadStandard {
pub fn ahri_550_590_2026_iplv() -> Self {
Self {
name: "AHRI 550/590-2026 IPLV".into(),
reference: "AHRI Standard 550/590 (2026 revision)".into(),
load_fractions: vec![1.0, 0.75, 0.50, 0.25],
weights: vec![/* new coefficients */],
}
}
}
// in builtin():
"iplv_2026" | "ahri_550_590_2026" => Some(Self::ahri_550_590_2026_iplv()),
Then add the id to builtin_ids() and a unit test asserting the weights and that
they sum to 1.
Option C — revise an existing preset's coefficients.
If a standard simply re-fits the weights of an existing metric, update the
corresponding constant (IPLV_WEIGHTS / ESEER_WEIGHTS) or the preset body, and
update the affected unit tests. The validate() invariant (weights sum to 1)
guards against typos.
Whichever option you choose, the surrounding cycle solve, the
ratecommand and the report format stay untouched — only the data changes.
7. The scop / seer CLI commands (bin method)
While rate integrates a handful of weighted part-load points, SCOP (seasonal
heating) and SEER (seasonal cooling) follow the EN 14825 bin method: the
machine is re-solved at every outdoor temperature bin of a climate, the building
demand is derived from a linear load line, and the season is aggregated by an
energy ratio.
# Seasonal heating COP (SCOP) — defaults to the EN 14825 average heating season
cargo run -p entropyk-cli -- scop --config crates/cli/examples/scop_heatpump_r134a.json
# Seasonal cooling EER (SEER) — requires an explicit cooling climate
cargo run -p entropyk-cli -- seer --config my_seer.json --output seer.json
# Machine-readable JSON only
cargo run -p entropyk-cli -- --quiet scop --config scop_heatpump_r134a.json
Both commands share one module (entropyk_cli::seasonal) and one config shape;
the subcommand only fixes the mode (which heat-exchanger side the outdoor
temperature drives and which duty is the useful output):
| Mode | Subcommand | Useful duty | Outdoor side (default) | Default climate |
|---|---|---|---|---|
| SCOP | scop |
heating (condenser) | evaporator | en_14825_average |
| SEER | seer |
cooling (evaporator) | condenser | (explicit required) |
7.1 Configuration schema
{
"base_config": "heatpump_r134a_air_source.json", // base run scenario (rel. to this file)
// Climate selection — precedence: climate > climate_file > climate_name > default
"climate_name": "en_14825_average", // built-in BinClimateStandard id
// "climate_file": "my_cooling_bins.json", // external BinClimateStandard JSON
// "climate": { ...inline BinClimateStandard... },
"outdoor_side": "evaporator", // "evaporator" | "condenser"; defaults by mode
// Building load line (linear demand vs outdoor temperature)
"design_load_w": 6000.0, // demand [W] at design_outdoor_c
"design_outdoor_c": -10.0, // outdoor temp where demand = design_load_w
"threshold_outdoor_c": 16.0, // outdoor temp where demand reaches 0 (default 16)
"cd": 0.25, // cycling degradation coeff (PLF = 1 - Cd*(1-CR)); default 0.25
"backup_cop": 1.0 // COP of electric backup for heating deficits; default 1.0
}
Only base_config, design_load_w and design_outdoor_c are required. The
outdoor temperature is applied to the matching heat exchanger's
secondary_inlet_temp_c — so the base config's secondary stream flow and
cp (e.g. air cp 1006 J/kg·K) are kept and only the inlet temperature is swept.
7.2 How a bin is solved
For each bin at outdoor temperature Tj, with hours h:
- Demand from the linear load line —
heating:
demand = design_load · (threshold − Tj) / (threshold − design); cooling:demand = design_load · (Tj − threshold) / (design − threshold), clamped at ≥ 0. - Full-load solve — the base cycle is re-solved with the outdoor-side
secondary_inlet_temp_c = Tj(a genuine coupled solve, no injected regime), givingQ_full,W_full,COP_full. - Capacity ratio
CR = demand / Q_full.CR ≤ 1(part load): cycling degradationPLF = 1 − Cd·(1 − CR),COP_bin = COP_full · PLF.CR > 1(heating deficit): electric backup coversdemand − Q_full,COP_bin = demand / (W_full + (demand − Q_full)/backup_cop); the reportedbackupfraction is(demand − Q_full)/demand. For cooling the capacity simply caps atQ_full.
- Aggregate — seasonal useful energy
Σ demand·h, seasonal electric energyΣ (demand/COP_bin)·h, andSCOP/SEER = useful / electric.
Bins whose demand is 0 (above the threshold for heating) contribute no energy and are skipped in the aggregation. If a demanded bin fails to converge, the integrated value is reported as unavailable rather than silently biased.
7.3 Report format
SeasonalReport (also the --quiet / --output JSON):
| Field | Meaning |
|---|---|
base_config |
base scenario path |
metric |
"SCOP" or "SEER" |
climate |
climate standard name |
climate_reference |
standard citation |
total_hours |
Σ bin hours |
bins[] |
per-bin rows (see below) |
seasonal_useful_kwh |
Σ demand·hours / 1000 |
seasonal_electric_kwh |
Σ electric·hours / 1000 |
integrated_value |
SCOP/SEER, or null if a demanded bin failed |
Each bins[] row: temperature_c, hours, demand_w, capacity_w,
full_cop, backup_fraction, effective_cop.
7.4 Worked example (SCOP)
crates/cli/examples/scop_heatpump_r134a.json runs the emergent air-source heat
pump (heatpump_r134a_air_source.json, evaporator = outdoor air) over the EN 14825
average heating season:
T[°C] h Demand[kW] Cap[kW] COP_full backup COP_eff
-10.0 1 6.000 4.967 3.649 17% 2.506
-7.0 24 5.308 5.419 3.775 0% 3.756
2.0 320 3.231 6.918 4.169 0% 3.613
7.0 326 2.077 7.840 4.392 0% 3.585
15.0 74 0.231 9.445 4.751 0% 3.592
Seasonal useful: 12393.7 kWh Seasonal electric: 3441.1 kWh
SCOP = 3.602
Full-load COP climbs monotonically as the outdoor air warms (less lift); at the
coldest bins the capacity falls short of demand and electric backup kicks in
(non-zero backup %), which is exactly where the effective COP is lowest.
7.5 Adding another bin-based metric
Because the climate is a data-driven BinClimateStandard, adding a new season
(SEER climate, a regional heating season, a future EN revision) means authoring a
bin table — a preset, a built-in id, or an external JSON climate_file — with no
code change. See §5 for the JSON schema (the same BinClimateStandard shape used
by climate_file/climate).
8. Validation & testing
The rating math is pure and exhaustively unit-tested. Run:
# Library: metric math, presets, validation, registries (+ doctests)
cargo test -p entropyk --lib rating
cargo test -p entropyk --doc rating
# CLI: override application, standard resolution & precedence, aggregation
cargo test -p entropyk-cli rate
# CLI seasonal: bin method demand/degradation/backup + end-to-end SCOP
cargo test -p entropyk-cli --lib seasonal
cargo test -p entropyk-cli --test single_run test_scop_command
Notable guarantees covered by tests:
- Presets reproduce the legacy constants bit-for-bit;
integrate()agrees withiplv()/eseer()to1e-12. validate()rejects length mismatches, non-normalized weights and empty standards with the rightRatingError.builtin()lookup is case- and separator-insensitive.- Custom standards with a different point count integrate correctly.
- CLI standard precedence (
standard>standard_file>standard_name>metric) resolves as specified; unknown ids error clearly. - End-to-end: the emergent chiller re-solves four points and produces a genuine IPLV, with per-point EER increasing monotonically as the pressure lift falls.
- Seasonal bin method: demand tracks the linear load line, cycling degradation and electric backup apply only where expected, and the end-to-end SCOP over the 26 EN 14825 bins is a physical energy ratio bounded by the per-bin COPs.
Build note. In environments where the Python bindings can't build, restrict to the relevant crates:
cargo test -p entropyk -p entropyk-cli …(avoid--workspace).
9. Reference tables
AHRI 550/590 IPLV weights (IPLV_WEIGHTS)
| Load | 100 % | 75 % | 50 % | 25 % |
|---|---|---|---|---|
| Weight | 0.01 | 0.42 | 0.45 | 0.12 |
Eurovent ESEER weights (ESEER_WEIGHTS)
| Load | 100 % | 75 % | 50 % | 25 % |
|---|---|---|---|---|
| Weight | 0.03 | 0.33 | 0.41 | 0.23 |
EN 14825 average heating season (EN_14825_AVERAGE_BINS) — 26 bins from
−10 °C to +15 °C, total 4910 h (Strasbourg reference; EU 813/2013 Annex III
Table 5 / EN 14825:2018 Table A.4).
Secondary-fluid cp defaults (used by run when only a mass flow is given):
air 1006 J/(kg·K), water 4186 J/(kg·K). Provide secondary_cp_j_per_kgk or
secondary_capacity_rate_w_per_k to override.
10. Standards references
- AHRI Standard 550/590 (2023) — Performance Rating of Water-Chilling and Heat Pump Water-Heating Packages Using the Vapor Compression Cycle.
- AHRI Standard 551/591 (SI) — metric counterpart of 550/590.
- Eurovent — ESEER definition for liquid chilling packages.
- EN 14511 — rating conditions for air conditioners, liquid chilling packages and heat pumps.
- EN 14825:2018 — Testing and rating at part load conditions and calculation of seasonal performance (SCOP/SEER bin method).
- Commission Regulation (EU) No 813/2013, Annex III — European reference heating seasons (average/warmer/colder).
See also
crates/entropyk/src/rating.rs— the module.crates/cli/src/rate.rs— theratecommand.- CLI_TUTORIAL.md — building and running
runscenarios (thebase_configfor a rating). - DOCUMENTATION.md — the definitive technical manual.