Skip to main content

SettleInput

Struct SettleInput 

Source
pub struct SettleInput {
Show 28 fields pub scheme: SettlementScheme, pub tariff_source: TariffSource, pub settlement_type: SettlementType, pub einspeisemenge_kwh: Option<Decimal>, pub marktwert_ct_kwh: Option<Decimal>, pub sanktion: Option<SanktionAlt>, pub pflichtverstoss: Vec<Pflichtverstoss>, pub aw_reductions: AwReductionContext, pub negative_price_quarter_hours: Option<u64>, pub einspeisemanagement_kwh: Option<Decimal>, pub biomasse: Option<BiomassSettlementData>, pub billing_days_fraction: Option<Decimal>, pub kwh_during_negative_epex: Option<Decimal>, pub inbetriebnahme: Option<Date>, pub inbetriebnahme_typ: InbetriebnahmeTyp, pub leistung_kwp: Option<Decimal>, pub foerderendedatum: Option<Date>, pub billing_date: Option<Date>, pub capacity_blocks: Vec<CapacityBlock>, pub eeg_gesetz: EegGesetz, pub erzeugungsart: Option<ErzeugungsArt>, pub aw_is_gross: bool, pub biogas_sect44b_eligible_kwh: Option<Decimal>, pub has_imesys: bool, pub marktwert_kategorie: Option<MarktpreisKategorie>, pub sect51_optin_wirksam_ab: Option<Date>, pub sect51_abs3_unreported_days: u32, pub ist_pilotwindanlage: bool,
}
Expand description

Input for a single settlement period calculation.

All monetary rates are in ct/kWh (Cent per kWh), not EUR/kWh. Supply Default::default() for fields not applicable to the model.

§Multi-EEG-version support

EEG has been revised many times (2000, 2004, 2009, 2012, 2014, 2017, 2021, 2023). The correct verguetungssatz_ct is fixed at the plant’s commissioning date and does not change over the 20-year Förderdauer. Supply the rate that was valid when the plant was commissioned — use eeg_billing::rates or einsd’s rate lookup table for historical rates.

The formula logic (which model is applicable, whether §27 applies, etc.) differs by EEG version and commissioning date. Supply inbetriebnahme so the engine can apply the correct version-specific guards automatically.

Fields§

§scheme: SettlementScheme

Which regulatory formula to apply.

  • FeedInTariff → §21 EEG Einspeisevergütung
  • MarketPremium → §20 EEG Gleitende Marktprämie (incl. Ausschreibung via tariff_source)
  • TenantElectricity → §21 Abs. 3 Mieterstrom
  • PostEeg → post-Förderung spot (configurable post_eeg_price_floor)
  • KwkSurcharge → §7 KWKG
  • TemporaryFeedInTariff → §21 Abs. 1 Satz 1 Nr. 3 Ausfallvergütung
  • Eigenverbrauch → no payment
  • FlexibilityPremium → §50b bestehende Biomasseanlagen
  • FlexibilitySurcharge → §50a neue Biomasseanlagen (capacity payment)
§tariff_source: TariffSource

Where the Anzulegender Wert (AW) comes from.

  • Statutory → §48 EEG statutory tables (default)
  • Auction(meta) → BNetzA tender award (Ausschreibung: same formula as MarketPremium)
  • Transitional(rule) → §100 EEG Übergangsregelung
§settlement_type: SettlementType

Settlement type: initial, correction, or reversal.

§einspeisemenge_kwh: Option<Decimal>

Einspeisemenge kWh for the billing period. None → output status = SettlementStatus::NoData.

§marktwert_ct_kwh: Option<Decimal>

Monthly EPEX Spot Day-Ahead average or technology-specific Jahresmarktwert (§20 Abs. 2 + Anlage 1 EEG 2023) in ct/kWh.

This is the market reference price used for all calculations that compare EEG payment against the market:

  • MarketPremium: spread = max(0, eff_AW - marktwert_ct_kwh)
  • PostEeg: plant paid at marktwert_ct_kwh per kWh
  • SanktionAlt::VerguetungAufMarktwert: old-regime sanction uses this as rate
  • §44b biogas excess: excess kWh paid at marktwert_ct_kwh

The library does not distinguish between EPEX monthly average and Jahresmarktwert — the caller resolves which value applies and passes it here.

