- pyproject.toml + puga/cli.py: `pip install -e .` installs a `puga <tool>` command, so no venv path is needed (replaces requirements.txt) - tools/plan_push.py: `pull <uuid>` reads a plan (incl. UI edits) back into a YAML spec; recipe entries may carry an amount so switched-off recipes round trip; fixes a variable that overwrote the plan name in build_payload, with a regression test - README: usage with the puga command, and a section on working on the same plans in the PRUNplanner UI and from Claude (write, edit in UI, read back) - CLAUDE.md updated accordingly Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
45 lines
2.2 KiB
Python
45 lines
2.2 KiB
Python
import importlib.util, sys
|
|
from pathlib import Path
|
|
import pytest
|
|
|
|
spec = importlib.util.spec_from_file_location("plan_push", Path(__file__).resolve().parent.parent / "tools" / "plan_push.py")
|
|
pp_tool = importlib.util.module_from_spec(spec)
|
|
spec.loader.exec_module(pp_tool)
|
|
|
|
R = [
|
|
{"recipe_id": "SME#6xALO 1xO 1xC=>3xAL", "building_ticker": "SME", "inputs": [{"material_ticker": t} for t in ("ALO", "O", "C")], "outputs": [{"material_ticker": "AL"}]},
|
|
{"recipe_id": "SME#6xALO 1xO 1xC 1xFLX=>4xAL", "building_ticker": "SME", "inputs": [{"material_ticker": t} for t in ("ALO", "O", "C", "FLX")], "outputs": [{"material_ticker": "AL"}]},
|
|
]
|
|
|
|
|
|
def test_resolve_by_ticker_sets_distinguishes_flux():
|
|
assert pp_tool.resolve_recipe("ALO,FLX,C,O=>AL", "SME", R).endswith("=>4xAL")
|
|
assert pp_tool.resolve_recipe("ALO,C,O=>AL", "SME", R).endswith("=>3xAL")
|
|
assert pp_tool.resolve_recipe("EXT#ALO", "EXT", R) == "EXT#ALO"
|
|
with pytest.raises(ValueError):
|
|
pp_tool.resolve_recipe("ALO=>AL", "SME", R)
|
|
|
|
|
|
def _spec(**kw):
|
|
s = dict(name="[PuGa] x", planet="ZV-759c", permits=1, cogc="METALLURGY", buildings=[{"building": "SME", "amount": 1, "recipes": ["ALO,C,O=>AL"]}])
|
|
s.update(kw)
|
|
return s
|
|
|
|
|
|
def test_guardrail_requires_prefix_and_validates():
|
|
ok = pp_tool.build_payload(_spec(), R, {"SME"})
|
|
assert ok["plan_cogc"] == "METALLURGY" and len(ok["plan_data"]["experts"]) == 9 and len(ok["plan_data"]["workforce"]) == 5
|
|
for bad in (dict(name="My plan"), dict(planet="ZV759c"), dict(permits=4), dict(cogc="FOO")):
|
|
with pytest.raises(ValueError):
|
|
pp_tool.build_payload(_spec(**bad), R, {"SME"})
|
|
with pytest.raises(ValueError):
|
|
pp_tool.build_payload(_spec(buildings=[{"building": "ZZZ", "amount": 1}]), R, {"SME"})
|
|
|
|
|
|
def test_recipe_dict_keeps_amount_and_does_not_clobber_plan_name():
|
|
"""Regression: a loop variable once overwrote the plan name; also switched-off recipes (amount 0) must round trip."""
|
|
spec = _spec(buildings=[{"building": "SME", "amount": 1, "recipes": [{"recipe": "ALO,C,O=>AL", "amount": 0}]}])
|
|
out = pp_tool.build_payload(spec, R, {"SME"})
|
|
assert out["plan_name"] == "[PuGa] x"
|
|
assert out["plan_data"]["buildings"][0]["active_recipes"] == [{"recipeid": "SME#6xALO 1xO 1xC=>3xAL", "amount": 0}]
|