compliance: derive the A-G band instead of accepting one #64

Merged
dcharlot merged 1 commit from compliance/banding-function into main 2026-08-29 03:09:24 -04:00
Owner

EnergyLabel is an A–G enum with an ordering and a description, and nothing in this workspace ever derived one. Every use supplies a letter the caller had already decided:

build_sample("svc-1", 0.35, EnergyLabel::A)   // the label is an input

The label was a container for someone else's judgement — the one shape a rating must not have. And the only threshold rule that did exist is:

pub fn is_compliant(&self) -> bool {
    self.pue < 1.5 && self.renewable_pct >= 50.0
}

Two magic floats, no provenance, no version, and no way to band anything.

BandingScale::rate is the missing function. What matters is less the arithmetic than what it refuses to let a caller do.

A letter is not a rating

Two facilities both rated "B" under different threshold sets are not comparable, and nothing about the letter says so. A Rating therefore cannot be constructed without its scale — it carries scale_id, metric, unit, and the measured value, so a reader re-derives the letter rather than trusting it.

comparable_with is an explicit check. better_than returns None for incomparable ratings rather than Ordering::Equal — refusing to rank is a different answer from ranking equal, and collapsing the two is exactly how a bare enum invites the mistake.

A revision is a different scale

Because scale_id travels inside every rating, re-tuning thresholds cannot silently re-band history. The test pins a value that is C under v1 and D under v2, and shows the two ratings do not compare.

The rest

  • Scales are data, not match arms — a new scale is a value, not a recompile.
  • Integers in a declared unit — milli_pue rather than 1.5. A float boundary makes the band depend on how the value was parsed, and band edges are precisely where that bites.
  • Boundaries earn the better band — stated once, tested at every edge, so "on the line" isn't left to the reader.
  • A non-monotonic scale cannot rate at all. Overlapping boundaries would assign different letters to one value depending on evaluation order, so validate rejects them and rate refuses rather than emitting a band from a scale that cannot rank. A scale must also carry an id and a unit — a rating that cannot cite its scale is back to being a bare letter.

No canonical scale is shipped

The examples in the tests are named example.* on purpose. Which thresholds are correct for a given metric is a policy question this crate has no standing to settle, and baking one in would repeat the pue < 1.5 mistake with more ceremony.

Verification

43 crate tests pass (7 new), no clippy warnings in the new file.

`EnergyLabel` is an A–G enum with an ordering and a description, and **nothing in this workspace ever derived one**. Every use supplies a letter the caller had already decided: ```rust build_sample("svc-1", 0.35, EnergyLabel::A) // the label is an input ``` The label was a container for someone else's judgement — the one shape a rating must not have. And the only threshold rule that did exist is: ```rust pub fn is_compliant(&self) -> bool { self.pue < 1.5 && self.renewable_pct >= 50.0 } ``` Two magic floats, no provenance, no version, and no way to band anything. `BandingScale::rate` is the missing function. What matters is less the arithmetic than what it refuses to let a caller do. ## A letter is not a rating Two facilities both rated "B" under different threshold sets are **not comparable**, and nothing about the letter says so. A `Rating` therefore cannot be constructed without its scale — it carries `scale_id`, metric, unit, and the measured value, so a reader **re-derives** the letter rather than trusting it. `comparable_with` is an explicit check. `better_than` returns `None` for incomparable ratings rather than `Ordering::Equal` — **refusing to rank is a different answer from ranking equal**, and collapsing the two is exactly how a bare enum invites the mistake. ## A revision is a different scale Because `scale_id` travels inside every rating, re-tuning thresholds cannot silently re-band history. The test pins a value that is **C under v1 and D under v2**, and shows the two ratings do not compare. ## The rest - **Scales are data, not match arms** — a new scale is a value, not a recompile. - **Integers in a declared unit** — `milli_pue` rather than `1.5`. A float boundary makes the band depend on how the value was parsed, and band edges are precisely where that bites. - **Boundaries earn the better band** — stated once, tested at every edge, so "on the line" isn't left to the reader. - **A non-monotonic scale cannot rate at all.** Overlapping boundaries would assign different letters to one value depending on evaluation order, so `validate` rejects them and `rate` refuses rather than emitting a band from a scale that cannot rank. A scale must also carry an id and a unit — a rating that cannot cite its scale is back to being a bare letter. ## No canonical scale is shipped The examples in the tests are named `example.*` on purpose. Which thresholds are correct for a given metric is a policy question this crate has no standing to settle, and baking one in would repeat the `pue < 1.5` mistake with more ceremony. ## Verification 43 crate tests pass (7 new), no clippy warnings in the new file.
compliance: derive the A-G band instead of accepting one
All checks were successful
jouleclaw public surface must stay documented / Public-surface audit (jouleclaw-rs) (pull_request) Successful in 59s
078aeb3c2a
`EnergyLabel` is an A–G enum with an ordering and a description, and nothing in
this workspace ever *derived* one — every use supplies a letter the caller had
already decided (`build_sample("svc-1", 0.35, EnergyLabel::A)`). The label was a
container for someone else's judgement, which is the one shape a rating must not
have. The only threshold rule that did exist,
`EuComplianceReport::is_compliant`, is a hardcoded `pue < 1.5 && renewable_pct
>= 50.0`: two magic floats with no provenance, no version, and no way to band
anything.

`BandingScale::rate` is the missing function. What matters is less the
arithmetic than what it refuses to let a caller do.

**A letter is not a rating.** Two facilities both rated "B" under different
threshold sets are not comparable, and nothing about the letter says so. A
`Rating` therefore cannot be constructed without its scale: it carries the
`scale_id`, the metric, the unit, and the measured value, so a reader
re-derives the letter rather than trusting it. `comparable_with` is an explicit
check, and `better_than` returns `None` for incomparable ratings rather than
`Ordering::Equal` — refusing to rank is a different answer from ranking equal,
and collapsing them is exactly how a bare enum invites the mistake.

**A revision is a different scale.** Because the `scale_id` travels inside every
rating, re-tuning thresholds cannot silently re-band history — the test pins a
value that is C under v1 and D under v2, and shows the two ratings do not
compare.

**Scales are data, not match arms.** A new scale is a value, not a recompile.

**Integers, in a declared unit.** Thresholds are `u64` in a unit the scale names
(milli-PUE rather than `1.5`), matching the receipt family's discipline: a
float boundary makes the band depend on how the value was parsed, and band edges
are precisely where that bites.

**Boundaries earn the better band**, stated once and tested at every edge, so
"on the line" is not left to the reader.

**A non-monotonic scale cannot rate at all.** Overlapping boundaries would
assign different letters to one value depending on evaluation order, so
`validate` rejects them and `rate` refuses rather than emitting a band from a
scale that cannot rank. A scale must also carry an id and a unit, since a rating
that cannot cite its scale is back to being a bare letter.

No scale is shipped as canonical. The examples in the tests are named
`example.*` on purpose: which thresholds are correct for a given metric is a
policy question this crate has no standing to settle, and baking one in would
repeat the `pue < 1.5` mistake with more ceremony.

Verified: 43 crate tests pass (7 new), no clippy warnings in the new file.
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
Transaction-Science/open-standards!64
No description provided.