Skip to main content

dpp_calc/
lib.rs

1//! EU-methodology compliance calculators for Odal Node.
2//!
3//! Pure, stateless calculation of regulatory metrics from typed inputs. No I/O,
4//! no infrastructure — every function is deterministic and side-effect free.
5//!
6//! **Placement:** these compute *EU methodology* (cradle-to-gate CO₂e, battery
7//! CFB stub) plus a non-regulatory repairability heuristic, so they change when a
8//! regulation changes and therefore live in `dpp-core` under Apache-2.0 — not in
9//! the platform.
10//! The licensing split: the methodology is open; licensed LCI datasets are
11//! never bundled here and are injected at runtime via [`factor::FactorProvider`].
12//!
13//! # Architecture
14//!
15//! ```text
16//! ┌──────────────────────────────────────────────────────────────────────┐
17//! │  dpp-calc (Apache-2.0, open)                                         │
18//! │                                                                      │
19//! │  co2e::calculate()          — cradle-to-gate, operator-supplied EFs  │
20//! │  repairability::calculate() — non-regulatory heuristic → A–E band    │
21//! │  co2e::cfb::calculate_cfb() — STUB → CalcError::NotImplemented       │
22//! │                                                                      │
23//! │  ruleset_registry           — date-based ruleset resolution          │
24//! │  Ruleset / RegulatoryBasis  — legal citation in every ruleset        │
25//! │  FactorProvider             — runtime injection point for LCI data   │
26//! │  CalculationReceipt         — proof-of-calculation envelope          │
27//! └──────────────────────────────────────────────────────────────────────┘
28//! ```
29//!
30//! Every calculator emits a [`receipt::CalculationReceipt`] that records the
31//! input hash, ruleset id + version, factor dataset version and table hash —
32//! ready to be stored in the proof-bound store for notified-body audit.
33//!
34//! # Adding a new sector calculator
35//!
36//! ## New methodology (algorithm not yet in dpp-calc)
37//!
38//! 1. Add `src/{methodology}/` with four files (see `co2e/` or `repairability/`):
39//!    - `mod.rs`        — `pub fn calculate(inputs, ruleset) -> Result<...>` + output types
40//!    - `parameters.rs` — typed input struct (derives `Serialize` for receipt hashing)
41//!    - `thresholds.rs` — `pub trait {Methodology}Ruleset: Ruleset { ... }` + concrete impls
42//!    - `golden_vectors.rs` — `#[cfg(test)]` regression tests
43//! 2. Every `impl RepairabilityRuleset / Co2eRuleset / CfbRuleset` must also
44//!    `impl Ruleset` and fill `regulatory_basis()` with the EU citation.
45//! 3. Add a `resolve_{methodology}()` function to `ruleset_registry/resolve.rs`
46//!    and a `&NewRuleset` row to `all_rulesets()`.
47//! 4. Write golden vectors, including the `all_concrete_rulesets_have_non_empty_regulatory_basis` pattern.
48//! 5. Register the module in `lib.rs`.
49//!
50//! ## New product category on an existing methodology
51//!
52//! 1. Add `impl Ruleset + impl {Methodology}Ruleset` for the new struct in `thresholds.rs`.
53//!    Fill `regulatory_basis` from the product-specific delegated act.
54//! 2. Add a row to the resolver table in `ruleset_registry/resolve.rs` and to
55//!    `all_rulesets()` (one-liner each).
56//! 3. Add golden vectors. Run `cargo test -p dpp-calc`.
57//!
58//! # Effective dates
59//!
60//! How a governing ruleset is selected, why `Pending` has no date, and the
61//! conditional `max(floor, entry-into-force + N months)` arithmetic that is
62//! deliberately not built yet:
63//! see `docs/architecture/EFFECTIVE-DATES.md`.
64//!
65//! ## Pending delegated act (stub)
66//!
67//! Use `Effectivity::pending(empowerment, adoption_deadline)` and
68//! `regulatory_basis.regulation = "pending — {PEFCR title}"`.
69//!
70//! There is **no date sentinel**. A pending ruleset has no application date, so
71//! it resolves for no date at all and `ensure_active_on` returns
72//! `CalcError::RulesetUndetermined` naming the instrument being waited on. The
73//! type stays compile-visible for future wiring. A far-future `from` date would
74//! assert something the regulation does not say — EU staged obligations are
75//! written as *"from &lt;date&gt; or N months after entry into force, whichever
76//! is the latest"*, so until the act lands the date is unknown, not distant.
77//!
78//! ## Superseded ruleset
79//!
80//! - Set the ruleset's `Effectivity` to `closed(from, last_valid_day)`.
81//! - Set `regulatory_basis.superseded_by` to the new ruleset's ID string.
82//! - Keep the row in `ruleset_registry` — receipts reference rulesets by
83//!   ID + version, so removing a row makes old receipts unverifiable.
84
85#![forbid(unsafe_code)]
86
87// Methodology-agnostic spine. Kept private and re-exported below so the internal
88// grouping never leaks: external callers keep using `dpp_calc::error`,
89// `dpp_calc::factor`, `dpp_calc::receipt`, and `dpp_calc::ruleset`.
90mod kernel;
91
92pub mod co2e;
93pub mod repairability;
94pub mod repairability_index;
95pub mod ruleset_registry;
96
97// ── Stable public paths for the spine ────────────────────────────────────────
98pub use kernel::{assessability, clock, error, factor, receipt, ruleset};
99
100/// Compile-checks this crate's README examples.
101///
102/// A README example is a public claim about the API, and nothing else in the
103/// build compiles one. Without this, a README can advertise a function that
104/// does not exist — which is exactly what happened before this harness landed.
105#[cfg(doctest)]
106#[doc = include_str!("../README.md")]
107struct ReadmeDoctests;