$monatsgrenzen Monatsnummer 1 bis 12 als Schluessel */ public static function wirksameLastspitzengrenze( int $modus, float $konstanteGrenze, array $monatsgrenzen, int $monat ): ?float { if ($modus === 0) { return null; } if ($modus === 1) { return $konstanteGrenze; } if ($modus !== 2) { throw new InvalidArgumentException('Lastspitzenmodus muss 0, 1 oder 2 sein.'); } if ($monat < 1 || $monat > 12 || !array_key_exists($monat, $monatsgrenzen)) { throw new InvalidArgumentException('Fuer den aktuellen Monat fehlt eine Lastspitzengrenze.'); } return (float) $monatsgrenzen[$monat]; } public static function bestimmeBetriebsart( bool $aktiv, bool $netzleistungGueltig, float $netzleistung, string $bisherigeBetriebsart, bool $peakAktiv, float $umschaltschwelleUeberschuss, float $umschaltschwelleLastspitze, float $sollwertSolarladen, float $wirksameLastspitzengrenze, float $umschaltdifferenzProzent ): string { if (!$aktiv || !$netzleistungGueltig) { return self::BETRIEBSART_INAKTIV; } if (!$peakAktiv) { return self::BETRIEBSART_PV; } if ($umschaltdifferenzProzent < 0 || $umschaltdifferenzProzent > 100) { throw new InvalidArgumentException('Umschaltdifferenz muss zwischen 0 und 100 Prozent liegen.'); } $kandidat = $bisherigeBetriebsart; if ($netzleistung < $umschaltschwelleUeberschuss) { $kandidat = self::BETRIEBSART_PV; } elseif ($netzleistung > $umschaltschwelleLastspitze) { $kandidat = self::BETRIEBSART_PEAK; } elseif (!in_array($kandidat, [self::BETRIEBSART_PV, self::BETRIEBSART_PEAK], true)) { $kandidat = self::BETRIEBSART_PV; } if ($kandidat === $bisherigeBetriebsart || !in_array($bisherigeBetriebsart, [self::BETRIEBSART_PV, self::BETRIEBSART_PEAK], true) || $umschaltdifferenzProzent === 0.0 ) { return $kandidat; } $pvKorrektur = $sollwertSolarladen - $netzleistung; $peakKorrektur = $wirksameLastspitzengrenze - $netzleistung; $bezugswert = max(abs($pvKorrektur), abs($peakKorrektur), 1.0); $differenzProzent = abs($pvKorrektur - $peakKorrektur) / $bezugswert * 100.0; return $differenzProzent >= $umschaltdifferenzProzent ? $kandidat : $bisherigeBetriebsart; } /** * @param list> $verbraucher Vollstaendige, bereits gepruefte Verbraucherdaten * @return array{ * Verteilbudget_W: int, * AktuelleVerbraucherleistung_W: int, * NichtSteuerbar_W: int, * Sollwerte: array, * Restabweichung_W: int * } */ public static function verteile( float $netzleistung, float $zielnetzleistung, array $verbraucher, string $prioritaetsfeld ): array { if (!in_array($prioritaetsfeld, ['PrioritaetPV', 'PrioritaetPeak'], true)) { throw new InvalidArgumentException('Unbekanntes Prioritaetsfeld.'); } $aktuelleLeistung = 0; $nichtSteuerbar = 0; $steuerbar = []; foreach ($verbraucher as $eintrag) { self::pruefeVerteilEintrag($eintrag, $prioritaetsfeld); $leistung = self::aktuelleLeistung($eintrag['Daten']); $aktuelleLeistung += $leistung; $daten = $eintrag['Daten']; if (!$daten['Verfuegbar'] || !$daten['AenderungMoeglich'] || $daten['Leistungswerte_W'] === []) { $nichtSteuerbar += $leistung; continue; } $steuerbar[] = [ 'InstanzID' => $eintrag['InstanzID'], 'Prioritaet' => $daten[$prioritaetsfeld], 'Leistungswerte_W' => $daten['Leistungswerte_W'], ]; } usort($steuerbar, static function (array $links, array $rechts): int { return [$links['Prioritaet'], $links['InstanzID']] <=> [$rechts['Prioritaet'], $rechts['InstanzID']]; }); $verteilbudget = (int) round($aktuelleLeistung + $zielnetzleistung - $netzleistung); $sollwerte = []; $minimumSumme = 0; foreach ($steuerbar as $eintrag) { $minimum = self::kleinsterLeistungswert($eintrag['Leistungswerte_W']); $sollwerte[$eintrag['InstanzID']] = $minimum; $minimumSumme += $minimum; } $verfuegbar = max(0, $verteilbudget - $nichtSteuerbar - $minimumSumme); foreach ($steuerbar as $eintrag) { $instanzID = $eintrag['InstanzID']; $minimum = $sollwerte[$instanzID]; $neuerSollwert = self::groessterLeistungswertBis( $eintrag['Leistungswerte_W'], $minimum + $verfuegbar ); $verfuegbar -= $neuerSollwert - $minimum; $sollwerte[$instanzID] = $neuerSollwert; } $zugeteilt = $nichtSteuerbar + array_sum($sollwerte); return [ 'Verteilbudget_W' => $verteilbudget, 'AktuelleVerbraucherleistung_W' => $aktuelleLeistung, 'NichtSteuerbar_W' => $nichtSteuerbar, 'Sollwerte' => $sollwerte, 'Restabweichung_W' => $verteilbudget - $zugeteilt, ]; } /** * @param array $daten */ public static function angenommenerSollwert(array $daten): ?int { foreach ($daten['Zustand'] as $zustand) { if ($zustand['Kennung'] === 'Sollleistung_W') { return is_int($zustand['Wert']) ? $zustand['Wert'] : null; } } return null; } /** * @param array $daten */ private static function aktuelleLeistung(array $daten): int { if ($daten['Leistungsquelle'] !== Nachrichtenvertrag::LEISTUNGSQUELLE_NICHT_VORHANDEN && is_numeric($daten['Istleistung_W']) ) { return (int) round((float) $daten['Istleistung_W']); } return self::angenommenerSollwert($daten) ?? 0; } /** * @param array $eintrag */ private static function pruefeVerteilEintrag(array $eintrag, string $prioritaetsfeld): void { if (!isset($eintrag['InstanzID'], $eintrag['Daten']) || !is_int($eintrag['InstanzID']) || $eintrag['InstanzID'] <= 0 || !is_array($eintrag['Daten']) ) { throw new InvalidArgumentException('Verbrauchereintrag ist ungueltig.'); } Nachrichtenvertrag::pruefeVerbraucherdaten($eintrag['Daten']); if (!array_key_exists($prioritaetsfeld, $eintrag['Daten'])) { throw new InvalidArgumentException('Prioritaet fehlt im Verbrauchereintrag.'); } } /** * @param list $leistungswerte */ private static function kleinsterLeistungswert(array $leistungswerte): int { $erster = $leistungswerte[0]; return is_int($erster) ? $erster : $erster['Von_W']; } /** * @param list $leistungswerte */ private static function groessterLeistungswertBis(array $leistungswerte, int $obergrenze): int { $gewaehlt = self::kleinsterLeistungswert($leistungswerte); foreach ($leistungswerte as $eintrag) { if (is_int($eintrag)) { if ($eintrag > $obergrenze) { break; } $gewaehlt = $eintrag; continue; } if ($eintrag['Von_W'] > $obergrenze) { break; } $gewaehlt = min($eintrag['Bis_W'], $obergrenze); } return $gewaehlt; } }