MAINT-20 part 2 r2: app/main.py comments, lines 2001-2500 (_reseed_drain_end .. _fill_peak_gaps)
Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude
parent
9ec0a84129
commit
56d5532b83
@@ -2013,9 +2013,10 @@ class Controller:
|
||||
|
||||
def _reseed_drain_end(self, maint_results, live) -> None:
|
||||
"""SAFETY-95, N>=2: when a battery's maintenance drain ends, its drain
|
||||
leaves the loop's start point, as a charge does (SAFETY-93): the
|
||||
start is what the others stood at plus its new command (its pin,
|
||||
else 0 W). (DECISIONS/MAIN-orchestration-archive.md)"""
|
||||
leaves the loop's start point, as a charge does (SAFETY-93): the start
|
||||
is what the others stood at plus its new command (its pin, else 0 W),
|
||||
never above last cycle's start; else the drain's floor lands on the
|
||||
others (B 0 -> +1600 W, exporting). DECISIONS/SAFETY-95-drain-end.md"""
|
||||
ended, self._drain_ended = self._drain_ended, frozenset()
|
||||
if len(self.inverters) < 2 or not ended:
|
||||
return
|
||||
@@ -2040,8 +2041,13 @@ class Controller:
|
||||
self.target = self.i_w = min(hi, max(lo, here))
|
||||
|
||||
def _drain_grid_w(self, live) -> float:
|
||||
"""SAFETY-95: the grid the loop regulates.
|
||||
(DECISIONS/MAIN-orchestration-archive.md)"""
|
||||
"""SAFETY-95: the grid the loop regulates. While a battery settles from
|
||||
its ended drain (`_held_out_w`, at most PLANT_SETTLE_DOWN_S) the
|
||||
batteries count at the site command, so the loop does not chase the
|
||||
drain's tail. SAFETY-103: a reading can be up to a register period (10
|
||||
s) older than the meter, so the house point is held and moved only as
|
||||
far as the meter proves.
|
||||
DECISIONS/SAFETY-103-old-readings-after-a-drain.md"""
|
||||
grid = float(self.grid)
|
||||
self._drain_tail &= set(self._settle) & set(live)
|
||||
if not self._drain_tail:
|
||||
@@ -2086,9 +2092,9 @@ class Controller:
|
||||
|
||||
def _reseed_drop(self, live) -> None:
|
||||
"""SAFETY-96, N>=2: a battery that drops out of the split (stale link)
|
||||
leaves the loop's start point, so the others take over at one slew
|
||||
step a cycle instead of taking its share at once.
|
||||
(DECISIONS/MAIN-orchestration-archive.md)"""
|
||||
leaves the loop's start point, so the others take over at one slew step
|
||||
a cycle instead of taking its share at once.
|
||||
DECISIONS/SAFETY-96-dropped-battery.md"""
|
||||
was, self._live_was = self._live_was, frozenset(live)
|
||||
if len(self.inverters) < 2 or was is None or was == live:
|
||||
return
|
||||
@@ -2110,8 +2116,9 @@ class Controller:
|
||||
|
||||
def _held_out_w(self, live) -> float:
|
||||
"""SAFETY-96, N>=2: what to add to the site sum so that a battery the
|
||||
loop cannot move right now counts at its command, not at its
|
||||
reading. (DECISIONS/MAIN-orchestration-archive.md)"""
|
||||
loop cannot move right now counts at its command, not at its reading.
|
||||
Its real power still reaches the loop through the meter: neither lost
|
||||
nor counted twice. DECISIONS/SAFETY-96-dropped-battery.md"""
|
||||
if len(self.inverters) < 2:
|
||||
return 0.0
|
||||
out = 0.0
|
||||
@@ -2132,7 +2139,9 @@ class Controller:
|
||||
|
||||
def _mode_fault_log(self, resolution) -> None:
|
||||
"""OPT-11: say once, per battery, that its mode was dropped as a fault.
|
||||
(DECISIONS/MAIN-orchestration-archive.md)"""
|
||||
A mode range without 0 W would force a direction, which no mode may do,
|
||||
so it is never applied (allocator.unit_interval()); main.py never
|
||||
builds one. (DECISIONS/MAIN-orchestration-archive.md)"""
|
||||
alloc = resolution.allocation
|
||||
now = {u for u, _ in getattr(alloc, "mode_faults", ())}
|
||||
if resolution.mode_fault is not None and self.inverters:
|
||||
@@ -2148,7 +2157,9 @@ class Controller:
|
||||
|
||||
def _pack_action(self, what, unit, value, now) -> dict:
|
||||
"""OPT-11: switch one battery on or off, set its mode, or (P3) turn its
|
||||
selling on or off. (DECISIONS/MAIN-orchestration-archive.md)"""
|
||||
selling on or off. Store state, not an option: it applies next cycle
|
||||
with no restart. Switching a battery off also stops its maintenance
|
||||
cycle. (DECISIONS/MAIN-orchestration-archive.md)"""
|
||||
if not self.modes_on:
|
||||
return {"error": messages.pack_refused("off")}
|
||||
inv = next((i for i in self.inverters if i.name == unit), None)
|
||||
@@ -2189,7 +2200,8 @@ class Controller:
|
||||
|
||||
async def _maint_unit_action(self, what, unit, now) -> dict:
|
||||
"""MAINT-11: start or abort the maintenance cycle of ONE named battery.
|
||||
(DECISIONS/MAIN-orchestration-archive.md)"""
|
||||
The site keeps ONE maintenance slot: a start while any battery holds it
|
||||
is refused (no queue). (DECISIONS/MAIN-orchestration-archive.md)"""
|
||||
inv = next((i for i in self.inverters if i.name == unit), None)
|
||||
if inv is None:
|
||||
return self._maint_refusal("unit")
|
||||
@@ -2234,8 +2246,10 @@ class Controller:
|
||||
return out
|
||||
|
||||
async def mqtt_command(self, topic, payload, retained) -> None:
|
||||
"""OPT-11 P2: one command from a battery's HA switch or mode list.
|
||||
(DECISIONS/MAIN-orchestration-archive.md)"""
|
||||
"""OPT-11 P2: one command from a battery's HA switch or mode list,
|
||||
through handle_action(). A retained command is never applied (the store
|
||||
already holds the last choice). Never raises: MQTT must not reach
|
||||
control. (DECISIONS/MAIN-orchestration-archive.md)"""
|
||||
try:
|
||||
slug, action, value, why = parse_command(
|
||||
topic, payload, retained, packmode.MODES)
|
||||
@@ -2303,8 +2317,9 @@ class Controller:
|
||||
|
||||
def _peak_on_able(self, decision, maint_results):
|
||||
"""SAFETY-87: at N>=2, `decision` placed on the batteries that can
|
||||
still give: live, not refusing, above their own reserve.
|
||||
(DECISIONS/MAIN-orchestration-archive.md)"""
|
||||
still give: live, not refusing, above their own reserve. `able_w` is
|
||||
their rating plus the drain a held battery gives anyway.
|
||||
DECISIONS/SAFETY-87-guard-uses-able-batteries.md"""
|
||||
if len(self.inverters) < 2:
|
||||
return decision
|
||||
floor = float(self.o.get(
|
||||
@@ -2332,8 +2347,9 @@ class Controller:
|
||||
def _peak_landing(self, decision):
|
||||
"""SAFETY-126: `decision` marked `within_aim` while the guard wants
|
||||
power but the batteries at 0 W would end the quarter within
|
||||
`ALARM_MISS_W` of the aim (`peakguard.misses_aim()`).
|
||||
(DECISIONS/MAIN-orchestration-archive.md)"""
|
||||
`ALARM_MISS_W` of the aim (`peakguard.misses_aim()`). The claim and the
|
||||
commands are unchanged; only the alarms and the "Why" read it.
|
||||
DECISIONS/SAFETY-126-landing-error-is-not-an-overrun.md"""
|
||||
batt, prev = self.site_batt_w, self._land_batt_prev
|
||||
self._land_batt_prev = batt
|
||||
if not (decision.engaged and decision.blind is None
|
||||
@@ -2387,8 +2403,9 @@ class Controller:
|
||||
|
||||
def _held_back(self) -> frozenset:
|
||||
"""SAFETY-91, N>=2: the batteries at or under their own peak reserve
|
||||
while another can still give (live, not refusing).
|
||||
(DECISIONS/MAIN-orchestration-archive.md)"""
|
||||
while another can still give (live, not refusing). One held back stays
|
||||
so until its SoC reads more than 1 point above its reserve.
|
||||
DECISIONS/SAFETY-91-held-battery-last.md"""
|
||||
if len(self.inverters) < 2:
|
||||
return frozenset()
|
||||
floor = float(self.o.get(
|
||||
@@ -2409,9 +2426,9 @@ class Controller:
|
||||
|
||||
def _drain_rise(self, claims, maint_results) -> None:
|
||||
"""SAFETY-91, N>=2: while a battery is held back (`_held_back()`), a
|
||||
draining battery's pin becomes a floor at its drain rate, so it
|
||||
rises towards its rating before the held one gives.
|
||||
(DECISIONS/MAIN-orchestration-archive.md)"""
|
||||
draining battery's pin becomes a floor at its drain rate, so it rises
|
||||
towards its rating before the held one gives. A site stop still decides
|
||||
alone. DECISIONS/SAFETY-91-held-battery-last.md"""
|
||||
held = self._held_back()
|
||||
if not held or any(c.kind == SET and c.actuator == ACTUATOR_SITE
|
||||
and c.priority > P_PEAK for c in claims):
|
||||
|
||||
Reference in New Issue
Block a user