Polish curses dashboard warnings and turbine/generator layout

This commit is contained in:
Codex Agent
2025-11-28 18:50:38 +01:00
parent d6c27a6373
commit c34f49319f

View File

@@ -486,33 +486,29 @@ class ReactorDashboard:
[ [
("Turbines", " ".join(self._turbine_status_lines())), ("Turbines", " ".join(self._turbine_status_lines())),
("Rated Elec", f"{len(self.reactor.turbines)*self.reactor.turbines[0].rated_output_mw:7.1f} MW"), ("Rated Elec", f"{len(self.reactor.turbines)*self.reactor.turbines[0].rated_output_mw:7.1f} MW"),
("Steam Avail", f"{self._steam_available_power(state):7.1f} MW"),
("Unit1 Elec", f"{state.turbines[0].electrical_output_mw:7.1f} MW" if state.turbines else "n/a"),
( (
"Unit2 Elec", "Steam",
f"{state.turbines[1].electrical_output_mw:7.1f} MW" if len(state.turbines) > 1 else "n/a", f"h={state.turbines[0].steam_enthalpy:5.0f} kJ/kg avail {self._steam_available_power(state):6.1f} MW "
f"flow {state.secondary_loop.mass_flow_rate * max(0.0, state.secondary_loop.steam_quality):6.0f} kg/s"
if state.turbines
else "n/a",
), ),
( (
"Unit3 Elec", "Units Elec",
f"{state.turbines[2].electrical_output_mw:7.1f} MW" if len(state.turbines) > 2 else "n/a", " ".join([f"{t.electrical_output_mw:6.1f}MW" for t in state.turbines]) if state.turbines else "n/a",
), ),
("Throttle", f"{self.reactor.turbines[0].throttle:5.2f}" if self.reactor.turbines else "n/a"), ("Throttle", f"{self.reactor.turbines[0].throttle:5.2f}" if self.reactor.turbines else "n/a"),
( (
"Condenser", "Condenser",
( (
f"P={state.turbines[0].condenser_pressure:4.2f}/{constants.CONDENSER_MAX_PRESSURE_MPA:4.2f} MPa " f"P={state.turbines[0].condenser_pressure:4.2f}/{constants.CONDENSER_MAX_PRESSURE_MPA:4.2f} MPa "
f"T={state.turbines[0].condenser_temperature:6.1f}/{constants.CONDENSER_COOLING_WATER_TEMP_K:6.1f}K " f"T={state.turbines[0].condenser_temperature:6.1f}K Foul={state.turbines[0].fouling_penalty*100:4.1f}%"
f"Foul={state.turbines[0].fouling_penalty*100:4.1f}%"
) )
if state.turbines if state.turbines
else "n/a", else "n/a",
), ),
("Electrical", f"{state.total_electrical_output():7.1f} MW"), ("Electrical", f"{state.total_electrical_output():7.1f} MW | Load {self._total_load_supplied(state):6.1f}/{self._total_load_demand(state):6.1f} MW"),
("Load", f"{self._total_load_supplied(state):7.1f}/{self._total_load_demand(state):7.1f} MW"), ("Consumer", f"{consumer_status} demand {consumer_demand:6.1f} MW"),
("Consumer", f"{consumer_status}"),
("Demand", f"{consumer_demand:7.1f} MW"),
("Steam Enthalpy", f"{state.turbines[0].steam_enthalpy:7.0f} kJ/kg" if state.turbines else "n/a"),
("Steam Flow", f"{state.secondary_loop.mass_flow_rate * max(0.0, state.secondary_loop.steam_quality):7.0f} kg/s"),
], ],
) )
right_y = self._draw_section(right_win, right_y, "Generators", self._generator_lines(state)) right_y = self._draw_section(right_win, right_y, "Generators", self._generator_lines(state))
@@ -594,7 +590,7 @@ class ReactorDashboard:
win: "curses._CursesWindow", win: "curses._CursesWindow",
start_y: int, start_y: int,
title: str, title: str,
lines: list[tuple[str, str] | str], lines: list[tuple[str, str] | tuple[str, str, int] | str],
) -> int: ) -> int:
height, width = win.getmaxyx() height, width = win.getmaxyx()
inner_width = width - 4 inner_width = width - 4
@@ -605,12 +601,16 @@ class ReactorDashboard:
for line in lines: for line in lines:
if row >= height - 1: if row >= height - 1:
break break
attr = 0
if isinstance(line, tuple): if isinstance(line, tuple):
label, value = line if len(line) == 3:
label, value, attr = line
else:
label, value = line
text = f"{label:<18}: {value}" text = f"{label:<18}: {value}"
else: else:
text = line text = line
win.addstr(row, 4, text[:inner_width]) win.addstr(row, 4, text[:inner_width], attr)
row += 1 row += 1
return row + 1 return row + 1
@@ -730,37 +730,44 @@ class ReactorDashboard:
] ]
def _protection_lines(self, state: PlantState) -> list[tuple[str, str]]: def _protection_lines(self, state: PlantState) -> list[tuple[str, str]]:
lines: list[tuple[str, str]] = [] lines: list[tuple[str, str] | tuple[str, str, int]] = []
lines.append(("SCRAM", "ACTIVE" if self.reactor.shutdown else "CLEAR")) lines.append(("SCRAM", "ACTIVE" if self.reactor.shutdown else "CLEAR", curses.color_pair(4) if self.reactor.shutdown else 0))
if self.reactor.meltdown: if self.reactor.meltdown:
lines.append(("Meltdown", "IN PROGRESS")) lines.append(("Meltdown", "IN PROGRESS", curses.color_pair(4) | curses.A_BOLD))
sec_flow_low = state.secondary_loop.mass_flow_rate <= 1.0 or not self.reactor.secondary_pump_active sec_flow_low = state.secondary_loop.mass_flow_rate <= 1.0 or not self.reactor.secondary_pump_active
heat_sink_risk = sec_flow_low and state.core.power_output_mw > 50.0 heat_sink_risk = sec_flow_low and state.core.power_output_mw > 50.0
if heat_sink_risk: if heat_sink_risk:
heat_text = "TRIPPED low secondary flow >50 MW" heat_text = "TRIPPED low secondary flow >50 MW"
heat_attr = curses.color_pair(4) | curses.A_BOLD
elif sec_flow_low: elif sec_flow_low:
heat_text = "ARMED (secondary off/low flow)" heat_text = "ARMED (secondary off/low flow)"
heat_attr = curses.color_pair(2) | curses.A_BOLD
else: else:
heat_text = "OK" heat_text = "OK"
lines.append(("Heat sink", heat_text)) heat_attr = curses.color_pair(3)
lines.append(("Heat sink", heat_text, heat_attr))
draws = getattr(state, "aux_draws", {}) or {} draws = getattr(state, "aux_draws", {}) or {}
demand = draws.get("total_demand", 0.0) demand = draws.get("total_demand", 0.0)
supplied = draws.get("supplied", 0.0) supplied = draws.get("supplied", 0.0)
if demand > 0.1 and supplied + 1e-6 < demand: if demand > 0.1 and supplied + 1e-6 < demand:
aux_text = f"DEFICIT {supplied:5.1f}/{demand:5.1f} MW" aux_text = f"DEFICIT {supplied:5.1f}/{demand:5.1f} MW"
aux_attr = curses.color_pair(2) | curses.A_BOLD
elif demand > 0.1: elif demand > 0.1:
aux_text = f"OK {supplied:5.1f}/{demand:5.1f} MW" aux_text = f"OK {supplied:5.1f}/{demand:5.1f} MW"
aux_attr = curses.color_pair(3)
else: else:
aux_text = "Idle" aux_text = "Idle"
lines.append(("Aux power", aux_text)) aux_attr = 0
lines.append(("Aux power", aux_text, aux_attr))
reliefs = [] reliefs = []
if self.reactor.primary_relief_open: if self.reactor.primary_relief_open:
reliefs.append("Primary") reliefs.append("Primary")
if self.reactor.secondary_relief_open: if self.reactor.secondary_relief_open:
reliefs.append("Secondary") reliefs.append("Secondary")
lines.append(("Relief valves", ", ".join(reliefs) if reliefs else "Closed")) relief_attr = curses.color_pair(2) | curses.A_BOLD if reliefs else 0
lines.append(("Relief valves", ", ".join(reliefs) if reliefs else "Closed", relief_attr))
lines.append( lines.append(
( (
"SCRAM trips", "SCRAM trips",
@@ -874,8 +881,14 @@ class _DashboardLogHandler(logging.Handler):
msg = self.format(record) msg = self.format(record)
if msg == self._last_msg: if msg == self._last_msg:
self._repeat_count += 1 self._repeat_count += 1
if self._repeat_count > 3: if self.buffer and self.buffer[-1].startswith(self._last_msg):
return try:
self.buffer[-1] = f"{self._last_msg} (x{self._repeat_count + 1})"
except Exception:
self.buffer.append(f"{msg} (x{self._repeat_count + 1})")
else:
self.buffer.append(f"{msg} (x{self._repeat_count + 1})")
return
else: else:
self._last_msg = msg self._last_msg = msg
self._repeat_count = 0 self._repeat_count = 0