fix(manager): feste Leistungsangebote immer zuteilen

This commit is contained in:
dh
2026-09-29 11:40:24 +00:00
parent ccecc437e9
commit 5ff0d405d4
4 changed files with 34 additions and 16 deletions
+6
View File
@@ -90,6 +90,12 @@ melden, ohne dass der Manager seine interne Geraetelogik kennen muss.
## Feste Regeln ## Feste Regeln
- Prioritaeten beginnen bei 0; eine kleinere Zahl bedeutet hoehere Prioritaet. - Prioritaeten beginnen bei 0; eine kleinere Zahl bedeutet hoehere Prioritaet.
- Jeder verfuegbare, synchronisierte Verbraucher mit einem nicht leeren
`Leistungswerte_W`-Angebot erhaelt einen Sollwert aus genau diesem Angebot.
- `AenderungMoeglich=false` kennzeichnet ein fixes Angebot und ist kein
Ausschlussgrund. Ein Angebot `[11000]` fuehrt deshalb zwingend zu `11000 W`.
- Prioritaeten verteilen nur die ueber den jeweiligen Mindestwert hinaus
verfuegbare Leistung; sie duerfen keinen angebotenen Mindestwert verdrängen.
- `Leistungsquelle`: 0 nicht vorhanden, 1 berechnet, 2 gemessen. - `Leistungsquelle`: 0 nicht vorhanden, 1 berechnet, 2 gemessen.
- Bei Leistungsquelle 0 ist `Istleistung_W` zwingend `null`. - Bei Leistungsquelle 0 ist `Istleistung_W` zwingend `null`.
- Leistungsbereiche enthalten jeden ganzen Wattwert von `Von_W` bis `Bis_W`. - Leistungsbereiche enthalten jeden ganzen Wattwert von `Von_W` bis `Bis_W`.
+13 -8
View File
@@ -302,14 +302,19 @@ der aktiven Betriebsart und der aktuellen Leistung aller frisch gemeldeten
Verbraucher. Eine gültige Istleistung wird bevorzugt; fehlt sie, bleibt der im Verbraucher. Eine gültige Istleistung wird bevorzugt; fehlt sie, bleibt der im
Zustand bestätigte Sollwert konservativ reserviert. Zustand bestätigte Sollwert konservativ reserviert.
Nicht verfügbare, nicht änderbare oder aktuell angebotlose Verbraucher behalten Nicht verfügbare oder aktuell angebotlose Verbraucher behalten ihre Leistung
ihre Leistung und erhalten keine neue Vorgabe. Die übrigen Verbraucher werden und erhalten keine neue Vorgabe. Jeder verfügbare, synchronisierte Verbraucher
nach der gemeldeten PV- beziehungsweise Peak-Priorität und danach stabil nach mit einem nicht leeren Angebot erhält dagegen einen Sollwert aus diesem Angebot.
Instanz-ID sortiert. Von ihrem jeweils kleinsten erlaubten Leistungswert aus Das gilt auch bei `AenderungMoeglich=false`: Ein fixes Angebot wie `[11000]`
wird das Budget in dieser Reihenfolge aufgefüllt. Einzelwerte und ganzzahlige wird mit genau `11000 W` zugeteilt.
Bereiche werden direkt verarbeitet; Lücken werden nie durch unzulässige Werte
geschlossen. Eine verbleibende Abweichung wird im `Verbraucherstatus` Die Verbraucher werden nach der gemeldeten PV- beziehungsweise Peak-Priorität
dokumentiert. und danach stabil nach Instanz-ID sortiert. Zuerst reserviert der Manager für
jeden Verbraucher den kleinsten erlaubten Leistungswert. Nur das danach
verbleibende Budget wird in Prioritätsreihenfolge aufgefüllt. Einzelwerte und
ganzzahlige Bereiche werden direkt verarbeitet; Lücken werden nie durch
unzulässige Werte geschlossen. Eine verbleibende Abweichung wird im
`Verbraucherstatus` dokumentiert.
Die Betriebsart wechselt unterhalb des Solar-Sollwerts zu `PV` und oberhalb Die Betriebsart wechselt unterhalb des Solar-Sollwerts zu `PV` und oberhalb
der wirksamen Lastspitzengrenze zu `Peak`. Zwischen den beiden Zielwerten der wirksamen Lastspitzengrenze zu `Peak`. Zwischen den beiden Zielwerten
+1 -1
View File
@@ -120,7 +120,7 @@ final class ManagerRegler
$aktuelleLeistung += $leistung; $aktuelleLeistung += $leistung;
$daten = $eintrag['Daten']; $daten = $eintrag['Daten'];
if (!$daten['Verfuegbar'] || !$daten['AenderungMoeglich'] || $daten['Leistungswerte_W'] === []) { if (!$daten['Verfuegbar'] || $daten['Leistungswerte_W'] === []) {
$nichtSteuerbar += $leistung; $nichtSteuerbar += $leistung;
continue; continue;
} }
+14 -7
View File
@@ -113,19 +113,19 @@ final class ManagerReglerTest extends TestCase
self::assertSame(500, $ergebnis['Restabweichung_W']); self::assertSame(500, $ergebnis['Restabweichung_W']);
} }
public function testNichtAenderbarerVerbraucherReserviertSeineIstleistung(): void public function testFestwertWirdUnabhaengigVonAenderungMoeglichZugewiesen(): void
{ {
$fest = $this->verbraucher(10, 0, [0, 3000], 3000, false); $fest = $this->verbraucher(10, 0, [3000], 3000, false);
$steuerbar = $this->verbraucher(20, 1, [0, ['Von_W' => 1, 'Bis_W' => 4000]], 0); $steuerbar = $this->verbraucher(20, 1, [0, ['Von_W' => 1, 'Bis_W' => 4000]], 0);
$ergebnis = ManagerRegler::verteile(3000.0, 5000.0, [$fest, $steuerbar], 'PrioritaetPV'); $ergebnis = ManagerRegler::verteile(3000.0, 5000.0, [$fest, $steuerbar], 'PrioritaetPV');
self::assertSame(3000, $ergebnis['NichtSteuerbar_W']); self::assertSame(0, $ergebnis['NichtSteuerbar_W']);
self::assertSame([20 => 2000], $ergebnis['Sollwerte']); self::assertSame([10 => 3000, 20 => 2000], $ergebnis['Sollwerte']);
self::assertSame(0, $ergebnis['Restabweichung_W']); self::assertSame(0, $ergebnis['Restabweichung_W']);
} }
public function testVeralteterVerbraucherBlockiertUeberschussverteilungNicht(): void public function testFestwerteWerdenMitHoeherPriorisierterBatterieZugewiesen(): void
{ {
$ergebnis = ManagerRegler::verteile( $ergebnis = ManagerRegler::verteile(
-2411.0, -2411.0,
@@ -147,7 +147,13 @@ final class ManagerReglerTest extends TestCase
self::assertSame(12562, $ergebnis['Verteilbudget_W']); self::assertSame(12562, $ergebnis['Verteilbudget_W']);
self::assertSame( self::assertSame(
[44234 => 2411, 29743 => 0, 38676 => 0], [
44234 => -12288,
29743 => 0,
38676 => 0,
43878 => 13850,
46052 => 11000,
],
$ergebnis['Sollwerte'] $ergebnis['Sollwerte']
); );
self::assertSame(0, $ergebnis['Restabweichung_W']); self::assertSame(0, $ergebnis['Restabweichung_W']);
@@ -155,7 +161,7 @@ final class ManagerReglerTest extends TestCase
public function testFehlendeIstleistungFaelltAufAngenommenenSollwertZurueck(): void public function testFehlendeIstleistungFaelltAufAngenommenenSollwertZurueck(): void
{ {
$verbraucher = $this->verbraucher(10, 0, [0, 3000], null, false); $verbraucher = $this->verbraucher(10, 0, [3000], null, false);
$verbraucher['Daten']['Leistungsquelle'] = Nachrichtenvertrag::LEISTUNGSQUELLE_NICHT_VORHANDEN; $verbraucher['Daten']['Leistungsquelle'] = Nachrichtenvertrag::LEISTUNGSQUELLE_NICHT_VORHANDEN;
$verbraucher['Daten']['Istleistung_W'] = null; $verbraucher['Daten']['Istleistung_W'] = null;
$verbraucher['Daten']['Zustand'][0]['Wert'] = 3000; $verbraucher['Daten']['Zustand'][0]['Wert'] = 3000;
@@ -163,6 +169,7 @@ final class ManagerReglerTest extends TestCase
$ergebnis = ManagerRegler::verteile(3000.0, 0.0, [$verbraucher], 'PrioritaetPV'); $ergebnis = ManagerRegler::verteile(3000.0, 0.0, [$verbraucher], 'PrioritaetPV');
self::assertSame(3000, $ergebnis['AktuelleVerbraucherleistung_W']); self::assertSame(3000, $ergebnis['AktuelleVerbraucherleistung_W']);
self::assertSame([10 => 3000], $ergebnis['Sollwerte']);
self::assertSame(-3000, $ergebnis['Restabweichung_W']); self::assertSame(-3000, $ergebnis['Restabweichung_W']);
} }