Add Phase Z B4 source-shape-aware placement
- enable B1/B2/B4 source-shape-aware F13 placement behind env flag - align F13 placement_trace with mapper top_bullets cardinality - preserve canonical render output when flag is off
This commit is contained in:
@@ -62,6 +62,7 @@ class InternalRegion:
|
||||
— B2 v0 에서 kind="frame_match" / frame_id=None /
|
||||
display_strategy="inline_full" 고정.
|
||||
실제 frame 결정은 Step 9 / B4 책임.
|
||||
source_shape_index : positional index from B1 source_shape split (Option 1, optional)
|
||||
"""
|
||||
|
||||
region_id: str
|
||||
@@ -70,6 +71,7 @@ class InternalRegion:
|
||||
ratio_estimate: float
|
||||
content_unit_ids: list[str]
|
||||
frame_match_strategy: dict
|
||||
source_shape_index: Optional[int] = None
|
||||
|
||||
|
||||
@dataclass
|
||||
@@ -146,6 +148,75 @@ _TYPE_ORDER_PRIORITY: dict[str, int] = {
|
||||
}
|
||||
|
||||
|
||||
# ─── Option 1 source_shape-aware planner ─────────────────────────
|
||||
|
||||
|
||||
def _plan_by_source_shape_index(
|
||||
content_objects: list[ContentObject],
|
||||
section_id: str,
|
||||
) -> ZoneRegionPlan:
|
||||
"""source_shape_index 기준 *positional* region grouping (Option 1).
|
||||
|
||||
같은 source_shape_index 끼리 1 region. mapper 의 split_source 와 cardinality align —
|
||||
F13 의 top_bullets 3 개 → 3 region 으로 mapper pillar_1/2/3 와 1:1 positional.
|
||||
"""
|
||||
groups: dict[int, list[ContentObject]] = {}
|
||||
for obj in content_objects:
|
||||
if obj.source_shape_index is None:
|
||||
continue
|
||||
groups.setdefault(obj.source_shape_index, []).append(obj)
|
||||
|
||||
sorted_indices = sorted(groups.keys())
|
||||
|
||||
# size proxy + ratio (positional region 내부 size_estimate 합산)
|
||||
index_sizes: dict[int, float] = {idx: sum(_size_proxy(o) for o in groups[idx]) for idx in sorted_indices}
|
||||
total_size = sum(index_sizes.values())
|
||||
if total_size <= 0:
|
||||
equal_share = 1.0 / max(len(sorted_indices), 1)
|
||||
index_sizes = {idx: equal_share for idx in sorted_indices}
|
||||
total_size = sum(index_sizes.values()) or 1.0
|
||||
|
||||
regions: list[InternalRegion] = []
|
||||
for ord_idx, sidx in enumerate(sorted_indices, start=1):
|
||||
objs = groups[sidx]
|
||||
# role / content_type : group 내 첫 obj 의 type 기반 (Option 1 pilot = text_block 동질)
|
||||
primary_obj = objs[0]
|
||||
ctype = primary_obj.type
|
||||
regions.append(
|
||||
InternalRegion(
|
||||
region_id=f"{section_id}.region-{ord_idx}",
|
||||
role=_TYPE_ROLE.get(ctype, "primary"),
|
||||
content_type=ctype,
|
||||
ratio_estimate=round(index_sizes[sidx] / total_size, 4),
|
||||
content_unit_ids=[o.id for o in objs],
|
||||
frame_match_strategy={
|
||||
"kind": "frame_match",
|
||||
"frame_id": None,
|
||||
"display_strategy": "inline_full",
|
||||
},
|
||||
source_shape_index=sidx,
|
||||
)
|
||||
)
|
||||
|
||||
region_count = len(regions)
|
||||
if region_count == 1:
|
||||
layout_type = "region-single"
|
||||
placement = "single"
|
||||
else:
|
||||
layout_type = "region-vertical-stack"
|
||||
placement = "vertical"
|
||||
|
||||
region_order = [r.region_id for r in regions]
|
||||
return ZoneRegionPlan(
|
||||
internal_regions=regions,
|
||||
region_layout=RegionLayout(
|
||||
region_layout_type=layout_type,
|
||||
region_order=region_order,
|
||||
region_placement=placement,
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
# ─── Public entry ────────────────────────────────────────────────
|
||||
|
||||
|
||||
@@ -186,6 +257,11 @@ def plan_internal_regions(
|
||||
if not content_objects:
|
||||
return ZoneRegionPlan()
|
||||
|
||||
# Option 1 source_shape-aware path : ContentObjects 가 source_shape_index 보유 시 *positional*
|
||||
# grouping. 같은 index 끼리 1 region. mapper 의 split_source 와 cardinality align.
|
||||
if any(o.source_shape_index is not None for o in content_objects):
|
||||
return _plan_by_source_shape_index(content_objects, section_id)
|
||||
|
||||
# 1. type 별 grouping
|
||||
groups = _group_by_type_preserving_order(content_objects)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user