use crate::scheme::{SettlementScheme, SettlementType, TariffSource};
use crate::technology::ErzeugungsArt;
use crate::version::EegGesetz;
use rust_decimal::Decimal;
use time::Date;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", serde(rename_all = "SCREAMING_SNAKE_CASE"))]
pub enum SanktionAlt {
VerguetungAufNull,
VerguetungAufMarktwert,
VerguetungReduziert20Prozent,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", serde(rename_all = "SCREAMING_SNAKE_CASE"))]
pub enum SanktionsTyp {
FernsteuerbarkeitmFehlend,
SpeicherAnforderungNichtErfuellt,
IMssAnforderungNichtErfuellt,
DirektvermarktungspflichtVerletzt,
MastrNichtRegistriert,
InbetriebnahmeVorgabeVerletzt,
VolleinspeisungspflichtVerletzt,
AusfallverguetungHoechstdauerUeberschritten,
EinspeiseverguetungUnzulaessigeNutzung,
VeraeusserungsformWechselUngueltig,
VeraeusserungsformNachweispflichtVerletzt,
ZuordnungsWechselNichtGemeldet,
DoppelvermarktungsverbotVerletzt,
}
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Pflichtverstoss {
pub typ: SanktionsTyp,
pub leistung_kw: Decimal,
pub monate_des_verstosses: u32,
pub nachtraeglich_erfuellt: bool,
pub technischer_defekt: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "serde", serde(rename_all = "SCREAMING_SNAKE_CASE"))]
pub enum Messkonzept {
Volleinspeisung,
Ueberschusseinspeisung,
Direktlieferung,
}
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct CapacityBlock {
pub leistung_kwp: Decimal,
pub verguetungssatz_ct: Decimal,
pub inbetriebnahme: Date,
pub foerderendedatum: Date,
}
#[derive(Debug, Clone, Default)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct SettleInput {
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 sect53b_regional_reduction_ct: Option<Decimal>,
pub negative_price_quarter_hours: Option<u64>,
pub einspeisemanagement_kwh: Option<Decimal>,
pub biomasse: Option<crate::biomasse::BiomassSettlementData>,
pub billing_days_fraction: Option<Decimal>,
pub kwh_during_negative_epex: Option<Decimal>,
pub inbetriebnahme: Option<Date>,
pub inbetriebnahme_typ: crate::technology::InbetriebnahmeTyp,
pub leistung_kwp: Option<Decimal>,
pub foerderendedatum: Option<Date>,
pub billing_date: Option<Date>,
pub capacity_blocks: Vec<CapacityBlock>,
pub messkonzept: Option<Messkonzept>,
pub eeg_gesetz: EegGesetz,
pub erzeugungsart: Option<ErzeugungsArt>,
pub biogas_sect44b_eligible_kwh: Option<Decimal>,
pub has_imesys: bool,
pub marktwert_kategorie: Option<crate::scheme::MarktpreisKategorie>,
}
impl SettleInput {
#[must_use]
pub fn effective_eeg_gesetz(&self) -> EegGesetz {
if let crate::scheme::TariffSource::Transitional(rule) = &self.tariff_source
&& let Some(implied) = rule.implied_eeg_gesetz()
{
return implied;
}
self.eeg_gesetz
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct SettleOutput {
pub settlement_eur: Option<Decimal>,
pub eligible_kwh: Option<Decimal>,
pub positions: Vec<SettlePosition>,
pub status: SettlementStatus,
pub pflichtzahlung_eur: Option<Decimal>,
pub pflichtzahlung_faelligkeitsdatum: Option<Date>,
pub verlaengerungsanspruch_qh: u64,
pub dezentrale_einspeisung_anspruch_verloren: bool,
pub billing_days_fraction_applied: Option<Decimal>,
pub faelligkeitsdatum: Option<Date>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct SettlePosition {
pub description: String,
pub legal_basis: String,
pub kwh: Decimal,
pub rate_ct_kwh: Decimal,
pub eur: Decimal,
}
impl SettlePosition {
pub fn to_line_item(&self) -> billing::LineItem {
use billing::{LineItem, RoundingStrategy};
let rate_eur = self.rate_ct_kwh / rust_decimal::Decimal::from(100);
let mut builder = LineItem::for_usage_rounded(
&self.description,
self.kwh,
"kWh",
rate_eur,
"EUR/kWh",
6,
RoundingStrategy::MidpointAwayFromZero,
)
.meta("legal_basis", self.legal_basis.as_str());
if self.legal_basis.contains("EEG") || self.legal_basis.contains("post-F\u{00f6}rderung") {
builder = builder.tag("eeg");
}
if self.legal_basis.contains("KWKG") {
builder = builder.tag("kwkg");
}
builder = match self.legal_basis.as_str() {
b if b.starts_with("\u{00a7}20 Abs. 3") => builder.tag("managementpraemie"),
b if b.starts_with("\u{00a7}20") || b.starts_with("\u{00a7}\u{00a7}22a") => {
builder.tag("marktpraemie")
}
"\u{00a7}38a EEG 2023" => builder.tag("mieterstrom"),
b if b == "\u{00a7}50b EEG 2023" || b == "\u{00a7}50 EEG 2023" => {
builder.tag("flexibilitaet")
}
b if b.contains("post-F\u{00f6}rderung") => builder.tag("post-eeg-spot"),
"\u{00a7}7 KWKG 2023" => builder.tag("kwk-zuschlag"),
"\u{00a7}21 EEG 2023" => builder.tag("verguetung"),
_ => builder,
};
builder
.build()
.expect("SettlePosition always has a non-empty static description")
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "serde", serde(rename_all = "SCREAMING_SNAKE_CASE"))]
pub enum SettlementStatus {
Calculated,
NoData,
PriceMissing,
FoerderungBeendet,
Sanctioned,
}