feat(review): View prompt + Call 2 declared attrs, Create-desc gate

Shared assemble_call2 for GET .../call2/prompt and POST call2 (seed +
DECLARED ATTRIBUTES + dimension fact + brand/geo; photo slots without
base64). Create desc disabled until content LLM + title/short + species +
wood cats/primary + edge + ≥1 figure + grade + moisture + price; show
missing. figure_term_ids required for ready. Moisture defaults to Kiln
dried (kd). Pin snl / liveedge in wood-cat UI. Ready-on-change via
MultiSelect onCommit.
This commit is contained in:
grokbot_ty
2026-09-26 10:11:50 -07:00
parent a801a757f8
commit 1ff082f77c
13 changed files with 790 additions and 44 deletions
+113
View File
@@ -342,3 +342,116 @@ def test_call1_sends_long_edge_dormant_while_disabled(env):
r = client.post("/api/v1/slabs/missing/inference/call1")
assert r.status_code == 410
assert r.json()["error"]["code"] == "call1_disabled"
def test_assemble_call2_includes_declared_attrs_and_dims(env):
"""Call 2 text = seed + DECLARED ATTRIBUTES + dimension fact + brand/geo; shared assembler."""
client, conn = env
conn.execute(
"INSERT INTO woo_categories(id,name,slug,parent_id,woo_id) VALUES "
"(10,'Species','species',NULL,10),"
"(11,'Black Walnut','black-walnut',10,11),"
"(20,'Wcat','wcat',NULL,20),"
"(21,'Slabs & Lumber','snl',20,21),"
"(22,'Live edge wood','liveedge',21,22)"
)
conn.execute(
"INSERT INTO woo_attributes(id,name,slug,woo_id) VALUES "
"(6,'Edge type','pa_etype',6),(8,'Figure','pa_fig',8),"
"(9,'Grade','pa_gr',9),(7,'Moisture','pa_mst',7)"
)
conn.execute(
"INSERT INTO woo_attribute_terms(id,attribute_id,name,slug,woo_id) VALUES "
"(321,6,'Live Edge 1 side','le1',321),"
"(328,8,'Curl','curl',328),"
"(342,9,'Fancy','fnc',342),"
"(326,7,'Kiln dried','kd',326)"
)
conn.execute(
"INSERT INTO woo_tags(id,name,slug,woo_id) VALUES (1,'feat-knot','feat-knot',1)"
)
conn.execute(
"INSERT OR REPLACE INTO settings(key,value,value_enc) VALUES "
"('brand_voice','Warm and precise.',NULL),"
"('geo_context','Pacific Northwest mill.',NULL)"
)
conn.execute(
"INSERT INTO slabs(id,sku,length_in,thickness_in,width_avg_in,width_min_in,width_max_in,bdft,"
"species_id,wood_category_ids,edge_type_term_id,figure_term_ids,grade_term_id,moisture_term_id,"
"feat_tag_ids,title,short_description,status,server_rev,client_rev,created_at,updated_at) "
"VALUES ('s1','SLAB-01',114.0,1.75,24.3,18.0,24.3,18.4,11,?,321,?,342,326,?,"
"'Figured Black Walnut Live Edge Slab','short line','ready',1,1,'t','t')",
(json.dumps([22]), json.dumps([328]), json.dumps([1])),
)
png = _rgba_png(200, 200)
conn.execute(
"INSERT INTO slab_photos(id,slab_id,seq,processed_blob,created_at) VALUES ('p9','s1',1,?, 't')",
(png,),
)
conn.commit()
from backend.inference import assemble_call2, assemble_call2_text, CALL2_SEED
assembled = assemble_call2(conn, "s1")
assert assembled is not None
text = assembled["text"]
assert "DECLARED ATTRIBUTES" in text
assert "species: Black Walnut" in text
assert "primary wood category: Live edge wood" in text
assert "edge: Live Edge 1 side" in text
assert "figure: Curl" in text
assert "grade: Fancy" in text
assert "moisture: Kiln dried" in text
assert "features: knot" in text
assert "DIMENSION FACT BLOCK" in text
assert "length=114.0in" in text
assert "Brand voice: Warm and precise." in text
assert "Geo: Pacific Northwest mill." in text
assert CALL2_SEED.split("\n")[0] in text
# No raw base64 in View-prompt shape
assert "photos" in assembled
assert assembled["photos"][0]["id"] == "p9"
assert assembled["photos"][0]["seq"] == 1
assert assembled["photos"][0]["bytes"] > 0
assert "base64" not in json.dumps(assembled["photos"])
# View prompt GET mirrors assembler (no model)
r = client.get("/api/v1/slabs/s1/inference/call2/prompt")
assert r.status_code == 200, r.text
body = r.json()
assert body["text"] == text
assert body["photos"][0]["id"] == "p9"
assert "data:image" not in json.dumps(body)
def test_call2_post_uses_same_assembler_text(env, monkeypatch):
"""POST call2 sends the same assembled text View prompt returns."""
client, conn = env
conn.execute("INSERT OR REPLACE INTO settings(key,value,value_enc) VALUES ('content_llm_enabled','true',NULL)")
conn.execute(
"INSERT OR REPLACE INTO settings(key,value,value_enc) VALUES "
"('brand_voice','voice',NULL),('geo_context','geo',NULL)"
)
conn.execute(
"INSERT INTO slabs(id,sku,length_in,thickness_in,width_avg_in,width_min_in,width_max_in,bdft,"
"title,status,server_rev,client_rev,created_at,updated_at) "
"VALUES ('s1','SLAB-01',100,2,20,18,22,18,'Walnut Slab','ready',1,1,'t','t')"
)
conn.execute(
"INSERT INTO slab_photos(id,slab_id,seq,processed_blob,created_at) VALUES ('p1','s1',1,?, 't')",
(_rgba_png(64, 64),),
)
conn.commit()
from backend.inference import InferenceClient, assemble_call2
expected = assemble_call2(conn, "s1")["text"]
captured = {}
def fake_chat(self, messages):
captured["text"] = messages[0]["content"][-1]["text"]
return "<p>ok</p>"
monkeypatch.setattr(InferenceClient, "chat", fake_chat)
r = client.post("/api/v1/slabs/s1/inference/call2")
assert r.status_code == 200, r.text
assert captured["text"] == expected
assert "DECLARED ATTRIBUTES" in captured["text"]
assert "DIMENSION FACT BLOCK" in captured["text"]
+26
View File
@@ -194,3 +194,29 @@ def test_review_persists_owner_overrides(env):
assert got["sku"] == "SLAB-99"
# server_rev was incremented (the UPDATE fired).
assert got["server_rev"] == 2
def test_ready_requires_figure_term_ids(env):
"""UX requires ≥1 figure — figure_term_ids is in _REQUIRED."""
from backend.review import missing_required, compute_ready
slab = _slab(figure_term_ids=[])
miss = missing_required(slab)
assert "figure_term_ids" in miss
ready, missing = compute_ready({**slab, "price": 1.0, "price_source": "manual",
"title": "T", "short_description": "S",
"edge_type_term_id": 1, "grade_term_id": 1,
"thickness_term_id": 1, "moisture_term_id": 1})
assert ready is False
assert "figure_term_ids" in missing
ready2, missing2 = compute_ready({
**slab,
"figure_term_ids": [1],
"price": 1.0, "price_source": "manual",
"title": "T", "short_description": "S",
"edge_type_term_id": 1, "grade_term_id": 1,
"thickness_term_id": 1, "moisture_term_id": 1,
})
# May still miss other fields depending on fixture; figure must not be listed
assert "figure_term_ids" not in missing2