Apply the image decision tree to missing hall portraits
A hall visual that stays on the general branch uses the Efficient model from the 2026-10-08 intelligence-radar tree. render-hall writes only portraits whose files are still absent. Assistant: grok Assistant-Session: 01a11ab6-1534-7d63-8606-662b92a80ed6
This commit is contained in:
parent
7073d2543c
commit
e1bab67bd1
10 changed files with 675 additions and 11 deletions
141
tests/test_image_selection.py
Normal file
141
tests/test_image_selection.py
Normal file
|
|
@ -0,0 +1,141 @@
|
|||
"""Decision tree and hall portrait planning."""
|
||||
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
from llm_connect.cli import main
|
||||
from llm_connect.hall_portraits import plan_missing_portraits, reveal_portrait
|
||||
from llm_connect.image_selection import classify_image_branch, selection_for_prompt
|
||||
|
||||
HALL_PROMPT = (
|
||||
"Constellation dialect. Square, dark indigo. A gold-wire helix. "
|
||||
"Fine technical-illustration linework, no logos, no readable text."
|
||||
)
|
||||
|
||||
|
||||
def test_hall_house_prompt_is_general_efficient() -> None:
|
||||
selection = selection_for_prompt(HALL_PROMPT)
|
||||
assert classify_image_branch(HALL_PROMPT) == "general"
|
||||
assert selection.tier == "efficient"
|
||||
assert selection.model == "google/gemini-nano-banana-2.1"
|
||||
assert selection.quote_usd == 0.04
|
||||
assert selection.max_cost_usd == 0.08
|
||||
|
||||
|
||||
def test_negated_text_does_not_become_text_heavy() -> None:
|
||||
assert classify_image_branch("no logos, no readable text, pale gold") == "general"
|
||||
|
||||
|
||||
def test_abstract_marks_stay_on_the_general_branch() -> None:
|
||||
prompt = (
|
||||
"a ledger glows with the words rendered as abstract marks, not readable text. "
|
||||
"no logos, no lettering"
|
||||
)
|
||||
assert classify_image_branch(prompt) == "general"
|
||||
|
||||
|
||||
def test_requested_words_take_the_text_branch() -> None:
|
||||
selection = selection_for_prompt("A poster whose headline is the words HELIX")
|
||||
assert selection.branch == "text_heavy"
|
||||
assert selection.model == "openai/gpt-image-2.5-sunburst"
|
||||
|
||||
|
||||
def test_vector_and_self_host_follow_the_tree() -> None:
|
||||
assert selection_for_prompt("Export as a vector svg icon").model.startswith("recraft/")
|
||||
try:
|
||||
selection_for_prompt("self-host the open weights on our own hardware")
|
||||
except Exception as exc:
|
||||
assert "OpenRouter" in str(exc)
|
||||
else:
|
||||
raise AssertionError("self-host branch should refuse")
|
||||
|
||||
|
||||
def _entry(visual: str, comment: str | None, entry_id: str = "hall-worker-ada-1") -> str:
|
||||
commented = f"<!--  -->\n" if comment else ""
|
||||
return f"""---
|
||||
id: {entry_id}
|
||||
status: draft
|
||||
---
|
||||
|
||||
# Ada — a seat
|
||||
|
||||
## Visual prompt
|
||||
|
||||
> {visual}
|
||||
|
||||
{commented}
|
||||
## Handoff
|
||||
|
||||
Next.
|
||||
"""
|
||||
|
||||
|
||||
def test_plan_skips_a_portrait_that_is_already_on_disk(tmp_path: Path) -> None:
|
||||
entries = tmp_path / "entries"
|
||||
visuals = tmp_path / "visuals"
|
||||
entries.mkdir()
|
||||
visuals.mkdir()
|
||||
(entries / "one.md").write_text(_entry(HALL_PROMPT, "Ada"), encoding="utf-8")
|
||||
(entries / "two.md").write_text(_entry(HALL_PROMPT, None, "hall-worker-bea-2"), encoding="utf-8")
|
||||
(visuals / "bea-file.jpg").write_bytes(b"x")
|
||||
# two.md has no comment, so its derived file is the id. Put that file on disk.
|
||||
(visuals / "hall-worker-bea-2.jpg").write_bytes(b"x")
|
||||
|
||||
jobs = plan_missing_portraits(tmp_path)
|
||||
assert [job.entry_id for job in jobs] == ["hall-worker-ada-1"]
|
||||
assert jobs[0].output_path == visuals / "ada.jpg"
|
||||
assert jobs[0].relative_path == "../visuals/ada.jpg"
|
||||
assert jobs[0].alt == "Ada"
|
||||
assert "no readable text" in jobs[0].prompt
|
||||
|
||||
|
||||
def test_reveal_portrait_uncomments_the_intended_line() -> None:
|
||||
text = _entry(HALL_PROMPT, "Ada")
|
||||
updated = reveal_portrait(text, "../visuals/ada.jpg", "Ada")
|
||||
assert "<!--" not in updated
|
||||
assert "" in updated
|
||||
assert "status: draft" in updated
|
||||
|
||||
|
||||
def test_plan_reads_a_prompt_that_is_not_a_blockquote(tmp_path: Path) -> None:
|
||||
entries = tmp_path / "entries"
|
||||
visuals = tmp_path / "visuals"
|
||||
entries.mkdir()
|
||||
visuals.mkdir()
|
||||
body = """---
|
||||
id: hall-worker-cy-3
|
||||
status: draft
|
||||
---
|
||||
|
||||
# Cy — a seat
|
||||
|
||||
## Visual prompt
|
||||
|
||||
**Dialect:** Constellation. Square, no logos, no readable text.
|
||||
|
||||
A gold-wire orrery suspended in dark indigo.
|
||||
|
||||
## Handoff
|
||||
|
||||
Next.
|
||||
"""
|
||||
(entries / "three.md").write_text(body, encoding="utf-8")
|
||||
jobs = plan_missing_portraits(tmp_path)
|
||||
assert len(jobs) == 1
|
||||
assert "orrery" in jobs[0].prompt
|
||||
assert jobs[0].output_path.name == "hall-worker-cy-3.jpg"
|
||||
|
||||
|
||||
def test_cli_plan_is_dry_and_names_the_efficient_model(tmp_path: Path, capsys) -> None:
|
||||
entries = tmp_path / "entries"
|
||||
visuals = tmp_path / "visuals"
|
||||
entries.mkdir()
|
||||
visuals.mkdir()
|
||||
(entries / "one.md").write_text(_entry(HALL_PROMPT, "Ada"), encoding="utf-8")
|
||||
code = main(["image", "plan", "--hall", str(tmp_path), "--json"])
|
||||
assert code == 0
|
||||
payload = json.loads(capsys.readouterr().out)
|
||||
assert payload["count"] == 1
|
||||
assert payload["jobs"][0]["model"] == "google/gemini-nano-banana-2.1"
|
||||
assert payload["max_batch_cost_usd"] == 0.08
|
||||
assert not (visuals / "ada.jpg").exists()
|
||||
Loading…
Add table
Add a link
Reference in a new issue