Skip to main content

dpp_calc/co2e/
cfb.rs

1//! Battery Carbon Footprint (CFB) calculator — Phase 2 placeholder.
2//!
3//! Will implement Article 7 of EU Battery Regulation 2023/1542 once the carbon
4//! footprint delegated act under that article is adopted. **It has not been.**
5//! It was due February 2025 and has slipped repeatedly; as of July 2026 no OJ
6//! number exists. The methodology is expected to follow the PEF Category Rules
7//! (PEF-CR) for rechargeable batteries.
8//!
9//! Do not mistake Commission Delegated Regulation (EU) 2025/606 for this act —
10//! that one covers recycling efficiency and material recovery rates, a different
11//! subject, and is not a basis for carbon footprint calculation.
12//!
13//! **Status: stub.** This module compiles but all entry points return
14//! `CalcError::NotImplemented`. Implementation is gated on Phase 1 completion:
15//!   1. Signed ecoinvent / EF dataset reseller sublicense.
16//!   2. Legal warranty scope agreed with counsel.
17//!   3. Reference CFB vectors extracted from the notified-body test report.
18//!
19//! ⚠️ COMPLIANCE-PIN PENDING: CFB Delegated Act number ("EU 2025/…") must be
20//! confirmed against EUR-Lex before implementation begins. Cite the final OJ number.
21
22use crate::error::CalcError;
23use crate::factor::FactorProvider;
24use crate::receipt::CalculationReceipt;
25use crate::ruleset::Ruleset;
26use serde::{Deserialize, Serialize};
27
28/// Regulatory ruleset for the Battery Carbon Footprint methodology.
29///
30/// Extends [`Ruleset`] — concrete impls will carry the CFB-specific parameters
31/// (allocation rules, system boundary, performance class thresholds per EU 2023/1542
32/// Delegated Act). No concrete impls exist yet — gated on Phase 1 (signed factor-data
33/// license + confirmed delegated act number).
34pub trait CfbRuleset: Ruleset {
35    // Battery Regulation 2023/1542, Art. 7, CFB Delegated Act (March 2025 draft).
36    // Parameters TBD from the delegated act's Annex; to be added in Phase 2.
37}
38
39/// Inputs for the battery CFB calculation per EU 2023/1542 Art. 7.
40///
41/// Field names follow the Annex XIII data attribute names from the regulation.
42#[derive(Debug, Clone, Serialize, Deserialize)]
43pub struct CfbInputs {
44    /// Battery model identifier (used in the receipt for traceability).
45    pub model_id: String,
46    /// Battery chemistry family (e.g. "LFP", "NMC", "NCA") — determines process
47    /// route. Family-level per `BatteryChemistry`; sub-ratios (e.g. "NMC811") are
48    /// composition data carried by the cathode/anode material attributes, not here.
49    pub battery_chemistry: String,
50    /// Declared nominal capacity in kWh.
51    pub nominal_capacity_kwh: f64,
52    // Additional life-cycle-stage inputs will be added when the Phase 1 data
53    // license is in place and the exact CFB methodology steps are confirmed.
54}
55
56/// Output of the CFB calculation.
57#[derive(Debug, Clone, Serialize, Deserialize)]
58pub struct CfbResult {
59    /// Total cradle-to-grave carbon footprint in kg CO₂e per kWh of capacity.
60    pub cfb_kg_co2e_per_kwh: f64,
61    /// Absolute carbon footprint of this battery unit in kg CO₂e.
62    pub cfb_kg_co2e_total: f64,
63    /// Proof-of-calculation receipt (includes factor dataset version).
64    pub receipt: CalculationReceipt,
65}
66
67/// Calculate the CFB for one battery per EU 2023/1542 Art. 7.
68///
69/// Currently returns `Err(CalcError::NotImplemented)` — implementation is
70/// gated on a signed ecoinvent/EF data sublicense. See module-level docs.
71pub fn calculate_cfb(
72    _inputs: &CfbInputs,
73    _provider: &dyn FactorProvider,
74) -> Result<CfbResult, CalcError> {
75    Err(CalcError::NotImplemented {
76        methodology: "battery-cfb".into(),
77        reason: "gate: signed ecoinvent/EF sublicense + legal warranty + confirmed \
78                 CFB Delegated Act number (EU 2025/…)"
79            .into(),
80    })
81}
82
83#[cfg(test)]
84mod tests {
85    use super::*;
86    use crate::factor::SyntheticFactorProvider;
87
88    #[test]
89    fn calculate_cfb_returns_not_implemented() {
90        let inputs = CfbInputs {
91            model_id: "test-battery".into(),
92            battery_chemistry: "NMC".into(),
93            nominal_capacity_kwh: 60.0,
94        };
95        let provider = SyntheticFactorProvider::new(std::iter::empty::<(String, f64)>());
96        let err = calculate_cfb(&inputs, &provider).unwrap_err();
97        assert!(
98            matches!(err, CalcError::NotImplemented { .. }),
99            "expected NotImplemented, got {err}"
100        );
101    }
102}