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
//! The `BillingProvider` trait — one implementation per product type.
//!
//! Every product type (electricity, gas, EEG feed-in, HEMS…) implements
//! `BillingProvider`. The `BillingEngine` orchestrates them in order.
//!
//! ## Execution order and tax computation
//!
//! Providers run in registration order. Each receives `prior_positions` — all
//! positions produced by earlier providers. Tax providers (MwSt) are typically
//! registered last and compute their amount from `prior_positions`.
//!
//! ```text
//! ElectricityProvider → commodity + grid + Stromsteuer positions
//! GridChargeProvider → NNE/KA positions (if not already in Electricity)
//! StromsteuerProvider → levy position (if separate from ElectricityProvider)
//! MwStProvider → tax position on sum(prior_positions)
//! ```
//!
//! The `is_tax_pass()` method marks tax providers so the engine knows to run them
//! in a second pass (after all commodity/levy providers have executed).
use crateEngineError;
use crateBillingContext;
use crate;
// ── Quantities ────────────────────────────────────────────────────────────────
pub use crateQuantities;
// ── Market time unit ──────────────────────────────────────────────────────────
/// Market Time Unit (MTU) length for the EPEX day-ahead auction, in minutes.
///
/// Since 2025-10-01 the SDAC day-ahead auction settles on **15-minute**
/// products (96 quarter-hours per delivery day; 92/100 on the DST days) —
/// EPEX SPOT go-live 01.10.2025. All spot pricing is keyed on this MTU.
pub const MTU_MINUTES: i64 = 15;
/// Floor a UTC instant to the start of its EPEX market time unit (quarter-hour).
///
/// CET/CEST are whole-hour offsets, so a local quarter-hour boundary is always
/// a UTC quarter-hour boundary — flooring in UTC is therefore DST-safe and
/// needs no timezone conversion. This is the canonical spot-price map key:
/// [`Quantities::dynamic_epex_prices`](crate::Quantities::dynamic_epex_prices)
/// is keyed on it, and a consumption interval is floored to it before lookup.
///
/// There is deliberately no `SpotPriceSource` trait behind this. One existed,
/// with one implementation over a `HashMap` and a documented invitation to add
/// NordPool and Tibber adapters; every construction path in the workspace
/// passed it an **empty** map and priced from `dynamic_epex_prices` anyway. A
/// seam nothing enters is not an extension point, it is a second code path to
/// keep correct — and this one hid the price lookup behind a `dyn` call that
/// could return `None` for reasons the caller could not see.
// ── BillingProvider trait ─────────────────────────────────────────────────────
/// A product or service component that generates billing positions.
///
/// Implement this trait for each billable product type. The engine calls
/// `bill()` for each registered provider in order, passing the accumulated
/// positions from all earlier providers.
///
/// ## Tax providers
///
/// Override `is_tax_pass()` to return `true` when this provider computes taxes
/// on the accumulated positions (e.g. MwSt). The engine ensures all commodity/
/// levy providers run before any tax provider.
///
/// ## Example
///
/// ```rust,ignore
/// struct MyFlatFeeProvider { eur: Decimal }
///
/// impl BillingProvider for MyFlatFeeProvider {
/// fn bill(
/// &self,
/// _ctx: &BillingContext,
/// _quantities: &Quantities,
/// _prior: &[BillingPosition],
/// ) -> Result<Vec<BillingPosition>, EngineError> {
/// Ok(vec![
/// BillingPosition::debit("Service Fee", Decimal::ONE, "Pauschal", self.eur, PositionCategory::Fee)
/// .with_tag("service_fee"),
/// ])
/// }
/// }
/// ```