§sanktion: Option<SanktionAlt>

§47 EEG (weggefallen/deleted in EEG 2023) / old EEG rules via §100 Übergangsregelung.

§52 EEG ≤2021 sanction tier (old regime via §100 Übergangsregelung).

§52 EEG 2021/2017 has three tiers that each reduce the Vergütung differently. These are for plants governed by EEG ≤2021 rules (commissioned before 01.01.2023).

SanktionAlt§52 EEG ≤2021Vergütung effect
VerguetungAufNullAbs. 1 Nr. 1: MaStR not registeredEUR 0
VerguetungAufNullAbs. 1 Nr. 2a: §10b DirektvermarktungspflichtEUR 0
VerguetungAufNullAbs. 1 Nr. 4: §27a Eigenversorgung (Ausschreibung)EUR 0
VerguetungAufMarktwertAbs. 2 Nr. 1: §9 Abs. 1/2/5 Fernsteuerbarkeit→ EPEX Marktwert
VerguetungAufMarktwertAbs. 2 Nr. 1a: §9 Abs. 8 Messeinrichtung→ EPEX Marktwert
VerguetungReduziert20ProzentAbs. 3 Nr. 1: MaStR partial/late× 0.80

For EEG 2023 plants, use pflichtverstoss instead — §52 EEG 2023 charges €10/kW/month without suspending Vergütung.

None = no sanction (normal settlement).

§pflichtverstoss: Vec<Pflichtverstoss>

§52 EEG 2023 — Pflichtverstöße (compliance violations).

§52 applies to plants governed by EEG 2023 rules (commissioned after 01.01.2023, or old plants for violations introduced in EEG 2023).

Each violation results in a separate payment obligation from the plant operator to the NB of €10/kW/month (§52 Abs. 2 EEG 2023). This is NOT a reduction of the Vergütung — the operator still receives the full Vergütung AND must separately pay the §52 penalty to the NB (§52 Abs. 6: the NB may net these).

§Common violation types

SanktionsTyp§52 Abs. 1 Nr.Trigger
FernsteuerbarkeitFehlendNr. 1§9 Abs. 1/2: no remote-control equipment
SpeicherAnforderungNichtErfuelltNr. 2§9 Abs. 5: missing storage requirement
MastrNichtRegistriertNr. 11Plant not registered in MaStR

Use crate::foerderdauer::calculate_pflichtzahlung to compute the penalty amount.

When pflichtverstoss is Some, the settlement formula still computes the full Vergütung — the penalty is returned separately in the output’s pflichtzahlung_eur field.

Default: empty Vec (no violations).

§aw_reductions: AwReductionContext

§§53b–54 EEG 2023 — reductions that act on the anzulegender Wert.

§53b Regionalnachweise, §53c Stromsteuerbefreiung and §54 solar first-segment auction defects. Each reduces the AW before the settlement formula, which matters for the gleitende Marktprämie: its max(0, …) floor must absorb the reduction rather than the result being pushed negative afterwards. See crate::aw_reductions.

§negative_price_quarter_hours: Option<u64>

§51a EEG 2023 — quarter-hours during which §51 reduced Vergütung to zero.

When provided, the engine computes SettleOutput.verlaengerungsanspruch_qh: Solar PV: ceil(qh / 2) · Others: 1:1 factor.

§einspeisemanagement_kwh: Option<Decimal>

§13a EnWG (Redispatch 2.0) — kWh curtailed by the NB (Einspeisemanagement compensation).

§51 Negativpreisregel does NOT apply to these kWh (§19 Abs. 2 EEG 2023).

§biomasse: Option<BiomassSettlementData>

§§ 39i, 42–44 EEG 2023 — Biomass/biogas fuel composition for settlement enforcement.

When set for biomass or biogas plants, the engine enforces:

  • § 39i Abs. 1 — a plant holding a Zuschlag whose Getreide- und Mais-Anteil exceeds the Höchstanteil for its Gebotstermin has no § 19 Abs. 1 claim, and the period settles as KeinAnspruch at EUR 0. A plant without a Zuschlag is outside Abs. 1 and is unaffected.
  • § 44 Güllekleinanlage eligibility is recorded in the position label for audit transparency — it does not change the formula here; the caller supplies the § 44 Abs. 1 verguetungssatz_ct (use crate::rates::guelle_lookup).

