S5-5: centralized part-box synthesis (band-y x content-margins)

Add synthesize_part_box() as the single authoritative S5 part-box projection
(T3 swap point): content-margin x-extent x part-band y-extent, BOTTOMLEFT
coords; label_box retained as a separate anchor. build() attaches box per part.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
This commit is contained in:
2026-06-07 20:38:25 +01:00
co-authored by Claude Opus 4.8
parent 71ddceb19e
commit 621d283ceb
3 changed files with 95 additions and 5 deletions
+37 -4
View File
@@ -44,6 +44,34 @@ def _furn_kind(it):
return "chrome_text"
def synthesize_part_box(part_band, content_x_band):
"""Return the one authoritative S5 part-box projection.
Parts remain boxes in S5, but the box is a projection rather than intrinsic
geometry: document content margins provide the x-extent and the part band
provides y. The band end is already bounded by the next part in bands.py;
the original label box remains a separate anchor for rendering/review.
Coordinates stay in the first-pass PDF-point BOTTOMLEFT bbox shape.
"""
if not content_x_band:
return None
try:
x_left = content_x_band["x_left"]
x_right = content_x_band["x_right"]
y_start = part_band["y_start"]
y_end = part_band["y_end"]
except KeyError:
return None
return {
"l": round(x_left, 1),
"t": round(y_start, 1),
"r": round(x_right, 1),
"b": round(y_end, 1),
"coord_origin": "BOTTOMLEFT",
}
def build(structured, bands, furniture, pdf=None, page_roles=None):
page_roles = page_roles or {}
part_bbox = {p["label"]: p.get("bbox")
@@ -124,10 +152,15 @@ def build(structured, bands, furniture, pdf=None, page_roles=None):
main = [{"question": m["question"], "y_start": m["y_start"], "y_end": m["y_end"],
"is_start": m.get("is_start", True),
"source": "auto", "confirmed": False} for m in pb["main"]]
part = [{"label": p["label"], "question": p["question"],
"y_start": p["y_start"], "y_end": p["y_end"],
"label_box": part_bbox.get(p["label"]), # app may render a box instead of lines
"source": "auto", "confirmed": False} for p in pb["part"]]
part = []
for p in pb["part"]:
part.append({
"label": p["label"], "question": p["question"],
"y_start": p["y_start"], "y_end": p["y_end"],
"label_box": part_bbox.get(p["label"]), # anchor, not the part extent
"box": synthesize_part_box(p, xband),
"source": "auto", "confirmed": False,
})
pr = page_roles.get(pgs) or page_roles.get(pg) or {}
pages[pgs] = {
"role": pr.get("role", "question"),