use std::time::{Duration as StdDuration, Instant};
use metering::allocation::session::{MeterSample, SessionSplitConfig, split_session};
use metering::time::holiday::Bundesland;
use metering::vee::substitute::{Policy, SubstitutionReason, substitute};
use metering::vee::validation::{Grade, Rules, validate};
use metering::{
DayBoundary, MeterInterval, QualityFlag, Resolution, Series, billing::aggregation::aggregate,
series::resample::resample,
};
use rust_decimal::{Decimal, dec};
use time::Duration;
use time::macros::date;
const BUDGET: StdDuration = StdDuration::from_secs(60);
fn year_2026() -> Vec<MeterInterval> {
let start = metering::DayBoundary::Strom
.day(date!(2026 - 01 - 01))
.unwrap()
.start();
let end = metering::DayBoundary::Strom
.day(date!(2027 - 01 - 01))
.unwrap()
.start();
let count = (end - start).whole_seconds() / 900;
(0..count)
.map(|i| {
let quarter_of_day = i % 96;
let kwh = if (24..80).contains(&quarter_of_day) {
dec!(2.5)
} else {
dec!(0.5)
};
MeterInterval::quarter_hour(
start + Duration::minutes(15 * i),
kwh,
metering::QualityFlag::Measured,
)
.unwrap()
})
.collect()
}
#[test]
fn a_settlement_year_runs_in_linear_time() {
let started = Instant::now();
let mut year = year_2026();
assert_eq!(year.len(), 35_040, "2026 is not a leap year");
for i in (1..year.len()).step_by(347) {
year[i] = year[i].clone().with_quality(QualityFlag::Faulty);
}
year.drain(20_000..20_200);
let series = Series::new(Resolution::QUARTER_HOUR, DayBoundary::Strom, year).unwrap();
let period = DayBoundary::Strom.year(2026).unwrap();
let rules = Rules::strom(period, period.end(), None).seasonal(Bundesland::Be, 4);
let report = validate(&series, &rules);
assert_eq!(report.grade(), Grade::C);
let policy = Policy::strom(Bundesland::Be, SubstitutionReason::CommunicationFailure);
let filled = substitute(&series, &report, &policy).unwrap();
assert_eq!(filled.series.len(), 35_040);
assert_eq!(filled.substitutes.len(), 300);
assert!(filled.refusals.is_empty());
let billed = aggregate(&filled.series, period).unwrap();
assert!(billed.coverage.is_complete());
assert!(billed.benutzungsdauer_h().is_some());
let s = &filled.series;
assert_eq!(resample(s, Resolution::Hour).unwrap().len(), 8_760);
assert_eq!(resample(s, Resolution::Day).unwrap().len(), 365);
assert_eq!(resample(s, Resolution::Month).unwrap().len(), 12);
assert_eq!(resample(s, Resolution::Year).unwrap().len(), 1);
assert!(
started.elapsed() < BUDGET,
"a settlement year took {:?} — that is a change of complexity class, not a slow machine",
started.elapsed()
);
}
#[test]
fn a_minute_sampled_day_places_in_linear_time() {
let started = Instant::now();
let start = metering::DayBoundary::Strom
.day(date!(2026 - 06 - 01))
.unwrap()
.start();
let samples: Vec<MeterSample> = (0..=1_440)
.map(|m| MeterSample::new(start + Duration::minutes(m), Decimal::from(m)))
.collect();
let series =
split_session(&samples, &SessionSplitConfig::quarter_hourly()).expect("a well-formed day");
let slots = series.as_slice();
assert_eq!(slots.len(), 96);
assert_eq!(
slots.iter().map(|s| s.value()).sum::<Decimal>(),
Decimal::from(1_440u32)
);
assert!(slots.iter().all(|s| s.quality() == QualityFlag::Measured));
assert!(started.elapsed() < BUDGET, "{:?}", started.elapsed());
}