Use crate::biomasse::BiomassSettlementData::new to derive from fuel composition data. None = plant is not biomass/biogas.

§billing_days_fraction: Option<Decimal>

§25 Abs. 1 Satz 3 EEG — Fraction of the billing month with entitlement.

When None, the library auto-computes from billing_date, inbetriebnahme, and foerderendedatum via foerderdauer::compute_billing_days_fraction(). When Some(x), the provided value is used directly (override).

Set explicitly only when the auto-computed value would be wrong for your settlement scenario (rare edge cases). For standard plant lifecycles, leave as None and ensure billing_date, inbetriebnahme, and foerderendedatum are set.

§kwh_during_negative_epex: Option<Decimal>

§51 EEG — kWh produced during negative EPEX hours (to be excluded).

Under §51 EEG 2023, for plants ≥100 kWp commissioned after 01.01.2016, EEG Vergütung is zero during hours when the hourly EPEX Spot price is negative AND the consecutive run of negative hours meets the version-specific threshold (§51 EEG 2023: any period; §51 EEG 2017: ≥6h; §51 EEG 2021: ≥4h).

When inbetriebnahme and leistung_kwp are both set, the engine automatically guards this rule based on eeg_gesetz.

Does NOT apply to §51b biogas Ausschreibungsanlagen — those plants use a different rule (AW = 0 when EPEX ≤ 2 ct/kWh).

Default: None (rule not applied).

§inbetriebnahme: Option<Date>

Plant commissioning date (Inbetriebnahmedatum).

When set, enables automatic EEG-version-aware rule enforcement:

  • §51 EEG Negativpreisregel: threshold and kW exemption depend on EEG version derived from commissioning year (see eeg_gesetz). Key boundary: §100 Abs. 1 Satz 4 EEG 2017 exempts plants commissioned before 01.01.2016. Plants from 2016-01-01 onwards are subject to §51 EEG 2017 (6h, 500 kW/3 MW).
  • Audit position labels: include the commissioning year for traceability.

For multi-block plants (§24 Anlagenerweiterung), the commissioning dates live on each CapacityBlock instead.

§inbetriebnahme_typ: InbetriebnahmeTyp

Type of commissioning event — for audit trail and Förderdauer rules.

Determines whether the Förderdauer clock resets (only Repowering resets it) and which lifecycle state the plant is in. Stored in einsd’s eeg_anlagen.inbetriebnahme_typ column.

InbetriebnahmeTypFörderdauerAudit relevance
Erstinbetriebnahme (default)starts at inbetriebnahmeNormal plant
Wiederinbetriebnahmecontinues from originalRestart after shutdown
Modernisierungcontinues from originalEquipment replacement
Repoweringresets to repowering dateNew 20-year clock
Zusammenlegungoldest component date§24 merger
Erweiterungnew block from extension date§24 capacity add

The engine records this in position descriptions for full audit traceability. Default: InbetriebnahmeTyp::Erstinbetriebnahme.

§leistung_kwp: Option<Decimal>

Installed peak power in kWp (or kW_el for KWKG).

Used for:

  • §27 EEG guard (threshold: 100 kWp)
  • §51 Abs. 2 kW exemption (aggregated per §24 when capacity_blocks is set)

Ignored when capacity_blocks is non-empty.

§foerderendedatum: Option<Date>

EEG subsidy end date from the plant registry.

When set together with billing_date, the engine automatically returns FoerderungBeendet when billing_date > foerderendedatum.

For KWKG plants, this is the calendar-year fallback (§8 Abs. 4 KWKG): Förderung ends at min(kwk_hour_limit, inbetriebnahme + 15y).

§billing_date: Option<Date>

First day of the billing period (ISO 8601 month-start, e.g. 2026-07-01).

Used together with foerderendedatum for automatic FoerderungBeendet detection. When omitted, the caller must check FoerderungBeendet manually.

§capacity_blocks: Vec<CapacityBlock>

Additional capacity blocks for §24 EEG Anlagenerweiterung / Zusammenlegung.

When non-empty, the engine performs multi-block settlement:

  1. Each block receives a proportional share of einspeisemenge_kwh (proportional to leistung_kwp of each block).
  2. The primary block uses SettleInput.verguetungssatz_ct and SettleInput.inbetriebnahme / foerderendedatum.
  3. Blocks whose foerderendedatum < billing_date are expired (EUR 0).
  4. The §27 Negativpreisregel is applied per-block based on each block’s commissioning date and capacity.

