1"""The watcher (``feedtwin.session.watch``), and the faults it found first.
3A clean LE4 (6) burn and abort raises no flag and keeps its mass; each check
4fires when its condition is put into a real stand; flags are episodes, not
5ticks. And the two faults the watcher turned up on its first runs:
7* The chamber of a drawing with no engine model counted any flow touching it
8 as "lit", including flow *out* of it: after a burn it stayed at its drawn
9 385 psia, and an Engine Abort's open mains drove 3 kg/s from the dead engine
10 back into the tanks -- 16 kg the mass balance could not account for.
11* A tank the cart loads through the drawing credited any liquid arriving at
12 its outlet, wherever it came from: at Fire on unpressed tanks the chamber's
13 push-back filled a full fuel tank 46 g a second, and on a longer pad
14 overfilled it ("tank is overfull") and stopped the stand.
17from __future__
import annotations
19from dataclasses
import replace
27from test_ignore_gse
import DRAWING, TABLES, _ids, _session
29needs_le4 = pytest.mark.skipif(
30 not (DRAWING.exists()
and TABLES.is_dir()), reason=
"LE4 fixture or tables absent"
35 session = _session(
True)
38 copv_psi=session.setup.copv_target_psi,
39 fill_fraction=session.setup.full_fraction,
41 for state, seconds
in ((
"Ready", 2.0), (
"Fire", 8.0), (
"Engine Abort", 8.0)):
43 for _
in range(int(seconds / 0.5)):
51 record = session.solver_log[-1]
52 assert record.mass_error_kg - record.guard_kg == pytest.approx(0.0, abs=1e-5)
53 assert [f.to_dict()
for f
in session.watcher.flags] == []
58 """After burnout the chamber is the air, not its drawn 385 psia."""
60 net = session.model.built.network
61 assert net.nodes[session._chamber_node].pressure == pytest.approx(101325.0)
62 assert all(sim.refused_kg == 0.0
for sim
in session.tanks.values())
68 session = _session(
False)
69 fuel = session.tanks[ids[
"Eth-Tank"]]
70 for state, steps
in ((
"Armed", 1), (
"Fuel Fill", 30), (
"Armed", 1)):
72 for _
in range(steps):
74 full = fuel.state.liquid_mass
76 session.state =
"Fire"
79 assert fuel.state.liquid_mass <= full + 1e-9,
"no liquid from the chamber"
85 session = _session(
True)
86 session.state =
"Armed"
88 record = session.solver_log[-1]
89 fuel = session.tanks[ids[
"Eth-Tank"]]
92 watcher.observe(session, record)
93 assert not watcher.flags,
"a stand at rest is clean"
95 fuel.sealed, fuel.floored_kg =
True, fuel.floored_kg + 1e-3
98 for b
in session.model.built.network.branches
99 if b
not in session._last_isolated
101 session._last_flows = {**session._last_flows, branch: INITIAL_FLOW}
102 fuel.refused_kg += 1.0
103 watcher.observe(session, replace(record, t=record.t + 0.5))
104 checks = {f.check
for f
in watcher.flags}
105 assert {
"sealed_gain",
"stuck_guess",
"liquid_from_nowhere"} <= checks
109 """Held for its hold before it opens; closed once gone for a second; one
110 row however many ticks it lasts."""
112 held =
_Seen(
"held_open",
"Eth-Tank (gas)",
"stuck", hold=2.0)
113 for t
in (0.0, 0.5, 1.0, 1.5):
114 watcher._update(t, [held])
115 assert not watcher.flags,
"not yet held for two seconds"
116 for t
in (2.0, 2.5, 3.0):
117 watcher._update(t, [held])
118 assert len(watcher.flags) == 1
and watcher.flags[0].started == 0.0
119 watcher._update(3.5, [])
120 assert watcher.flags[0].open,
"a tick off is not the end of it"
121 watcher._update(3.0 + CLOSE_AFTER_S, [])
122 assert not watcher.flags[0].open
and watcher.flags[0].last == 3.0
123 assert len(watcher.flags) == 1
None test_a_full_fuel_tank_does_not_fill_from_the_chamber()
None test_a_dead_engine_does_not_push_back_up_the_mains()
None test_a_burn_and_an_abort_raise_nothing_and_keep_their_mass()
None test_a_flag_is_an_episode()
None test_each_check_fires_on_its_condition()