1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
//! German energy-metering quantities: exact, cited, and calendar-correct.
//!
//! A pure library — no I/O, no async, no clock — for the quantities German
//! metering is settled on. Every quantity is a
//! [`Decimal`](rust_decimal::Decimal), every rounding names its places and
//! mode ([`precision`]), and every rule cites the clause it implements. It
//! computes kWh, m³ and kW, not money.
//!
//! Time is Europe/Berlin. A [`DayBoundary`] — `Strom` at 00:00, `Gas` at
//! 06:00 — names days, months and years as UTC [`Period`]s of their real
//! length (23, 24 or 25 hours a day). Interval data enters as
//! [`MeterInterval`]s, validated once into a [`Series`] — sorted, on one grid,
//! one channel — which the rest of the crate consumes.
//!
//! # Quick start
//!
//! **Aggregate a series** into the quantities a billing period is invoiced on:
//!
//! ```rust
//! use metering::prelude::*;
//! use time::{Duration, macros::date};
//!
//! let day = DayBoundary::Strom.day(date!(2026 - 06 - 01)).unwrap();
//! let slots = (0..96)
//! .map(|i| MeterInterval::quarter_hour(day.start() + Duration::minutes(15 * i), dec!(1), QualityFlag::Measured))
//! .collect::<Result<Vec<_>, _>>()?;
//! let series = Series::new(Resolution::QUARTER_HOUR, DayBoundary::Strom, slots)?;
//!
//! let period = aggregate(&series, day)?;
//! assert_eq!(period.arbeitsmenge, dec!(96)); // kWh
//! assert_eq!(period.spitzenleistung_kw, Some(dec!(4))); // 1 kWh in 15 min
//! assert_eq!(period.coverage.pct(), Some(dec!(100)));
//! # Ok::<(), Box<dyn std::error::Error>>(())
//! ```
//!
//! **Validate, then substitute** — a missing quarter-hour is an error finding,
//! and a gap up to 2 h is interpolated between validated measured values:
//!
//! ```rust
//! use metering::prelude::*;
//! use metering::time::holiday::Bundesland;
//! use metering::vee::substitute::{Method, SubstitutionReason};
//! use metering::vee::validation::Grade;
//! use time::{Duration, macros::{date, datetime}};
//!
//! let day = DayBoundary::Strom.day(date!(2026 - 06 - 01)).unwrap();
//! let kwh = |i: i64| Decimal::new(200 + i, 3); // a ramp, 0.200 … 0.295 kWh
//! let slots = (0..96)
//! .filter(|i| *i != 50) // one quarter-hour never arrived
//! .map(|i| MeterInterval::quarter_hour(day.start() + Duration::minutes(15 * i), kwh(i), QualityFlag::Measured))
//! .collect::<Result<Vec<_>, _>>()?;
//! let series = Series::new(Resolution::QUARTER_HOUR, DayBoundary::Strom, slots)?;
//!
//! let rules = Rules::strom(day, datetime!(2026-07-01 0:00 UTC), None);
//! 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)?;
//! assert_eq!(filled.substitutes[0].method, Method::Interpolation);
//! assert_eq!(filled.substitutes[0].value, dec!(0.25)); // between 0.249 and 0.251
//! assert_eq!(validate(&filled.series, &rules).grade(), Grade::A);
//! # Ok::<(), Box<dyn std::error::Error>>(())
//! ```
//!
//! **The Gastag and gas conversion** — a gas day runs 06:00 to 06:00 local,
//! and a metered volume becomes kWh_Hs through Brennwert and Zustandszahl:
//!
//! ```rust
//! use metering::gas::conversion::gas_m3_to_kwh_hs;
//! use metering::prelude::*;
//! use time::macros::{date, datetime};
//!
//! let gastag = DayBoundary::Gas.day(date!(2026 - 01 - 15)).unwrap();
//! assert_eq!(gastag.start(), datetime!(2026-01-15 5:00 UTC));
//! // The Gastag across the autumn change is 25 hours long.
//! assert_eq!(DayBoundary::Gas.day(date!(2026 - 10 - 24)).unwrap().duration().whole_hours(), 25);
//!
//! // 100 m³ × 11.2 kWh/m³ × 0.95
//! assert_eq!(gas_m3_to_kwh_hs(dec!(100), dec!(11.2), dec!(0.95)), Some(dec!(1064.000)));
//! ```
//!
//! # Features
//!
//! `serde` adds `Serialize`/`Deserialize` to the coded enums, identifiers,
//! inputs and most results, and enables `time/serde`; the covered types and
//! wire representation are under
//! [Design](https://hupe1980.github.io/metering/docs/design/#serde).
//!
//! # Modules
//!
//! | Group | Modules |
//! |---|---|
//! | Series, time, IDs | [`series`] (intervals, [`Series`], readings, resampling), [`time`] (calendar, holidays, resolution), [`ids`] (MaLo, MeLo, EIC, BDEW codes, OBIS) |
//! | VEE | [`vee`] — validation and grading, substitute values, Messtyp |
//! | Billing | [`billing`] — aggregation, Zählzeiten, reactive energy, imbalance, losses, forecast |
//! | SLP | [`slp`] — `strom` (BDEW SLP), `gas` (SigLinDe) |
//! | Gas, heat | [`gas`] (DVGW G 685 conversion), [`heat`] (HeizkostenV) |
//! | Grid | [`grid`] — § 14a EnWG, Ausfallarbeit, EN 50160, rollout |
//! | Allocation | [`allocation`] — UTILTS Berechnungsformel, § 42b community, § 42c sharing, sessions |
//! | EEG | [`eeg`] — MiSpeL, EEG § 51/§ 51a, EnFG § 46 |
//! | Cross-cutting | [`precision`] (every rounding), [`error`], [`prelude`] |
//!
//! Start with the [guides](https://hupe1980.github.io/metering); the
//! [design constraints](https://hupe1980.github.io/metering/docs/design/) and
//! [regulatory basis](https://hupe1980.github.io/metering/docs/regulatory-basis/)
//! are the reference for rules and sources.
pub use ParseError;
pub use ObisCode;
pub use ;
pub use Series;
pub use ;
pub use ;
pub use Resolution;
/// The common pipeline in one import: the core nouns, the entry points from
/// readings to a billing period, and `Decimal` with its `dec!` literal.
/// The README and the guide pages, compiled as doctests: every `rust` block
/// published outside this crate's source builds and runs against the current
/// API. A block that is not meant to compile is fenced `text`.