Leave empty for single-block plants (the vast majority).

§eeg_gesetz: EegGesetz

EEG law year applicable to this plant (Gesetz-Jahr des anzuwendenden EEG).

Determines which version-specific rules the engine applies:

EEG law version governing this plant — the §52 Pflichtverstoß regime and the §100 Übergangsbestimmungen.

Not the source of the §51 rules: those are keyed on the commissioning date (see SettleInput::negativpreis_regime), because the Solarspitzengesetz rewrote §51 with effect from 25.02.2025 — inside the EEG 2023 range.

Use EegGesetz::from_db_year to convert the eeg_gesetz DB column, or EegGesetz::from_inbetriebnahme_year as a fallback.

§erzeugungsart: Option<ErzeugungsArt>

Plant technology type (optional, used for §51 EEG 2017 wind exemption).

Under EEG 2017, wind turbines get a separate 3 MW kW exemption (§51 Abs. 3 Nr. 1); other plants get the 500 kW exemption (Nr. 2). Derive from einsd erzeugungsart column via ErzeugungsArt::from_db_str.

None is treated as non-wind (conservative: 500 kW exemption under EEG 2017).

§aw_is_gross: bool

§53 Abs. 1 EEG 2023 — whether the Einspeisevergütung rate supplied in the scheme is the gross anzulegender Wert (as published in §48/BNetzA bulletins) rather than the net Vergütungssatz.

When true, the engine subtracts the §53 Abs. 1 deduction (0.4 ct/kWh Solar/Wind, 0.2 ct/kWh Wasserkraft/Biomasse/Geothermie/Gas) keyed on erzeugungsart for SettlementScheme::FeedInTariff. Default false: the rate is already net (einsd’s eeg_verguetungssaetze stores net rates), so nothing is deducted — this prevents a double deduction.

§biogas_sect44b_eligible_kwh: Option<Decimal>

§44b Abs. 1 EEG 2023 — Biogas >100 kW: annual 45% Bemessungsleistung cap.

For Biogas plants (fermentation biogas, excluding fermentation-biomass §44 plants and Ausschreibungsanlagen §39) with installed capacity >100 kW, the EEG payment is limited to the share of annual production corresponding to 45% of installed kW:

annual_quota_kwh = leistung_kw × 0.45 × <§3 Nr. 6 hours of the year>

The hour count is not a flat 8 760: it is the actual hours of the calendar year (8 784 in a leap year) less the hours before the plant’s first generation. See crate::sect44b_jahreskontingent_kwh, which is what both the settlement and the check_sect44b_quota MCP tool call.

When set, this field is the eligible kWh for the current billing period (the caller tracks cumulative annual production and passes min(kwh, remaining_quota)):

  • Eligible part: normal remuneration (this field’s value)
  • Excess part (einspeisemenge_kwh - eligible):
    • MarketPremium: AW reduces to zero, Marktprämie = 0 (§44b Abs. 1 Satz 2)
    • FeedInTariff: paid at EPEX Marktwert (epex_avg_ct_kwh), requires EPEX price

None = cap does not apply (plant ≤100 kW, fermentation biomass §44, Ausschreibung §39, or non-Biogas technology).

Legal basis: §44b Abs. 1 EEG 2023 (BGBl. I Nr. 28, 10.01.2023).

§has_imesys: bool

§51 Abs. 2 Nr. 1 EEG 2023 — whether the sub-100-kW exemption has lapsed.

The exemption is transitional: §51 Abs. 2 Nr. 1 grants it only „für Zeiträume vor dem Ablauf des Kalenderjahres, in dem die Anlage mit einem intelligenten Messsystem ausgestattet wird“. It therefore survives the whole installation year and lapses on 1 January of the year after, from when a 30 kWp plant is subject to §51 like any other. Derive it with crate::negativpreis::imesys_befreiung_entfallen rather than comparing the installation date with the settlement period.

It lifts only that exemption. The 2 kW floor of Abs. 2 Nr. 2 stands until the Bundesnetzagentur’s §85 Abs. 2 Nr. 12 Festlegung, and the exemptions of the older Fassungen (400 kW, 500 kW, 3 MW) are unaffected — they have no iMSys condition.

