Wire up reporting (plots, BOM, summary, animation) and a working CLI (Stage 7)
- report/plots.py: current/field/velocity plots per stage, Agg backend so it works headless in Docker/on a server - report/bom.py: itemized bill of materials with real component prices - report/summary.py: honest text/JSON summary including a "model limitations" section and a saturation warning flag per stage - report/animate.py: the visualization the user explicitly asked for -- a GIF of the slug flying through the tube with each coil glowing by its instantaneous current, reconstructing the pre-trigger ballistic flight segment (not just the stored discharge phase) for a continuous timeline - cli.py: sweep/evolve/simulate/report subcommands now actually call the underlying modules instead of being stubs - Extended StageResult/StageOutcome with the coil/entry-state fields the report layer needed (mu_eff, turns, coil_length, entry_x/v) rather than recomputing them by other means Verified end-to-end through `docker compose run`, not just pytest: a real sweep (30 runs, 11 feasible) followed by `report --animate` produced a correct GIF/plots/BOM/summary for the actual best result found (32.8% efficiency, 982 RUB) -- not a synthetic fixture. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
55
tests/test_cli_smoke.py
Normal file
55
tests/test_cli_smoke.py
Normal file
@@ -0,0 +1,55 @@
|
||||
from pathlib import Path
|
||||
|
||||
from gausse.cli import main
|
||||
from gausse.storage.database import fetch_runs, open_connection
|
||||
|
||||
|
||||
def test_cli_sweep_writes_database(tmp_path):
|
||||
db_path = tmp_path / "runs.sqlite3"
|
||||
rc = main(["sweep", "--db", str(db_path), "--n", "10", "--workers", "2", "--seed", "1", "--max-stages", "2"])
|
||||
assert rc == 0
|
||||
assert db_path.exists()
|
||||
conn = open_connection(db_path)
|
||||
assert len(fetch_runs(conn)) == 10
|
||||
conn.close()
|
||||
|
||||
|
||||
def test_cli_sweep_then_report_end_to_end(tmp_path):
|
||||
db_path = tmp_path / "runs.sqlite3"
|
||||
out_dir = tmp_path / "report"
|
||||
rc = main(["sweep", "--db", str(db_path), "--n", "40", "--workers", "2", "--seed", "2"])
|
||||
assert rc == 0
|
||||
|
||||
rc2 = main(["report", "--db", str(db_path), "--out", str(out_dir), "--top", "1"])
|
||||
assert rc2 == 0
|
||||
rank_dirs = list(out_dir.glob("rank1_*"))
|
||||
assert len(rank_dirs) == 1
|
||||
run_dir = rank_dirs[0]
|
||||
assert (run_dir / "summary.txt").exists()
|
||||
assert (run_dir / "summary.json").exists()
|
||||
assert (run_dir / "bom.txt").exists()
|
||||
assert (run_dir / "current.png").exists()
|
||||
assert (run_dir / "velocity_vs_position.png").exists()
|
||||
|
||||
|
||||
def test_cli_simulate_single_run_id(tmp_path):
|
||||
db_path = tmp_path / "runs.sqlite3"
|
||||
out_dir = tmp_path / "sim_out"
|
||||
main(["sweep", "--db", str(db_path), "--n", "5", "--workers", "1", "--seed", "3", "--max-stages", "2"])
|
||||
|
||||
conn = open_connection(db_path)
|
||||
row = fetch_runs(conn)[0]
|
||||
conn.close()
|
||||
|
||||
rc = main(["simulate", "--db", str(db_path), "--run-id", row.run_id, "--out", str(out_dir)])
|
||||
assert rc == 0
|
||||
assert (out_dir / "summary.txt").exists()
|
||||
|
||||
|
||||
def test_cli_report_with_empty_database_reports_error_not_crash(tmp_path):
|
||||
db_path = tmp_path / "empty.sqlite3"
|
||||
from gausse.storage.database import open_connection as oc
|
||||
|
||||
oc(db_path).close() # создаёт пустую (но валидную) базу
|
||||
rc = main(["report", "--db", str(db_path), "--out", str(tmp_path / "out")])
|
||||
assert rc == 1
|
||||
Reference in New Issue
Block a user