$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 $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 < $sollwertSolarladen) { $kandidat = self::BETRIEBSART_PV; } elseif ($netzleistung > $wirksameLastspitzengrenze) { $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 * @param array $bezogeneEnergieWh Bezogene Energie je Verbraucher in Wh * @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 $bezogeneEnergieWh = [] ): 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['Leistungswerte_W'] === []) { $nichtSteuerbar += $leistung; continue; } $festwert = self::istFestwert($daten['Leistungswerte_W']); if ($festwert) { $nichtSteuerbar += $leistung; } $energie = $bezogeneEnergieWh[$eintrag['InstanzID']] ?? 0.0; if ((!is_int($energie) && !is_float($energie)) || (is_float($energie) && !is_finite($energie)) ) { throw new InvalidArgumentException('Bezogene Energie muss eine endliche Zahl sein.'); } $steuerbar[] = [ 'InstanzID' => $eintrag['InstanzID'], 'Prioritaet' => $daten[$prioritaetsfeld], 'Energiegruppe' => (int) round((float) $energie / 2000.0), 'Leistungswerte_W' => $daten['Leistungswerte_W'], 'Festwert' => $festwert, ]; } usort($steuerbar, static function (array $links, array $rechts): int { return [ $links['Prioritaet'], $links['Energiegruppe'], $links['InstanzID'], ] <=> [ $rechts['Prioritaet'], $rechts['Energiegruppe'], $rechts['InstanzID'], ]; }); $verteilbudget = (int) round($aktuelleLeistung + $zielnetzleistung - $netzleistung); $sollwerte = []; $ausgangssumme = 0; foreach ($steuerbar as $eintrag) { if ($eintrag['Festwert']) { $sollwerte[$eintrag['InstanzID']] = self::kleinsterLeistungswert( $eintrag['Leistungswerte_W'] ); continue; } $ausgangswert = self::neutralerLeistungswert($eintrag['Leistungswerte_W']); $sollwerte[$eintrag['InstanzID']] = $ausgangswert; $ausgangssumme += $ausgangswert; } $rest = $verteilbudget - $nichtSteuerbar - $ausgangssumme; foreach ($steuerbar as $eintrag) { if ($eintrag['Festwert']) { continue; } $instanzID = $eintrag['InstanzID']; $ausgangswert = $sollwerte[$instanzID]; if ($rest >= 0) { $neuerSollwert = self::groessterLeistungswertBis( $eintrag['Leistungswerte_W'], $ausgangswert + $rest ); } else { $neuerSollwert = self::kleinsterLeistungswertAb( $eintrag['Leistungswerte_W'], $ausgangswert + $rest ); } $rest -= $neuerSollwert - $ausgangswert; $sollwerte[$instanzID] = $neuerSollwert; } $zugeteilt = $nichtSteuerbar; foreach ($steuerbar as $eintrag) { if (!$eintrag['Festwert']) { $zugeteilt += $sollwerte[$eintrag['InstanzID']]; } } 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.'); } } /** * Ein einzelner Leistungswert bleibt als Befehl erhalten. Fuer die * Leistungsbilanz zaehlt jedoch die gemessene Istleistung. * * @param list $leistungswerte */ private static function istFestwert(array $leistungswerte): bool { return count($leistungswerte) === 1 && is_int($leistungswerte[0]); } /** * @param list $leistungswerte */ private static function neutralerLeistungswert(array $leistungswerte): int { $negativ = null; $positiv = null; foreach ($leistungswerte as $eintrag) { if (is_int($eintrag)) { if ($eintrag === 0) { return 0; } $negativ = $eintrag < 0 ? max($negativ ?? $eintrag, $eintrag) : $negativ; $positiv = $eintrag > 0 ? min($positiv ?? $eintrag, $eintrag) : $positiv; continue; } if ($eintrag['Von_W'] <= 0 && $eintrag['Bis_W'] >= 0) { return 0; } $negativ = $eintrag['Bis_W'] < 0 ? max($negativ ?? $eintrag['Bis_W'], $eintrag['Bis_W']) : $negativ; $positiv = $eintrag['Von_W'] > 0 ? min($positiv ?? $eintrag['Von_W'], $eintrag['Von_W']) : $positiv; } if ($negativ === null) { return $positiv; } if ($positiv === null) { return $negativ; } return abs($negativ) <= abs($positiv) ? $negativ : $positiv; } /** * @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; } /** * @param list $leistungswerte */ private static function kleinsterLeistungswertAb(array $leistungswerte, int $untergrenze): int { foreach ($leistungswerte as $eintrag) { if (is_int($eintrag)) { if ($eintrag >= $untergrenze) { return $eintrag; } continue; } if ($eintrag['Bis_W'] < $untergrenze) { continue; } return max($eintrag['Von_W'], $untergrenze); } return self::neutralerLeistungswert($leistungswerte); } }