- 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>
56 lines
2.0 KiB
Python
56 lines
2.0 KiB
Python
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
|