From 710459b5213df757b05cfa11e81648c920c165de Mon Sep 17 00:00:00 2001 From: Codex Agent Date: Sun, 23 Nov 2025 10:45:10 +0100 Subject: [PATCH] Surface protection warnings in dashboard --- src/reactor_sim/dashboard.py | 48 ++++++++++++++++++++++++++++++++---- 1 file changed, 43 insertions(+), 5 deletions(-) diff --git a/src/reactor_sim/dashboard.py b/src/reactor_sim/dashboard.py index 0df2a1f..ac3b20c 100644 --- a/src/reactor_sim/dashboard.py +++ b/src/reactor_sim/dashboard.py @@ -44,7 +44,7 @@ class ReactorDashboard: self.reset_requested = False self._last_state: Optional[PlantState] = None self._trend_history: deque[tuple[float, float, float]] = deque(maxlen=120) - self.log_buffer: deque[str] = deque(maxlen=4) + self.log_buffer: deque[str] = deque(maxlen=8) self._log_handler: Optional[logging.Handler] = None self._previous_handlers: list[logging.Handler] = [] self._logger = logging.getLogger("reactor_sim") @@ -297,7 +297,7 @@ class ReactorDashboard: def _draw(self, stdscr: "curses._CursesWindow", state: PlantState) -> None: stdscr.erase() height, width = stdscr.getmaxyx() - min_status = 4 + min_status = 6 if height < min_status + 12 or width < 70: stdscr.addstr( 0, @@ -308,8 +308,9 @@ class ReactorDashboard: stdscr.refresh() return - status_height = min_status - data_height = height - status_height + log_rows = max(2, min(len(self.log_buffer) + 2, 8)) + status_height = min(height - 1, max(min_status, min_status + log_rows)) + data_height = max(1, height - status_height) right_width = max(28, width // 3) left_width = width - right_width if left_width < 50: @@ -442,6 +443,7 @@ class ReactorDashboard: ) right_y = self._draw_section(right_win, right_y, "Generators", self._generator_lines(state)) right_y = self._draw_section(right_win, right_y, "Power Stats", self._power_lines(state)) + right_y = self._draw_section(right_win, right_y, "Protections / Warnings", self._protection_lines(state)) right_y = self._draw_section(right_win, right_y, "Maintenance", self._maintenance_lines()) self._draw_health_bars(right_win, right_y) @@ -487,7 +489,9 @@ class ReactorDashboard: f"Failures: {', '.join(self.reactor.health_monitor.failure_log)}", curses.color_pair(4) | curses.A_BOLD, ) - log_y = 3 + log_y = 4 + else: + log_y = 3 for record in list(self.log_buffer): if log_y >= win.getmaxyx()[0] - 1: break @@ -599,6 +603,40 @@ class ReactorDashboard: ] return lines + def _protection_lines(self, state: PlantState) -> list[tuple[str, str]]: + lines: list[tuple[str, str]] = [] + lines.append(("SCRAM", "ACTIVE" if self.reactor.shutdown else "CLEAR")) + if self.reactor.meltdown: + lines.append(("Meltdown", "IN PROGRESS")) + 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 + if heat_sink_risk: + heat_text = "TRIPPED low secondary flow >50 MW" + elif sec_flow_low: + heat_text = "ARMED (secondary off/low flow)" + else: + heat_text = "OK" + lines.append(("Heat sink", heat_text)) + + draws = getattr(state, "aux_draws", {}) or {} + demand = draws.get("total_demand", 0.0) + supplied = draws.get("supplied", 0.0) + if demand > 0.1 and supplied + 1e-6 < demand: + aux_text = f"DEFICIT {supplied:5.1f}/{demand:5.1f} MW" + elif demand > 0.1: + aux_text = f"OK {supplied:5.1f}/{demand:5.1f} MW" + else: + aux_text = "Idle" + lines.append(("Aux power", aux_text)) + + reliefs = [] + if self.reactor.primary_relief_open: + reliefs.append("Primary") + if self.reactor.secondary_relief_open: + reliefs.append("Secondary") + lines.append(("Relief valves", ", ".join(reliefs) if reliefs else "Closed")) + return lines + def _steam_pressure(self, state: PlantState) -> float: # Only report steam pressure if quality/flow indicate steam is present. if state.secondary_loop.steam_quality < 0.05 or state.secondary_loop.mass_flow_rate < 100.0: