Expand description
Automated INVOIC plausibility and tariff validation, over BO4E.
An invoice recipient — an LF, an NB or an ESA — receives INVOIC messages
(PIDs 31001–31011) from NB/GNB/MSB/BIKO counterparties for grid fees (NNE),
meter charges, Leistungen des Preisblatts B and Mehr-/Mindermengen (MMM)
settlement. This library runs automated business-rule checks over BO4E
Rechnung objects — the industry-standard German
energy domain model — and produces a CheckReport that drives the REMADV /
dispute workflow in invoicd.
§Boundary with mako-pruefung
mako-pruefung decides published BDEW Antwortcodes for the wire and
knows nothing but Prüfschritte. This crate decides mako’s own Findings
for the operator queue and the § 147 AO receipt, over BO4E, for every
INVOIC PID — including those with no Entscheidungsbaum. The dependency runs
one way: this crate maps BO4E onto mako_pruefung::rechnung’s Prüfschritte
and calls the walk, which holds no BO4E or money type. See the README for
why they are not one crate.
Where a plausibility check asks the same question as a Prüfschritt —
position arithmetic against A20, the document total against A24, the tax
breakdown against A22/A23 — both paths read the same tolerance:
Summen-level total_tolerance_ppm, position-level
arithmetic_tolerance_ppm. Two knobs for one question would let the engine
record a TotalMismatch Dispute while the walk dispatched a Zahlungsavis.
EDIFACT INVOIC segments
→ [makod adapter: anti-corruption layer]
→ BO4E Rechnung — industry-standard domain model, stored in events
→ InvoicCheckEngine::check — pure business rules, no EDIFACT dependency
→ CheckReport { Ok | Warn | Dispute }
→ REMADV auto-dispatch or dispute workflow§Design principles
- Format-agnostic: zero dependency on
edifact-rs. Operates solely on the BO4E domain model. EDIFACT → BO4E translation belongs in themakodtransport adapter (anti-corruption layer). - Pure library — no I/O, no async, no Tokio dependency.
- Trait-injected stores —
PreisblattStoreis injected by the caller (e.g.invoicdinjects an in-memory store seeded frommarktd’s price-sheet API). - No floating-point money — all amounts are
EuroAmount(i64×10⁻⁵ EUR).
§Monetary precision
EuroAmount stores values as i64 in units of 10⁻⁵ EUR (1/100 000 EUR):
EuroAmount(100_000)= 1.00000 EUREuroAmount(3_456)= 0.03456 EUR (typical NNE unit price per kWh)
This gives five decimal places — sufficient for all BDEW INVOIC precision requirements (NNE unit prices: typically 4 decimal places).
§Example
use invoic_checker::{
check::{CheckConfig, CheckOutcome, InvoicCheckEngine},
tariff::InMemoryPreisblattStore,
amount::EuroAmount,
};
use rubo4e::current::{PreisblattNetznutzung, Rechnung};
let preisblatt_store = InMemoryPreisblattStore::default();
// A `Rechnung` that states no Umsatzsteuer is disputed: §14 Abs. 4 Nr. 8
// UStG makes the rate and the amount mandatory, and without them the
// recipient has no Vorsteuerabzug.
let rechnung = Rechnung::default();
let report = InvoicCheckEngine::check(
31001,
"9900357000004",
&rechnung,
&preisblatt_store,
&CheckConfig::default(),
);
assert_eq!(report.outcome, CheckOutcome::Dispute);Re-exports§
pub use amount::EuroAmount;pub use check::CheckConfig;pub use check::CheckOutcome;pub use check::CheckReport;pub use check::Finding;pub use check::FindingKind;pub use check::InvoicCheckEngine;pub use check::is_stornierung;pub use error::CheckError;pub use rechnung::EmpfaengerFakten;pub use rechnung::StornoEmpfaengerFakten;pub use rechnung::antwort_auf_erneute_rechnung;pub use rechnung::antwort_auf_rechnung;pub use rechnung::antwort_auf_stornorechnung;pub use tariff::InMemoryPreisblattStore;pub use tariff::PreisblattStore;
Modules§
- amount
- Monetary amount type for INVOIC plausibility checks.
- check
- INVOIC plausibility check engine — operates on BO4E
Rechnung. - error
- Errors from the
invoic-checkerpipeline. - rechnung
- The ESA’s own invoice check —
E_0264/E_0266, driven off a BO4ERechnung. - tariff
- Price-sheet store — seeded from PRICAT 27003 EDIFACT messages.