use metering::vee::validation::{Grade, Rule, Rules, validate};
use metering::{DayBoundary, MeterInterval, ObisCode, QualityFlag, Resolution, Series};
use rust_decimal::{Decimal, dec};
use time::macros::{date, datetime};
#[test]
fn day_starts_are_the_four_codings_of_the_allgemeine_festlegungen() {
let start = |b: DayBoundary, d| b.day(d).unwrap().start();
assert_eq!(
start(DayBoundary::Strom, date!(2026 - 01 - 15)),
datetime!(2026-01-14 23:00 UTC),
"2300"
);
assert_eq!(
start(DayBoundary::Strom, date!(2026 - 07 - 15)),
datetime!(2026-07-14 22:00 UTC),
"2200"
);
assert_eq!(
start(DayBoundary::Gas, date!(2026 - 01 - 15)),
datetime!(2026-01-15 5:00 UTC),
"0500"
);
assert_eq!(
start(DayBoundary::Gas, date!(2026 - 07 - 15)),
datetime!(2026-07-15 4:00 UTC),
"0400"
);
}
#[test]
fn the_worked_examples_of_kap_3_1() {
let june = |b: DayBoundary| b.month(2021, time::Month::June).unwrap();
assert_eq!(
june(DayBoundary::Strom).start(),
datetime!(2021-05-31 22:00 UTC)
);
assert_eq!(
june(DayBoundary::Strom).end(),
datetime!(2021-06-30 22:00 UTC)
);
assert_eq!(
june(DayBoundary::Gas).start(),
datetime!(2021-06-01 4:00 UTC)
);
assert_eq!(june(DayBoundary::Gas).end(), datetime!(2021-07-01 4:00 UTC));
let start = |b: DayBoundary, d| b.day(d).unwrap().start();
assert_eq!(
start(DayBoundary::Strom, date!(2021 - 11 - 30)),
datetime!(2021-11-29 23:00 UTC)
);
assert_eq!(
start(DayBoundary::Gas, date!(2021 - 11 - 30)),
datetime!(2021-11-30 5:00 UTC)
);
}
#[test]
fn the_obis_value_groups_follow_the_codeliste() {
for messart in [6u8, 8, 9, 29] {
let bezug: ObisCode = format!("1-0:1.{messart}.0").parse().unwrap();
let lieferung: ObisCode = format!("1-0:2.{messart}.0").parse().unwrap();
assert!(bezug.is_import() && !bezug.is_export(), "{bezug}");
assert!(
lieferung.is_export() && !lieferung.is_import(),
"{lieferung}"
);
}
assert!(ObisCode::STROM_BEZUG_MAXIMUM.is_maximum());
assert!(ObisCode::STROM_BEZUG_TOTAL.is_zaehlerstand());
assert!(ObisCode::STROM_BEZUG_VORSCHUB.is_vorschub());
assert!(ObisCode::STROM_BEZUG_LASTGANG.is_lastgang());
for c in 3..=8u8 {
let code: ObisCode = format!("1-0:{c}.8.0").parse().unwrap();
assert!(code.is_reactive(), "1-0:{c}.8.0");
}
let fehler: ObisCode = "1-0:1.8.63".parse().unwrap();
assert!(fehler.is_fehlerregister());
assert_eq!(fehler.tariff(), None);
assert_eq!("1-0:1.8.62".parse::<ObisCode>().unwrap().tariff(), Some(62));
assert_eq!(ObisCode::GAS_VOLUME_M3.to_string(), "7-0:3.0.0");
assert_eq!(ObisCode::STROM_BEZUG_LASTGANG.to_string(), "1-0:1.29.0");
}
#[test]
fn a_day_holds_92_96_or_100_quarter_hours() {
let now = datetime!(2027-01-01 0:00 UTC);
for (date, count) in [
(date!(2026 - 03 - 29), 92),
(date!(2024 - 02 - 29), 96),
(date!(2026 - 10 - 25), 100),
] {
let day = DayBoundary::Strom.day(date).unwrap();
let slots = |n: i64| {
let ivs = (0..n)
.map(|i| {
let v = Decimal::new(1000 + (i * 37) % 200, 3);
MeterInterval::quarter_hour(
day.start() + time::Duration::minutes(15 * i),
v,
QualityFlag::Measured,
)
.unwrap()
})
.collect();
Series::new(Resolution::QUARTER_HOUR, DayBoundary::Strom, ivs).unwrap()
};
let report = validate(&slots(count), &Rules::strom(day, now, None));
assert_eq!(report.grade(), Grade::A, "{date}: {:?}", report.findings);
if count == 100 {
let short = validate(&slots(96), &Rules::strom(day, now, None));
let gap = short.by_rule(Rule::Gap).next().unwrap();
assert_eq!(gap.reference, Some(dec!(4)));
}
}
}