Money is configured in three places:
diff --git a/new_docs/chapters/ch11-capacity.adoc b/new_docs/chapters/ch11-capacity.adoc
index b904b7fe..a0483eaf 100644
--- a/new_docs/chapters/ch11-capacity.adoc
+++ b/new_docs/chapters/ch11-capacity.adoc
@@ -65,10 +65,11 @@ the GUI starts as "share everything evenly".
=== Open the model
-Load `models/Water1.rbds`. In **Model → Project Settings** notice
-two settings that were never there before:
+Load `models/Water1.rbds`. In the **Run** panel, tick **Capacity** under
+Analysis and look at the two settings that make it work:
-* **Flow generated: 180** — the demand, offered at the start every hour.
+* **Flow generated: 180** — the demand, offered at the start every hour
+ (in the same Analysis section).
* **Capacity analysis: checked** — tells the engine to track flow,
not just up/down.
diff --git a/new_docs/chapters/ch12-cost.adoc b/new_docs/chapters/ch12-cost.adoc
index f855f458..19056307 100644
--- a/new_docs/chapters/ch12-cost.adoc
+++ b/new_docs/chapters/ch12-cost.adoc
@@ -34,9 +34,9 @@ the arithmetic.
=== Three places money lives
-Open `models/Water2.rbds` and choose **Model → Project Settings**:
-
-image::water2-sysconfig.png[Project Settings: cost fields for the water utility]
+Open `models/Water2.rbds` and open the **Run** panel. With **Cost** on
+(and **Capacity** on as well, for the lost-flow price), the Analysis
+section carries the two system costs:
Money is configured in three places:
diff --git a/new_docs/images/water2-sysconfig.png b/new_docs/images/water2-sysconfig.png
deleted file mode 100644
index 7cd9307c..00000000
Binary files a/new_docs/images/water2-sysconfig.png and /dev/null differ
diff --git a/new_docs/tools/capture_screens.py b/new_docs/tools/capture_screens.py
index e0e304ec..e6dca37a 100644
--- a/new_docs/tools/capture_screens.py
+++ b/new_docs/tools/capture_screens.py
@@ -274,20 +274,6 @@ def descend(name):
load("Water1")
shot("water-loaded")
-# 22b. ch-cost: Project Settings is the Inspector page, not a dialog.
-load("Water2")
-w._open_project_settings()
-# A tall window so the whole page fits without scrolling; let the layout
-# settle before the grab (one event pass leaves the rows half laid out).
-w.resize(1600, 1500)
-for _ in range(3):
- app.processEvents()
-w._inspector_dock.widget().grab().copy(0, 0, 400, 830).save(
- os.path.join(IMG, "water2-sysconfig.png"), "PNG")
-w.resize(1600, 1000)
-app.processEvents()
-print("shot water2-sysconfig")
-
# 23-24. ch-capstone (data centre): home level, then inside Power_sub.
load("DC1")
shot("dc-loaded")
diff --git a/raptor/gui/ui/dialogs.py b/raptor/gui/ui/dialogs.py
index 53034126..838c4790 100644
--- a/raptor/gui/ui/dialogs.py
+++ b/raptor/gui/ui/dialogs.py
@@ -934,11 +934,6 @@ def fspin(value):
num_runs = QSpinBox()
num_runs.setRange(1, 1_000_000)
num_runs.setValue(cfg.num_runs)
- flow_gen = QSpinBox()
- flow_gen.setRange(0, 1_000_000_000)
- flow_gen.setValue(int(cfg.flow_generated))
- red_cost = fspin(cfg.system_red_cost)
- lost_cost = fspin(cfg.lost_flow_unit_cost)
gy = fspin(cfg.gy_threshold)
yr = fspin(cfg.yr_threshold)
# Original ranges (Raptor 7's own validation): GY in 0.01..1.0,
@@ -952,9 +947,6 @@ def fspin(value):
form.addRow(f"Statistics start time ({shown_units}):" if shown_units
else "Statistics start time:", start_stats)
form.addRow("Number of runs:", num_runs)
- form.addRow("Flow generated:", flow_gen)
- form.addRow("System red cost:", red_cost)
- form.addRow("Cost of lost flow:", lost_cost)
form.addRow("Green/Yellow threshold:", gy)
form.addRow("Yellow/Red threshold:", yr)
@@ -1057,9 +1049,6 @@ def fspin(value):
("TermCondition", f"{term_cond.value():.6f}"),
("StartStats", f"{display_to_native(start_stats.value(), native_units, display_unit):.6f}"),
("NumRuns", str(num_runs.value())),
- ("flowGenerated", str(flow_gen.value())),
- ("systemRedCost", f"{red_cost.value():.6f}"),
- ("sysLostCost", f"{lost_cost.value():.6f}"),
("GYthreshold", f"{gy.value():.6f}"),
("YRthreshold", f"{yr.value():.6f}"),
("weakAnalysis", "1" if checks["weak_analysis"][0].isChecked()
diff --git a/raptor/gui/ui/inspector.py b/raptor/gui/ui/inspector.py
index 860ca84f..ccd5528c 100644
--- a/raptor/gui/ui/inspector.py
+++ b/raptor/gui/ui/inspector.py
@@ -4078,10 +4078,6 @@ def commit_streams(_=None, spins=stream_spins,
lambda on: self.system_config_changed.emit(
{"IgnoreStartFail": "1" if on else "0"}))
fl.addRow(startfail_cb)
- self._int_row(
- fl, "Flow generated", int(cfg.flow_generated),
- lambda v: self.system_config_changed.emit(
- {"flowGenerated": str(v)}), minimum=0)
neg_cb = QCheckBox("Ignore negative-value warning")
neg_cb.setAccessibleName("Ignore negative-value warning")
neg_cb.setChecked(bool(cfg.show_neg_err_msg))
@@ -4090,16 +4086,6 @@ def commit_streams(_=None, spins=stream_spins,
{"ShowNegErrMsg": "1" if on else "0"}))
fl.addRow(neg_cb)
- _gb, fl = self._group("System Costs", layout)
- self._float_row(
- fl, "System red cost", cfg.system_red_cost,
- lambda v: self.system_config_changed.emit(
- {"systemRedCost": f"{v:.6f}"}))
- self._float_row(
- fl, "Cost of lost flow", cfg.lost_flow_unit_cost,
- lambda v: self.system_config_changed.emit(
- {"sysLostCost": f"{v:.6f}"}))
-
self._restore_tab()
def _refresh_theme(self, _dark: bool | None = None):
diff --git a/raptor/gui/ui/main_window.py b/raptor/gui/ui/main_window.py
index 7f61ae2a..1ca5d4a6 100644
--- a/raptor/gui/ui/main_window.py
+++ b/raptor/gui/ui/main_window.py
@@ -1773,6 +1773,8 @@ def _setup_docks(self):
dock_bottom.raise_()
self._setup_dock_toggle(self._run_toggle_action, self._run_dock)
self._run_panel.experiment_changed.connect(self._on_panel_experiment)
+ self._run_panel.system_config_changed.connect(
+ self._on_system_config_changed)
self._run_panel.dependency_blocked.connect(self._on_panel_dependency)
self._run_panel.cores_changed.connect(self._on_cores_changed)
self._sync_cores_box()
@@ -2145,8 +2147,8 @@ def _on_bands_edited(self, gy: float, yr: float):
"Set Weak Link bands")
def _on_system_config_changed(self, updates: dict) -> None:
- """Apply Inspector Project Settings edits through the undoable
- row writer, then refresh every dependent readout."""
+ """Apply Project Settings / Run panel SYSTEM_DATA edits through the
+ undoable row writer, then refresh every dependent readout."""
if not updates or self._rbd is None:
return
sec = self._rbd.sections.get("SYSTEM_DATA")
diff --git a/raptor/gui/ui/run_experiment_mixin.py b/raptor/gui/ui/run_experiment_mixin.py
index 03f3d424..750c222a 100644
--- a/raptor/gui/ui/run_experiment_mixin.py
+++ b/raptor/gui/ui/run_experiment_mixin.py
@@ -254,18 +254,25 @@ def _sync_run_mirrors(self) -> None:
self._sync_run_panel_units()
def _sync_run_panel_units(self) -> None:
- """Show the model's time unit on the panel's time-valued rows."""
+ """Show the model's time unit and system-level values on the panel."""
panel = getattr(self, "_run_panel", None)
if panel is None:
return
if self._rbd is None:
# Called during dock setup, before any model is open.
panel.set_units("", None)
+ panel.set_system_config(None)
if hasattr(panel, "set_importance_blocks"):
panel.set_importance_blocks(0)
return
units = self._current_units() or ""
panel.set_units(units, getattr(self._rbd, "display_unit", None))
+ sec = self._rbd.sections.get("SYSTEM_DATA")
+ if sec and sec.rows:
+ from raptor.core.fileio.typed import system_config_from_row
+ panel.set_system_config(system_config_from_row(sec.rows[0]))
+ else:
+ panel.set_system_config(None)
# The importance cost estimate is per block, so the panel needs the
# count of the open model. `block_rows` is the same list the
# analysis walks, so the estimate cannot disagree with the work.
diff --git a/raptor/gui/ui/run_panel.py b/raptor/gui/ui/run_panel.py
index ef58a518..3bc146d6 100644
--- a/raptor/gui/ui/run_panel.py
+++ b/raptor/gui/ui/run_panel.py
@@ -44,6 +44,7 @@
)
from raptor.core import run_experiment as rx
+from raptor.core.model import SystemConfig
from raptor.gui.resources.design_tokens import SPACING, active_colour
@@ -56,6 +57,11 @@ class RunPanel(QWidget):
column mirror all stay in one place)."""
experiment_changed = Signal(dict)
+ #: A model-level system value edited here (flowGenerated/systemRedCost/
+ #: sysLostCost). Kept separate from `experiment_changed`: these are
+ #: SYSTEM_DATA columns, not experiment fields, even though the panel
+ #: is where they are now edited.
+ system_config_changed = Signal(dict)
#: Emitted when a dependent plot is requested without its analysis.
dependency_blocked = Signal(str, str) # (plot key, analysis label)
#: The Cores field changed. Not part of the experiment: how many cores
@@ -72,6 +78,13 @@ def __init__(self, parent=None):
self._exp = rx.defaults()
self._loading = False
self._enabled = True
+ # Mirrors of the SYSTEM_DATA cells shown in this panel. Kept here
+ # (not in `_exp`) so the experiment stays a run-settings object;
+ # MainWindow remains the writer of the row.
+ self._flow_generated = 0
+ self._system_red_cost = 0.0
+ self._lost_flow_unit_cost = 0.0
+ self._system_rows_visible: tuple[bool, bool] | None = None
outer = QVBoxLayout(self)
outer.setContentsMargins(SPACING["sm"], SPACING["sm"],
@@ -338,8 +351,52 @@ def _build_analysis_box(self) -> QGroupBox:
Qt.AlignmentFlag.AlignLeft)
self._importance_blocks = 0
self._sync_importance_rows()
+
+ # --- system flow/cost values ----------------------------------
+ # These were Project Settings fields. They are run-wide model
+ # values (SYSTEM_DATA) but belong beside the analyses that spend
+ # them: flow generated is the demand Capacity distributes, the
+ # red cost prices failed time, and the lost-flow cost only means
+ # anything when capacity is tracked too.
+ self._system_form = QFormLayout()
+ self._system_form.setContentsMargins(0, 4, 0, 0)
+ self._flow_gen = QSpinBox()
+ self._flow_gen.setRange(0, 1_000_000_000)
+ self._flow_gen.setValue(int(self._flow_generated))
+ self._flow_gen.setAccessibleName("Flow generated")
+ self._flow_gen.setToolTip(
+ "Demand offered at the Start node each time unit; the "
+ "capacity analysis distributes it across the network.")
+ self._flow_gen.editingFinished.connect(self._on_flow_generated)
+ self._system_form.addRow("Flow generated:", self._flow_gen)
+ self._system_red_cost_field = self._cost_field(
+ "System red cost", self._system_red_cost,
+ self._on_system_red_cost,
+ "Cost per time unit while the whole system is down.")
+ self._system_form.addRow("System red cost:",
+ self._system_red_cost_field)
+ self._lost_flow_cost_field = self._cost_field(
+ "Cost of lost flow", self._lost_flow_unit_cost,
+ self._on_lost_flow_cost,
+ "Price per unit of demand the capacity analysis cannot "
+ "deliver. Needs Capacity and Cost.")
+ self._system_form.addRow("Cost of lost flow:",
+ self._lost_flow_cost_field)
+ outer.addLayout(self._system_form)
return box
+ def _cost_field(self, name: str, value: float, on_commit,
+ tooltip: str) -> QDoubleSpinBox:
+ """A compact cost field with the dialog's old numeric range."""
+ spin = QDoubleSpinBox()
+ spin.setDecimals(6)
+ spin.setRange(-1e15, 1e15)
+ spin.setValue(float(value))
+ spin.setAccessibleName(name)
+ spin.setToolTip(tooltip)
+ spin.editingFinished.connect(on_commit)
+ return spin
+
def _sync_importance_rows(self) -> None:
"""Show/hide the mode picker and state the cost for this model."""
from raptor.api import importance as imp
@@ -506,6 +563,9 @@ def _label_all_controls(self) -> None:
(self._start_stats, "Statistics start time"),
(self._ao_interval, "Availability plot sample interval"),
(self._cost_interval, "Interim cost sample interval"),
+ (self._flow_gen, "Flow generated"),
+ (self._system_red_cost_field, "System red cost"),
+ (self._lost_flow_cost_field, "Cost of lost flow"),
)
for w, name in pairs:
w.setAccessibleName(name)
@@ -646,6 +706,28 @@ def set_experiment(self, exp: dict, *, enabled: bool = True) -> None:
self.set_enabled(enabled)
self._refresh()
+ def set_system_config(self, cfg: SystemConfig | None) -> None:
+ """Show the model's system values without emitting edits back.
+
+ `None` (no model open) resets the three fields to their neutral
+ defaults. Called by MainWindow after every row/undo sync, so the
+ panel and the model cannot drift apart.
+ """
+ self._loading = True
+ try:
+ self._flow_generated = int(
+ getattr(cfg, "flow_generated", 0) or 0)
+ self._system_red_cost = float(
+ getattr(cfg, "system_red_cost", 0.0) or 0.0)
+ self._lost_flow_unit_cost = float(
+ getattr(cfg, "lost_flow_unit_cost", 0.0) or 0.0)
+ self._flow_gen.setValue(self._flow_generated)
+ self._system_red_cost_field.setValue(self._system_red_cost)
+ self._lost_flow_cost_field.setValue(self._lost_flow_unit_cost)
+ finally:
+ self._loading = False
+ self._refresh()
+
def experiment(self) -> dict:
return rx.normalise(self._exp)
@@ -782,6 +864,8 @@ def set_enabled(self, enabled: bool) -> None:
for w in (self._profile, self._trials, self._term_type,
self._term_value, self._start_stats,
self._ao_interval, self._cost_interval,
+ self._flow_gen, self._system_red_cost_field,
+ self._lost_flow_cost_field,
self._importance_check, self._importance_mode,
*self._analysis_checks.values(),
*self._recording_checks.values()):
@@ -804,6 +888,39 @@ def _emit_change(self, key, value) -> None:
self._refresh()
self.experiment_changed.emit(experiment)
+ def _emit_system_change(self, column: str, value: str) -> None:
+ """One SYSTEM_DATA cell edited here -> the model's single writer."""
+ if self._loading or not self._enabled:
+ return
+ self.system_config_changed.emit({column: value})
+
+ def _on_flow_generated(self) -> None:
+ if self._loading or not self._enabled:
+ return
+ value = int(self._flow_gen.value())
+ if value == self._flow_generated:
+ return
+ self._flow_generated = value
+ self._emit_system_change("flowGenerated", str(value))
+
+ def _on_system_red_cost(self) -> None:
+ if self._loading or not self._enabled:
+ return
+ value = float(self._system_red_cost_field.value())
+ if value == self._system_red_cost:
+ return
+ self._system_red_cost = value
+ self._emit_system_change("systemRedCost", f"{value:.6f}")
+
+ def _on_lost_flow_cost(self) -> None:
+ if self._loading or not self._enabled:
+ return
+ value = float(self._lost_flow_cost_field.value())
+ if value == self._lost_flow_unit_cost:
+ return
+ self._lost_flow_unit_cost = value
+ self._emit_system_change("sysLostCost", f"{value:.6f}")
+
def _on_profile(self) -> None:
if self._loading or not self._enabled:
return
@@ -959,6 +1076,24 @@ def _refresh(self) -> None:
self._cost_interval.setEnabled(
bool(self._enabled and experiment["recording"].get("cost_plot")))
+ # Capacity-dependent values. Flow generation is the demand the
+ # capacity analysis distributes; the lost-flow cost only has a
+ # meaning when both capacity and cost are tracked. Hide rows
+ # rather than disable them: an irrelevant number is still a
+ # misleading one. The red cost stays visible -- it applies to
+ # any run that accumulates cost, including a cost-only run.
+ lost_cost_on = ("capacity" in experiment["analyses"]
+ and "cost" in experiment["analyses"])
+ flow_on = "capacity" in experiment["analyses"]
+ visible = (lost_cost_on, flow_on)
+ if visible != self._system_rows_visible:
+ self._system_rows_visible = visible
+ self._system_form.setRowVisible(0, flow_on)
+ self._system_form.setRowVisible(2, lost_cost_on)
+ # Hiding a row changes the panel's preferred height, and the
+ # dock caps itself to that height.
+ self.size_changed.emit()
+
class RunPanelDock(QDockWidget):
"""The panel as a dockable, tabbable widget."""
diff --git a/tests/unit/test_run_panel.py b/tests/unit/test_run_panel.py
index 40596ec9..174a6dc0 100644
--- a/tests/unit/test_run_panel.py
+++ b/tests/unit/test_run_panel.py
@@ -21,6 +21,7 @@
from PySide6.QtWidgets import QApplication
from raptor.core import run_experiment as rx
+from raptor.core.model import SystemConfig
from raptor.gui.ui.run_panel import RunPanel
pytestmark = pytest.mark.gui
@@ -137,6 +138,78 @@ def test_turning_the_plot_on_enables_the_interval(self, panel):
assert panel._ao_interval.isEnabled()
+class TestSystemValues:
+ """Flow/cost values moved out of Project Settings and into the sections
+ whose analyses consume them. Visibility is the point: a lost-flow cost
+ with no capacity model, or no cost analysis, is a number with no
+ meaning."""
+
+ def _cfg(self, **changes):
+ cfg = SystemConfig(flow_generated=120,
+ system_red_cost=45.5,
+ lost_flow_unit_cost=7.25)
+ for key, value in changes.items():
+ setattr(cfg, key, value)
+ return cfg
+
+ def test_values_load_without_emitting(self, panel):
+ got = []
+ panel.system_config_changed.connect(got.append)
+ panel.set_system_config(self._cfg())
+ assert panel._flow_gen.value() == 120
+ assert panel._system_red_cost_field.value() == pytest.approx(45.5)
+ assert panel._lost_flow_cost_field.value() == pytest.approx(7.25)
+ assert got == []
+
+ def test_flow_generated_writes_the_system_row_column(self, panel):
+ got = []
+ panel.system_config_changed.connect(got.append)
+ panel._flow_gen.setValue(250)
+ panel._flow_gen.editingFinished.emit()
+ assert got[-1] == {"flowGenerated": "250"}
+
+ def test_costs_write_their_system_row_columns(self, panel):
+ got = []
+ panel.system_config_changed.connect(got.append)
+ panel._system_red_cost_field.setValue(12.5)
+ panel._system_red_cost_field.editingFinished.emit()
+ panel._lost_flow_cost_field.setValue(3.75)
+ panel._lost_flow_cost_field.editingFinished.emit()
+ assert got == [{"systemRedCost": "12.500000"},
+ {"sysLostCost": "3.750000"}]
+
+ def test_system_red_cost_is_always_visible(self, panel):
+ panel.set_experiment(rx.normalise({"analyses": []}))
+ assert panel._system_form.isRowVisible(1)
+
+ def test_flow_generated_needs_capacity(self, panel):
+ panel.set_experiment(rx.normalise({"analyses": []}))
+ assert not panel._system_form.isRowVisible(0)
+ panel.set_experiment(rx.normalise({"analyses": ["capacity"]}))
+ assert panel._system_form.isRowVisible(0)
+
+ def test_lost_flow_cost_needs_capacity_and_cost(self, panel):
+ panel.set_experiment(rx.normalise({"analyses": ["capacity"]}))
+ assert not panel._system_form.isRowVisible(2)
+ panel._analysis_checks["cost"].setChecked(True)
+ assert panel._system_form.isRowVisible(2)
+
+ def test_turning_capacity_off_hides_its_values_again(self, panel):
+ panel.set_experiment(
+ rx.normalise({"analyses": ["capacity", "cost"]}))
+ panel._analysis_checks["capacity"].setChecked(False)
+ assert not panel._system_form.isRowVisible(0)
+ assert not panel._system_form.isRowVisible(2)
+
+ def test_a_locked_panel_does_not_write_system_values(self, panel):
+ got = []
+ panel.system_config_changed.connect(got.append)
+ panel.set_enabled(False)
+ panel._flow_gen.setValue(999)
+ panel._flow_gen.editingFinished.emit()
+ assert got == []
+
+
class TestTerminationRelabelling:
@pytest.mark.parametrize("term,label", [
("TimeSim", "Run length:"),
@@ -232,6 +305,8 @@ def test_every_named_control_has_an_accessible_name(self, panel):
panel._start_stats, panel._profile, panel._step_count,
panel._run_to, panel._breakpoint, panel._ao_interval,
panel._cost_interval, panel._readiness,
+ panel._flow_gen, panel._system_red_cost_field,
+ panel._lost_flow_cost_field,
*panel._analysis_checks.values(),
*panel._recording_checks.values()]
for w in widgets:
diff --git a/tests/unit/test_run_panel_system_values.py b/tests/unit/test_run_panel_system_values.py
new file mode 100644
index 00000000..aba01fe4
--- /dev/null
+++ b/tests/unit/test_run_panel_system_values.py
@@ -0,0 +1,83 @@
+"""System flow/cost values live in the Run panel, not Project Settings.
+
+The panel is still a view: it emits the SYSTEM_DATA edit and MainWindow
+writes the row through its one undoable path. These tests pin both the
+visibility rules (capacity gates flow, cost gates the lost-flow price)
+and the removal from the Inspector's Project Settings page.
+"""
+from __future__ import annotations
+
+import pytest
+from PySide6.QtWidgets import QApplication, QGroupBox, QLabel
+
+pytestmark = pytest.mark.gui
+
+MODEL = "tests/golden/TwoBlockRedundancy/input.rbd"
+
+
+@pytest.fixture
+def win():
+ from raptor.core.fileio.rbd import load_rbd
+ from raptor.gui.ui.main_window import MainWindow
+
+ QApplication.instance() or QApplication([])
+ w = MainWindow()
+ w._load_into_app(load_rbd(MODEL), MODEL)
+ yield w
+ w._dirty = False
+ w.close()
+
+
+def _row(win):
+ return win._rbd.sections["SYSTEM_DATA"].rows[0]
+
+
+def test_loading_a_model_fills_the_panel_system_fields(win):
+ from raptor.core.fileio.typed import system_config_from_row
+
+ cfg = system_config_from_row(_row(win))
+ panel = win._run_panel
+ assert panel._flow_gen.value() == cfg.flow_generated
+ assert panel._system_red_cost_field.value() == pytest.approx(
+ cfg.system_red_cost)
+ assert panel._lost_flow_cost_field.value() == pytest.approx(
+ cfg.lost_flow_unit_cost)
+
+
+def test_flow_generated_edit_writes_system_data(win):
+ panel = win._run_panel
+ panel._analysis_checks["capacity"].setChecked(True)
+ assert panel._system_form.isRowVisible(0)
+
+ panel._flow_gen.setValue(250)
+ panel._flow_gen.editingFinished.emit()
+
+ assert _row(win)["flowGenerated"] == "250"
+ assert panel._flow_gen.value() == 250
+
+
+def test_cost_edits_write_system_data(win):
+ panel = win._run_panel
+ panel._analysis_checks["capacity"].setChecked(True)
+ panel._analysis_checks["cost"].setChecked(True)
+ assert panel._system_form.isRowVisible(2)
+
+ panel._system_red_cost_field.setValue(12.5)
+ panel._system_red_cost_field.editingFinished.emit()
+ panel._lost_flow_cost_field.setValue(3.75)
+ panel._lost_flow_cost_field.editingFinished.emit()
+
+ assert float(_row(win)["systemRedCost"]) == pytest.approx(12.5)
+ assert float(_row(win)["sysLostCost"]) == pytest.approx(3.75)
+
+
+def test_project_settings_no_longer_show_the_moved_values(win):
+ win._inspector.show_system_config()
+ labels = [label.text() for label in
+ win._inspector.findChildren(QLabel)]
+ groups = [box.title() for box in
+ win._inspector.findChildren(QGroupBox)]
+ assert not any("Flow generated" in text for text in labels)
+ assert not any("System red cost" in text for text in labels)
+ assert not any("Cost of lost flow" in text for text in labels)
+ assert "System Costs" not in groups