untracked files on main: dceb101 feat(#63): IMP-34 R1 donor capacity measured bound (u1+u2)

This commit is contained in:
2026-05-21 22:07:41 +09:00
commit 8f085a28d3
3220 changed files with 985495 additions and 0 deletions
+48
View File
@@ -0,0 +1,48 @@
Executing unit: u2
[Claude #4] Stage 3 Round #2 — IMP-09 PR 2 u2
### unit_executed
- **id**: `u2`
- **summary**: Add `_aggregate_axis_zone` helper (combine zones in same row/col track into synthetic zone for PR 1 solvers)
### files_changed
- `src/phase_z2_pipeline.py` (+44 / -0)
### diff_summary
- Inserted directly after the `_TWO_D_TOPOLOGIES = frozenset(...)` line that u1 added, and before the existing `# Layout preset → zone position 순서 ...` comment (HEAD line 1152 → working-tree line 1196). u1 surface and PR 1 helpers untouched.
- Helper signature: `_aggregate_axis_zone(positions: list[str], zones_data: list[dict]) -> dict`.
- Aggregation rules (verified against PR 1 solver contracts):
- `content_weight.score` = **SUM** of member scores (more content in a track = larger share of available space — matches the proportional distribution in both `compute_zone_layout` row solver and `compute_zone_layout_cols` col solver).
- `min_height_px` = **MAX** across members (row track must accommodate the tallest side-by-side member min; the cols solver does not consume this field but the value is harmless on that path).
- `position` / `template_id` = `"+".join(...)` of member ids — surfaces composite ("bottom-left+bottom-right", "tB+tC") in Step 8 trace `raw_zone_layout` for explainability.
- Missing-position guard: raises `ValueError` listing the absent positions and the actual position keys present in `zones_data` — composition planning must declare every zone referenced by `TOPOLOGY_AXIS_MAP[topology][axis]`.
- Default fallback: members without `min_height_px` use `DEFAULT_ZONE_MIN_HEIGHT_PX` (module constant at line 120 — same fallback `compute_zone_layout` itself uses at line 791).
- No dispatch wiring added. `build_layout_css` normal path still routes only `topology in {"rows","cols"}` to dynamic sinks; all 2-D topologies still fall through to `_build_fr_default` (`computation == "fr_default_from_preset"`, `dynamic_rows == False`, `dynamic_cols == False`). u3 will add `_build_2d_dynamic` (the consumer) and u6 will wire dispatch.
### evidence
- `git log -1 --format=%H``201099e53b0048f3040e17ee9b0394ba9ad6e940` (Stage 2 baseline preserved; no commit/push this turn per Stage 3 contract).
- `git diff --stat src/phase_z2_pipeline.py``1 file changed, 80 insertions(+)` (cumulative across u1 + u2; u2-only delta = 80 36 = **44 lines** ≤ 50-line gate ✓).
- `git diff --numstat``80 0 src/phase_z2_pipeline.py` (single file ≤ 3-file gate ✓; 0 deletions = pure insertion).
- `git diff --check src/phase_z2_pipeline.py` → no whitespace errors.
- `git diff --name-only HEAD -- src/space_allocator.py` → empty (Kei legacy invariant preserved ✓).
- `python -c "import ast; ast.parse(...)"``PARSE OK`.
- Behavior smoke (import-load with module on sys.path):
- **single-position track** `_aggregate_axis_zone(['top'], zones)``{'position': 'top', 'template_id': 'tA', 'content_weight': {'score': 4.0}, 'min_height_px': 200}` ✓ (verbatim passthrough of member zone shape).
- **multi-position track** `_aggregate_axis_zone(['bottom-left','bottom-right'], zones)``{'position': 'bottom-left+bottom-right', 'template_id': 'tB+tC', 'content_weight': {'score': 5.0}, 'min_height_px': 250}` ✓ (sum=2+3=5; max=max(150,250)=250).
- **default min_height fallback** when member lacks `min_height_px` → returns `DEFAULT_ZONE_MIN_HEIGHT_PX` (100) when member-with-min < default ✓.
- **missing-position guard** `_aggregate_axis_zone(['nope'], zones)``ValueError: _aggregate_axis_zone: positions ['nope'] absent from zones_data (have ...)` ✓.
- PR 1 solver compatibility (T topology with `TOPOLOGY_AXIS_MAP["T"]` from u1):
- row synthesis: `[_aggregate_axis_zone(g, zones) for g in TOPOLOGY_AXIS_MAP["T"]["rows"]]``compute_zone_layout(...)``heights_px = [254, 317]`, `sum = 571 == SLIDE_BODY_HEIGHT - GRID_GAP (585 - 14 = 571)` ✓.
- col synthesis: `[_aggregate_axis_zone(g, zones) for g in TOPOLOGY_AXIS_MAP["T"]["cols"]]``compute_zone_layout_cols(...)``widths_px = [466, 700]`, `sum = 1166 == SLIDE_BODY_WIDTH - GRID_GAP (1180 - 14 = 1166)` ✓.
- Scope-lock no-creep check:
- `hasattr(p, '_build_2d_dynamic')``False` (u3) ✓
- `hasattr(p, '_derive_axis_tracks_from_override')``False` (u4) ✓
- `hasattr(p, '_override_to_grid_tracks')``False` (u5) ✓
- `build_layout_css('top-1-bottom-2', ...)["computation"]``'fr_default_from_preset'` (dispatch still falls through; u6 will route) ✓.
### remaining_units
`u3, u4, u5, u6, u7, u8, u9, u10, u11, u12, u13, u14, u15, u16, u17, u18, u19, u20, u21, u22, u23, u24, u25, u26`
### follow_up_issue_candidates
None this turn. No new axis surfaced — `_aggregate_axis_zone` matches the Stage 2 plan and reuses existing PR 1 solver contracts (no min_width_px needed; `compute_zone_layout_cols` is weight-only). Composite position keys for synthetic zones (`"top-left+top-right"` style) flow into `raw_zone_layout` trace explainability; Step 8 `computation` label change to `"2d_dynamic_aggregated"` is scheduled for u3 (sink) — not introduced here.