Default: false (conservative — retains the exemption when unknown).

§marktwert_kategorie: Option<MarktpreisKategorie>

Technology-specific Jahresmarktwert category (§20 Abs. 2 + Anlage 1 EEG 2023).

Documents which ÜNB technology category marktwert_ct_kwh was sourced from. The library uses marktwert_ct_kwh directly — this field is informational only (validation aid and audit label).

§sect51_optin_wirksam_ab: Option<Date>

§100 EEG — the date a Bestandsanlage’s opt-in into the Solarspitzengesetz regime takes effect.

The operator declares in Textform to the Netzbetreiber that §§ 51 and 51a shall apply; the declaration runs at the earliest from the end of the calendar year in which the plant is fitted with an iMSys. Derive it with crate::negativpreis::optin_wirksam_ab. From that date the plant is under the Solarspitzengesetz regime and its anzulegender Wert rises by crate::negativpreis::SECT51_OPTIN_ZUSCHLAG_CT_KWH.

None — the usual case — leaves the plant on its commissioning vintage.

§sect51_abs3_unreported_days: u32

§51 Abs. 3 EEG — calendar days of an unreported negative-price period, for a plant on the Ausfallvergütung.

An operator on the Ausfallvergütung must report, with the §71 Abs. 1 Nr. 1 data, the quantity it fed in while the Spotmarktpreis was continuously negative. Where it does not, the claim for that calendar month falls by 5 % per calendar day on which such a period fell, wholly or partly.

Set this to the number of those days when the figure is missing, and 0 when it was reported (or the month had no negative period). Applies only to SettlementScheme::TemporaryFeedInTariff; ignored elsewhere.

§ist_pilotwindanlage: bool

§3 Nr. 37 EEG 2023 — Pilotwindenergieanlage an Land.

Every Fassung of §51 carves these out of the Negativpreisregel, whatever their size. The status is a BNetzA/FGW certification fact about the turbine, so it is declared rather than derived.

Default: false.

Implementations§

Source§

impl SettleInput

Source

pub fn effective_eeg_gesetz(&self) -> EegGesetz

Effective EEG law version for §51/§52 calculation.

When tariff_source = Transitional(rule) and the rule implies a specific EegGesetz, that implied version is returned instead of self.eeg_gesetz.

This prevents silent miscalculation when a §100 Transitional rule is set without the corresponding eeg_gesetz being updated by the caller.

tariff_sourceReturns
Statutory / Auction(_)self.eeg_gesetz (caller-supplied)
Transitional(Pre2016Bestandsschutz)EegGesetz::Eeg2012 — §51 never applies
Transitional(Eeg2017Negativpreis6h)EegGesetz::Eeg2017 — 6h threshold
Transitional(OldPlantBeforeEeg2023)EegGesetz::Eeg2021 — 4h threshold
Transitional(_) otherself.eeg_gesetz
§Example
use eeg_billing::{SettleInput, EegGesetz};
use eeg_billing::scheme::{TariffSource, Paragraph100Rule};

// Pre-2016 plant: §51 must never apply, regardless of what eeg_gesetz says.
let input = SettleInput {
    tariff_source: TariffSource::Transitional(Paragraph100Rule::Pre2016Bestandsschutz),
    eeg_gesetz: EegGesetz::Eeg2017, // deliberately wrong — overridden
    ..SettleInput::default()
};
assert_eq!(input.effective_eeg_gesetz(), EegGesetz::Eeg2012);
Source

pub fn negativpreis_regime(&self) -> NegativpreisRegime

The §51 Negativpreisregel version governing this plant.

Derived from inbetriebnahme, because the Solarspitzengesetz boundary (25.02.2025) falls inside a calendar year and inside the EEG 2023 range. When the commissioning date is unknown the plant’s law version supplies a coarse fallback, and a §100 Transitional rule overrides both — a rule that pins a plant to a pre-2016 vintage must keep §51 off it.

Trait Implementations§

Source§

impl Clone for SettleInput

Source§

fn clone(&self) -> SettleInput

Returns a duplicate of the value. Read more
1.0.0 (const: unstable) · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for SettleInput

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Default for SettleInput

Source§

fn default() -> SettleInput

Returns the “default value” for a type. Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = !

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, !>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.