The acknowledged overhang: "doesn't fit, shelved anyway" goes legit

Eric's office towers are 9.5 inches deep; Etherfields boxes are 11.8
inches square. Spine-out they stand 2.3 inches proud of the shelf edge
— clearly livable, it's how they sit today — but the lane model will
honestly refuse forever: can't stand (11.8 > 9.5 depth), can't stack
flat (same), so the opening warns NO PACKING FITS. The first deliberate
misfit deserves first-class status.

An assignment can now carry acknowledged: true. An acknowledged box
takes the standing lane WITHOUT the height/depth check — the human has
seen the overhang — while the width budget stays honest (nine
acknowledged monoliths still overflow a 23.25-inch row). It drops out
of misfit/laneless warnings everywhere: the wall diagram, the sheet,
and bggpipe dims (which now says "overhangs, acknowledged" instead).

Ways in: an "accept overhang" button in the opening's sheet (appears
on any resident the lanes can't take; tap again to retract), or an
acknowledge column (yes) in shelve --import. Once acknowledged, later
imports needn't re-state it.

Re-import hardening, prompted by Eric's "will a new import make a
mess?": it's an upsert, not a reset — and now genuinely so. Previously
a re-import preserved notes but silently wiped lane overrides; now
notes, lanes, and acknowledgments all survive when the opening is
unchanged (a move resets lane and acknowledgment — new spot, new
verdict). Same fix in /api/locate: the lane-flip button used to wipe
the hand-entered note because LocateBody.note defaulted to "" —
it's now tri-state, None means leave it alone.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016jXZFSTZQKzAC8fqpWSz9g
This commit is contained in:
Eric Wagoner
2026-08-09 15:45:36 -04:00
co-authored by Claude Fable 5
parent 4ce7706521
commit 7d7d3bb8da
10 changed files with 291 additions and 37 deletions
+86
View File
@@ -13,9 +13,11 @@ from bggpipe.shelves import (
fits_opening,
import_assignments,
load_furniture,
load_locations,
new_opening_id,
opening_report,
save_furniture,
save_locations,
)
@@ -1009,3 +1011,87 @@ def test_add_openings_at_top(tmp_path):
o["label"] for o in web.get("/api/shelves").json()["units"][0]["openings"]
]
assert labels == ["on top", "A1", "A2", "B1", "B2"]
def test_acknowledged_overhang(tmp_path):
"""Etherfields on the office towers: 11.8\" boxes on a 9.5\"-deep
shelf stand 2.3\" proud. The checker honestly refuses — until the
overhang is acknowledged, after which it takes the standing lane
(width-honest) and stops counting as a warning."""
tower = {
"id": "tower-e1",
"label": "E1",
"zone": "",
"width_in": 23.25,
"height_in": 19.0,
"depth_in": 9.5,
}
ether = _entry("Etherfields", 11.8, 11.8, 2.9)
# face-out it FITS the opening; it's the lane packing that refuses
# (can't stand spine-out, can't stack flat — 11.8" > 9.5" depth)
report = opening_report(tower, [("g1", ether)])
assert report["misfits"] == []
assert report["laneless"] == ["Etherfields"]
assert report["overfull"]
report = opening_report(tower, [("g1", ether)], acknowledged={"g1"})
assert report["misfits"] == []
assert report["laneless"] == []
assert report["lanes"] == {"g1": "standing"}
assert report["standing_in"] == pytest.approx(2.9)
# the width budget stays honest: nine acknowledged monoliths
# still overflow a 23.25" row
crowd = [(f"g{i}", ether) for i in range(9)]
report = opening_report(tower, crowd, acknowledged={k for k, _ in crowd})
assert report["overfull"]
def test_import_acknowledge_column(tmp_path):
"""The CSV import rejects a doesn't-fit row with a hint, accepts it
with acknowledge=yes, and a re-import keeps lane + acknowledgment
when the opening is unchanged (upsert, not reset)."""
cfg = Config(data_dir=tmp_path / "data")
cfg.data_dir.mkdir(parents=True)
cfg.games_path.write_text(json.dumps({"1": _entry("Etherfields", 11.8, 11.8, 2.9)}))
save_furniture(
cfg,
[
{
"name": "Tower",
"openings": [
{
"id": "tower-e1",
"label": "E1",
"zone": "",
"width_in": 23.25,
"height_in": 10.0,
"depth_in": 9.5,
}
],
}
],
)
csv_path = tmp_path / "plan.csv"
csv_path.write_text("name,opening\nEtherfields,tower-e1\n")
result = import_assignments(cfg, csv_path)
assert result["assigned"] == 0
assert "acknowledge column" in result["rejects"][0]
csv_path.write_text("name,opening,acknowledge\nEtherfields,tower-e1,yes\n")
result = import_assignments(cfg, csv_path)
assert result["assigned"] == 1
locations = load_locations(cfg)
assert locations["1"]["acknowledged"] is True
# hand-set a lane, re-import WITHOUT the column: same opening, so
# the note, lane, and acknowledgment all survive
locations["1"]["lane"] = "standing"
locations["1"]["note"] = "top row"
save_locations(cfg, locations)
csv_path.write_text("name,opening\nEtherfields,tower-e1\n")
result = import_assignments(cfg, csv_path)
assert result["assigned"] == 1
record = load_locations(cfg)["1"]
assert record == {
"opening_id": "tower-e1",
"note": "top row",
"lane": "standing",
"acknowledged": True,
}