use alloc::vec::Vec;
pub const COBALT_RECYCLED_PCT_2031: f64 = 16.0;
pub const LEAD_RECYCLED_PCT_2031: f64 = 85.0;
pub const LITHIUM_RECYCLED_PCT_2031: f64 = 6.0;
pub const NICKEL_RECYCLED_PCT_2031: f64 = 6.0;
pub const COBALT_RECYCLED_PCT_2036: f64 = 26.0;
pub const LEAD_RECYCLED_PCT_2036: f64 = 85.0;
pub const LITHIUM_RECYCLED_PCT_2036: f64 = 12.0;
pub const NICKEL_RECYCLED_PCT_2036: f64 = 15.0;
#[derive(Debug, Clone, Copy)]
pub struct RecycledContentInput {
pub cobalt_pct: Option<f64>,
pub lithium_pct: Option<f64>,
pub nickel_pct: Option<f64>,
pub lead_pct: Option<f64>,
}
#[derive(Debug, Clone, Copy)]
pub struct RecycledContentShortfall {
pub material: &'static str,
pub declared_pct: f64,
pub required_pct: f64,
}
#[must_use]
pub fn annex_x_shortfalls_2031(input: &RecycledContentInput) -> Vec<RecycledContentShortfall> {
check_targets(
input,
COBALT_RECYCLED_PCT_2031,
LEAD_RECYCLED_PCT_2031,
LITHIUM_RECYCLED_PCT_2031,
NICKEL_RECYCLED_PCT_2031,
)
}
#[must_use]
pub fn annex_x_shortfalls_2036(input: &RecycledContentInput) -> Vec<RecycledContentShortfall> {
check_targets(
input,
COBALT_RECYCLED_PCT_2036,
LEAD_RECYCLED_PCT_2036,
LITHIUM_RECYCLED_PCT_2036,
NICKEL_RECYCLED_PCT_2036,
)
}
fn check_targets(
input: &RecycledContentInput,
cobalt_req: f64,
lead_req: f64,
lithium_req: f64,
nickel_req: f64,
) -> Vec<RecycledContentShortfall> {
let mut out = Vec::new();
if let Some(pct) = input.cobalt_pct {
if !pct.is_finite() || pct < cobalt_req {
out.push(RecycledContentShortfall {
material: "cobalt",
declared_pct: pct,
required_pct: cobalt_req,
});
}
}
if let Some(pct) = input.lead_pct
&& (!pct.is_finite() || pct < lead_req)
{
out.push(RecycledContentShortfall {
material: "lead",
declared_pct: pct,
required_pct: lead_req,
});
}
if let Some(pct) = input.lithium_pct
&& (!pct.is_finite() || pct < lithium_req)
{
out.push(RecycledContentShortfall {
material: "lithium",
declared_pct: pct,
required_pct: lithium_req,
});
}
if let Some(pct) = input.nickel_pct
&& (!pct.is_finite() || pct < nickel_req)
{
out.push(RecycledContentShortfall {
material: "nickel",
declared_pct: pct,
required_pct: nickel_req,
});
}
out
}
#[cfg(test)]
mod tests {
use super::*;
fn all_metals(co: f64, pb: f64, li: f64, ni: f64) -> RecycledContentInput {
RecycledContentInput {
cobalt_pct: Some(co),
lead_pct: Some(pb),
lithium_pct: Some(li),
nickel_pct: Some(ni),
}
}
#[test]
fn exactly_at_2031_targets_passes() {
let input = all_metals(16.0, 85.0, 6.0, 6.0);
assert!(annex_x_shortfalls_2031(&input).is_empty());
}
#[test]
fn above_2031_targets_passes() {
let input = all_metals(20.0, 90.0, 10.0, 10.0);
assert!(annex_x_shortfalls_2031(&input).is_empty());
}
#[test]
fn below_2031_cobalt_flagged() {
let input = all_metals(15.0, 85.0, 6.0, 6.0); let shortfalls = annex_x_shortfalls_2031(&input);
assert_eq!(shortfalls.len(), 1);
assert_eq!(shortfalls[0].material, "cobalt");
assert_eq!(shortfalls[0].required_pct, 16.0);
}
#[test]
fn multiple_shortfalls_all_returned() {
let input = all_metals(10.0, 80.0, 3.0, 4.0); assert_eq!(annex_x_shortfalls_2031(&input).len(), 4);
}
#[test]
fn undeclared_metals_not_flagged() {
let input = RecycledContentInput {
cobalt_pct: Some(20.0),
lead_pct: None,
lithium_pct: None,
nickel_pct: None,
};
assert!(annex_x_shortfalls_2031(&input).is_empty());
}
#[test]
fn phase2_stricter_than_phase1() {
let input = all_metals(16.0, 85.0, 6.0, 6.0);
assert!(annex_x_shortfalls_2031(&input).is_empty());
let shortfalls = annex_x_shortfalls_2036(&input);
assert!(shortfalls.iter().any(|s| s.material == "cobalt"));
}
#[test]
fn nan_cobalt_treated_as_shortfall() {
let input = RecycledContentInput {
cobalt_pct: Some(f64::NAN),
lead_pct: None,
lithium_pct: None,
nickel_pct: None,
};
let shortfalls = annex_x_shortfalls_2031(&input);
assert_eq!(shortfalls.len(), 1);
assert_eq!(shortfalls[0].material, "cobalt");
}
#[test]
fn infinity_cobalt_treated_as_shortfall() {
let input = RecycledContentInput {
cobalt_pct: Some(f64::INFINITY),
lead_pct: None,
lithium_pct: None,
nickel_pct: None,
};
let shortfalls = annex_x_shortfalls_2031(&input);
assert_eq!(shortfalls.len(), 1);
assert_eq!(shortfalls[0].material, "cobalt");
}
}