0db9b8belisted three things here as not done. `CarbonAccount` dropped provenance, and `account_request` is a public way in. A report with zero correct answers carries `joules_per_correct == u64::MAX` as a sentinel, which `sci_from_eval_report` multiplied by `correct.max(1)` into a u64::MAX µJ energy term (18.4 TJ). And the test that pins the bridge's provenance sat behind the `eval-bridge` feature, which nothing in the workspace enables, so `cargo test --workspace` never compiled it, nor any mutation it catches. What holds now: - `CarbonAccount` gains `it_energy_source`, the input `JouleCost`'s provenance unchanged, and `facility_energy_source`. The facility joules are `it_joules` × PUE, so they are measured only when every part of them was: the IT joules were measured, and the PUE added no overhead. A PUE above 1 is a published or assumed facility ratio applied to the reading, energy no counter on the host read; a figure containing any of it is an estimate. A PUE of 1 adds nothing, so measured IT joules stay measured, as an estimated zero adds nothing to a live span in eoc-cascade. Grid intensity has its own provenance (the reading's provider and kind) and does not enter the tag. Both fields read back as `Estimated` when absent. Nothing outside this crate builds a `CarbonAccount`. - `SciScore::energy_source` is now the account's facility provenance, because the score's energy term is facility energy. Before, it was the input's provenance, so measured IT joules at the default PUE of 1.58 gave a "measured" term of which 37% no counter read. This changes what0db9b8beset: an SCI score at PUE > 1 is now estimated even from measured joules. The SCI numbers do not change. - The bridge's pure part is compiled in every build. `EvalEnergy` (micro-joules per correct answer, correct answers, provenance) and `sci_per_correct_answer` hold all the scoring, including the `JouleCost` construction 0db9b8be's mutation targeted. `sci_from_eval_report`, still behind `eval-bridge`, only copies the report's three fields across. - A run with no correct answer has no score per correct answer: `sci_per_correct_answer` and `sci_from_eval_report` return `None` for `correct == 0` and for the `u64::MAX` sentinel, whatever `correct` says beside it. The sentinel is never read as energy. `sci_from_eval_report` now returns `Option<SciScore>`; its only callers are this crate's tests. Tests in the default build: an account's (IT, facility) tags are (Measured, Measured) at PUE 1, (Measured, Estimated) at 1.2, (Estimated, Estimated) from an estimate at either, and (Measured, Measured) at a PUE of 0.5, which is clamped to 1; the figures are the same whichever tag. An account round-trips and an old one reads back estimated. A score is measured at PUE 1, estimated at 1.2 from measured joules, with the same SCI. An eval run's score keeps the run's provenance (2 × 1 J × 1.2 = 2.4 J over R = 2), and a run with 0 correct, or the sentinel with 3 correct, has none. `account_per_request` now asserts its tags. Under `eval-bridge`, a report's score equals its run's score for both provenances and both PUEs, and a 0-correct report has none. Falsified, each mutant restored byte-for-byte (sha256 checked); every one but the last runs in the default build: - 0db9b8be's mutant, `JouleCost::measured(total)` in the bridge, now fails `an_eval_runs_score_keeps_the_runs_provenance` in a plain `cargo test`; - an account dropping the IT provenance fails 1 test; - the facility tag ignoring the PUE fails 3, ignoring the IT provenance 3; - the score taking the input's provenance (0db9b8be's rule) fails 2; - removing the no-correct-answer guard fails 1, and so does checking `correct == 0` without the sentinel; - the `eval-bridge` wrapper tagging every report measured fails `an_eval_reports_score_is_its_runs_score` under `--features eval-bridge`. That wrapper's three-field copy is the only part a default build cannot see. `cargo test -p eoc-carbon` passes (42 + 8), and with `--features eval-bridge` (43 + 8). No clippy warning in this crate, before or after. Not done: `joules_per_correct` is an integer quotient, so the total reconstructed from it can fall short of the run's by less than one µJ per correct answer. Co-Authored-By: Claude Opus 5.5 <[email protected]>
36 lines
1.1 KiB
Rust
36 lines
1.1 KiB
Rust
//! Spec example: 1000 J at 400 gCO2e/kWh, PUE 1.2 → 0.1333... gCO2e.
|
|
|
|
use chrono::Utc;
|
|
use eoc_carbon::account::account_request;
|
|
use eoc_carbon::intensity::{CarbonIntensity, IntensityKind, ProviderKind};
|
|
use eoc_carbon::pue::Pue;
|
|
use eoc_core::{JouleCost, JouleSource};
|
|
|
|
#[test]
|
|
fn one_kilojoule_400g_pue_1_2() {
|
|
let cost = JouleCost {
|
|
microjoules: 1_000_000_000, // 1000 J
|
|
source: JouleSource::Measured,
|
|
};
|
|
let intensity = CarbonIntensity::new(
|
|
"EXAMPLE",
|
|
400.0,
|
|
IntensityKind::Average,
|
|
ProviderKind::Mock,
|
|
Utc::now(),
|
|
);
|
|
let pue = Pue::new(1.2);
|
|
let acc = account_request(&cost, &intensity, &pue);
|
|
// 1000 / 3.6e6 * 400 * 1.2 = 0.13333...
|
|
assert!(
|
|
(acc.g_co2e - 0.13333333).abs() < 1e-5,
|
|
"got {} gCO2e",
|
|
acc.g_co2e
|
|
);
|
|
assert!((acc.facility_joules - 1200.0).abs() < 1e-9);
|
|
assert_eq!(acc.pue, 1.2);
|
|
assert_eq!(acc.g_co2e_per_kwh, 400.0);
|
|
// The IT joules were read; the PUE's overhead was not.
|
|
assert_eq!(acc.it_energy_source, JouleSource::Measured);
|
|
assert_eq!(acc.facility_energy_source, JouleSource::Estimated);
|
|
}
|