feat: add Phase Z override CLI and trace support
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -270,6 +270,33 @@ def lookup_v4_match(v4: dict, section_id: str) -> Optional[V4Match]:
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def lookup_v4_all_judgments(v4: dict, section_id: str) -> list[V4Match]:
|
||||||
|
"""V4 raw 32 entry 그대로 반환 — reject 포함, max_n filter 없음.
|
||||||
|
|
||||||
|
Step 7-A axis 보강 (사용자 lock 2026-05-08) — 사용자 UI 가 모든 frame 의
|
||||||
|
png 를 보여줄 수 있도록 reject 까지 trace. lookup_v4_candidates 는 변경 없음
|
||||||
|
(backward compat — non-reject + max_n 만 반환).
|
||||||
|
|
||||||
|
Returns :
|
||||||
|
list[V4Match] — 0~32 길이. raw judgments_full32 순서 (= V4 score desc) 보존.
|
||||||
|
"""
|
||||||
|
sec = v4.get("mdx_sections", {}).get(section_id)
|
||||||
|
if not sec:
|
||||||
|
return []
|
||||||
|
judgments = sec.get("judgments_full32", [])
|
||||||
|
out: list[V4Match] = []
|
||||||
|
for j in judgments:
|
||||||
|
out.append(V4Match(
|
||||||
|
section_id=section_id,
|
||||||
|
frame_id=str(j["frame_id"]),
|
||||||
|
frame_number=int(j["frame_number"]),
|
||||||
|
template_id=j["template_id"],
|
||||||
|
confidence=float(j["confidence"]),
|
||||||
|
label=j["label"],
|
||||||
|
))
|
||||||
|
return out
|
||||||
|
|
||||||
|
|
||||||
def lookup_v4_candidates(
|
def lookup_v4_candidates(
|
||||||
v4: dict, section_id: str, max_n: int = 6
|
v4: dict, section_id: str, max_n: int = 6
|
||||||
) -> list[V4Match]:
|
) -> list[V4Match]:
|
||||||
@@ -412,15 +439,67 @@ def compute_zone_layout(zones_data: list[dict],
|
|||||||
|
|
||||||
|
|
||||||
def build_layout_css(layout_preset: str, zones_data: list[dict],
|
def build_layout_css(layout_preset: str, zones_data: list[dict],
|
||||||
gap: int = GRID_GAP) -> dict:
|
gap: int = GRID_GAP,
|
||||||
|
override_zone_geometries: Optional[dict[str, dict]] = None) -> dict:
|
||||||
"""Composition v0 layout preset → CSS grid 문자열.
|
"""Composition v0 layout preset → CSS grid 문자열.
|
||||||
|
|
||||||
horizontal-2 (= old type-b, 2-zone vertical stack) 만 dynamic heights 유지
|
horizontal-2 (= old type-b, 2-zone vertical stack) 만 dynamic heights 유지
|
||||||
(MDX 03 회귀 보존 — content_weight 기반). 다른 preset 은 fr default.
|
(MDX 03 회귀 보존 — content_weight 기반). 다른 preset 은 fr default.
|
||||||
향후 cardinality_fit / density_score axis 가 score_candidate 에 들어가면
|
|
||||||
cols/rows 도 dynamic 으로 확장 가능.
|
Step D-ext (사용자 lock 2026-05-08) — override_zone_geometries (zone_id → {x,y,w,h}
|
||||||
|
slide-body 내부 0~1) 가 들어오면 그 비율로 layout_css 강제. horizontal-2 / vertical-2
|
||||||
|
만 처리. 다른 preset 은 일단 무시 + warning. 비율 합 != 1 이면 normalize.
|
||||||
"""
|
"""
|
||||||
preset = LAYOUT_PRESETS[layout_preset]
|
preset = LAYOUT_PRESETS[layout_preset]
|
||||||
|
positions = preset["positions"]
|
||||||
|
|
||||||
|
# ── Step D-ext : user override 처리 ──
|
||||||
|
if override_zone_geometries:
|
||||||
|
if layout_preset == "horizontal-2":
|
||||||
|
# heights_px override — zone 의 h 비율로 SLIDE_BODY_HEIGHT 분배.
|
||||||
|
ratios = []
|
||||||
|
for pos in positions:
|
||||||
|
geom = override_zone_geometries.get(pos)
|
||||||
|
ratios.append(float(geom["h"]) if geom else 0.0)
|
||||||
|
total = sum(ratios)
|
||||||
|
if total > 0:
|
||||||
|
heights_px = [int(round(r / total * SLIDE_BODY_HEIGHT)) for r in ratios]
|
||||||
|
rows = " ".join(f"{h}px" for h in heights_px)
|
||||||
|
return {
|
||||||
|
"areas": preset["css_areas"],
|
||||||
|
"cols": preset["css_cols"],
|
||||||
|
"rows": rows,
|
||||||
|
"heights_px": heights_px,
|
||||||
|
"ratios": [round(r / total, 3) for r in ratios],
|
||||||
|
"computation": "user_override_geometry",
|
||||||
|
"dynamic_rows": True,
|
||||||
|
"raw_zone_layout": {"override_applied": True, "source": override_zone_geometries},
|
||||||
|
}
|
||||||
|
elif layout_preset == "vertical-2":
|
||||||
|
# cols override — zone 의 w 비율로 fr 분배.
|
||||||
|
ratios = []
|
||||||
|
for pos in positions:
|
||||||
|
geom = override_zone_geometries.get(pos)
|
||||||
|
ratios.append(float(geom["w"]) if geom else 0.0)
|
||||||
|
total = sum(ratios)
|
||||||
|
if total > 0:
|
||||||
|
cols = " ".join(f"{round(r / total * 100, 2)}fr" for r in ratios)
|
||||||
|
return {
|
||||||
|
"areas": preset["css_areas"],
|
||||||
|
"cols": cols,
|
||||||
|
"rows": preset["css_rows"],
|
||||||
|
"heights_px": [],
|
||||||
|
"ratios": [round(r / total, 3) for r in ratios],
|
||||||
|
"computation": "user_override_geometry",
|
||||||
|
"dynamic_rows": False,
|
||||||
|
"raw_zone_layout": {"override_applied": True, "source": override_zone_geometries},
|
||||||
|
}
|
||||||
|
else:
|
||||||
|
print(
|
||||||
|
f" [override-warning] zone-geometry override 는 layout '{layout_preset}' 미지원 "
|
||||||
|
f"(현재 horizontal-2 / vertical-2 만). default layout_css 사용.",
|
||||||
|
file=sys.stderr,
|
||||||
|
)
|
||||||
|
|
||||||
if layout_preset == "horizontal-2":
|
if layout_preset == "horizontal-2":
|
||||||
zl = compute_zone_layout(zones_data, gap=gap)
|
zl = compute_zone_layout(zones_data, gap=gap)
|
||||||
@@ -1157,12 +1236,26 @@ def write_debug_json(run_dir: Path, layout_preset: str,
|
|||||||
|
|
||||||
# ─── Main entry ────────────────────────────────────────────────
|
# ─── Main entry ────────────────────────────────────────────────
|
||||||
|
|
||||||
def run_phase_z2_mvp1(mdx_path: Path, run_id: Optional[str] = None) -> Path:
|
def run_phase_z2_mvp1(
|
||||||
|
mdx_path: Path,
|
||||||
|
run_id: Optional[str] = None,
|
||||||
|
*,
|
||||||
|
override_layout: Optional[str] = None,
|
||||||
|
override_frames: Optional[dict[str, str]] = None,
|
||||||
|
override_zone_geometries: Optional[dict[str, dict]] = None,
|
||||||
|
) -> Path:
|
||||||
"""MVP-1.5b entry — single slide + composition planner v0 + 8 preset vocabulary.
|
"""MVP-1.5b entry — single slide + composition planner v0 + 8 preset vocabulary.
|
||||||
|
|
||||||
Pipeline :
|
Pipeline :
|
||||||
parse_mdx → align_sections_to_v4_granularity → plan_composition →
|
parse_mdx → align_sections_to_v4_granularity → plan_composition →
|
||||||
mapper per unit → render slide_base + frame partial → Selenium check
|
mapper per unit → render slide_base + frame partial → Selenium check
|
||||||
|
|
||||||
|
User overrides (Step 7-A axis, 2026-05-08) :
|
||||||
|
override_layout : 자동 결정된 layout_preset 을 사용자 선택값으로 강제 (8 preset 중 하나).
|
||||||
|
override_frames : {unit_id: template_id} — 자동 결정된 frame template 을 사용자 선택값
|
||||||
|
으로 강제. unit_id = "+".join(source_section_ids) (e.g., "03-1"
|
||||||
|
또는 "03-1+03-2"). 매칭 unit 의 v4_candidates 에 있는 entry 면
|
||||||
|
그 entry 의 score / label 도 함께 갱신. 없으면 template_id 만 변경.
|
||||||
"""
|
"""
|
||||||
mdx_path = Path(mdx_path)
|
mdx_path = Path(mdx_path)
|
||||||
if run_id is None:
|
if run_id is None:
|
||||||
@@ -1321,6 +1414,25 @@ def run_phase_z2_mvp1(mdx_path: Path, run_id: Optional[str] = None) -> Path:
|
|||||||
v4_candidates_lookup_fn=candidates_lookup_fn,
|
v4_candidates_lookup_fn=candidates_lookup_fn,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# ── Step 7-A axis : layout override ──
|
||||||
|
# 사용자가 LayoutPanel 에서 다른 preset 을 선택했을 때 자동 결정값을 강제 변경.
|
||||||
|
# 길이 mismatch (positions count vs unit count) 는 zone loop 의 fallback (zone_{i})
|
||||||
|
# 으로 처리됨. 알 수 없는 preset 이면 ValueError.
|
||||||
|
auto_layout_preset = layout_preset
|
||||||
|
layout_override_applied = False
|
||||||
|
if override_layout is not None and override_layout != layout_preset:
|
||||||
|
if override_layout not in LAYOUT_PRESETS:
|
||||||
|
raise ValueError(
|
||||||
|
f"--override-layout '{override_layout}' is not a known preset. "
|
||||||
|
f"Available: {sorted(LAYOUT_PRESETS.keys())}"
|
||||||
|
)
|
||||||
|
print(
|
||||||
|
f" [override] layout_preset: {layout_preset} → {override_layout}",
|
||||||
|
file=sys.stderr,
|
||||||
|
)
|
||||||
|
layout_preset = override_layout
|
||||||
|
layout_override_applied = True
|
||||||
|
|
||||||
if not units or layout_preset is None:
|
if not units or layout_preset is None:
|
||||||
# composition planner 결과 = 0 units. Sections 가 모두 V4 lookup 실패 또는
|
# composition planner 결과 = 0 units. Sections 가 모두 V4 lookup 실패 또는
|
||||||
# status filter 통과 못 함. error.json 기록 후 abort.
|
# status filter 통과 못 함. error.json 기록 후 abort.
|
||||||
@@ -1404,6 +1516,66 @@ def run_phase_z2_mvp1(mdx_path: Path, run_id: Optional[str] = None) -> Path:
|
|||||||
debug_zones = []
|
debug_zones = []
|
||||||
adapter_needed_units: list[dict] = []
|
adapter_needed_units: list[dict] = []
|
||||||
|
|
||||||
|
# ── Step 7-A axis : frame override ──
|
||||||
|
# {unit_id: template_id} 형식. unit_id 매칭 시 unit.frame_template_id 강제 변경.
|
||||||
|
# v4_candidates 안에서 같은 template_id 를 가진 entry 를 찾으면 frame_id /
|
||||||
|
# frame_number / confidence / label 까지 그 entry 에서 가져와 갱신 — 그래야 step09
|
||||||
|
# artifact 의 메타가 일관됨.
|
||||||
|
# frame contract 가 catalog 에 등록 안 된 template_id 면 skip + warning —
|
||||||
|
# crash 방지 (V4 score 는 매겨지지만 catalog partial 은 없는 후보 존재).
|
||||||
|
frame_overrides_applied: list[dict] = []
|
||||||
|
frame_overrides_skipped: list[dict] = []
|
||||||
|
if override_frames:
|
||||||
|
for unit in units:
|
||||||
|
unit_id = "+".join(unit.source_section_ids)
|
||||||
|
if unit_id not in override_frames:
|
||||||
|
continue
|
||||||
|
new_tid = override_frames[unit_id]
|
||||||
|
old_tid = unit.frame_template_id
|
||||||
|
if new_tid == old_tid:
|
||||||
|
continue
|
||||||
|
# catalog contract 존재 확인 — 없으면 override 거부.
|
||||||
|
new_contract = get_contract(new_tid)
|
||||||
|
if new_contract is None:
|
||||||
|
frame_overrides_skipped.append({
|
||||||
|
"unit_id": unit_id,
|
||||||
|
"from": old_tid,
|
||||||
|
"to": new_tid,
|
||||||
|
"reason": "no_frame_contract_in_catalog",
|
||||||
|
})
|
||||||
|
print(
|
||||||
|
f" [override-skip] unit {unit_id}: '{new_tid}' has no entry in "
|
||||||
|
f"frame_contracts catalog — keeping {old_tid}",
|
||||||
|
file=sys.stderr,
|
||||||
|
)
|
||||||
|
continue
|
||||||
|
match = None
|
||||||
|
for cand in (unit.v4_candidates or []):
|
||||||
|
if getattr(cand, "template_id", None) == new_tid:
|
||||||
|
match = cand
|
||||||
|
break
|
||||||
|
if match is not None:
|
||||||
|
unit.frame_template_id = match.template_id
|
||||||
|
unit.frame_id = match.frame_id
|
||||||
|
unit.frame_number = match.frame_number
|
||||||
|
unit.confidence = match.confidence
|
||||||
|
unit.label = match.label
|
||||||
|
meta_source = "v4_candidates"
|
||||||
|
else:
|
||||||
|
unit.frame_template_id = new_tid
|
||||||
|
meta_source = "raw_template_id_only"
|
||||||
|
frame_overrides_applied.append({
|
||||||
|
"unit_id": unit_id,
|
||||||
|
"from": old_tid,
|
||||||
|
"to": new_tid,
|
||||||
|
"meta_source": meta_source,
|
||||||
|
})
|
||||||
|
print(
|
||||||
|
f" [override] unit {unit_id} frame: {old_tid} → {new_tid} "
|
||||||
|
f"({meta_source})",
|
||||||
|
file=sys.stderr,
|
||||||
|
)
|
||||||
|
|
||||||
for i, unit in enumerate(units):
|
for i, unit in enumerate(units):
|
||||||
position = positions[i] if i < len(positions) else f"zone_{i}"
|
position = positions[i] if i < len(positions) else f"zone_{i}"
|
||||||
synth_section = MdxSection(
|
synth_section = MdxSection(
|
||||||
@@ -1749,8 +1921,11 @@ def run_phase_z2_mvp1(mdx_path: Path, run_id: Optional[str] = None) -> Path:
|
|||||||
note="map_with_contract 결과 — actual slot_payload 값 그대로 (key 만 X).",
|
note="map_with_contract 결과 — actual slot_payload 값 그대로 (key 만 X).",
|
||||||
)
|
)
|
||||||
|
|
||||||
# 6. Build layout CSS — horizontal-2 = dynamic heights (regression preserve), 그 외 = fr default
|
# 6. Build layout CSS — horizontal-2 = dynamic heights (regression preserve), 그 외 = fr default.
|
||||||
layout_css = build_layout_css(layout_preset, zones_data)
|
# Step D-ext : override_zone_geometries 가 들어오면 layout_css 강제.
|
||||||
|
layout_css = build_layout_css(
|
||||||
|
layout_preset, zones_data, override_zone_geometries=override_zone_geometries
|
||||||
|
)
|
||||||
if layout_css["dynamic_rows"]:
|
if layout_css["dynamic_rows"]:
|
||||||
for dz, h, r in zip(debug_zones, layout_css["heights_px"], layout_css["ratios"]):
|
for dz, h, r in zip(debug_zones, layout_css["heights_px"], layout_css["ratios"]):
|
||||||
dz["height_px"] = h
|
dz["height_px"] = h
|
||||||
@@ -1771,6 +1946,9 @@ def run_phase_z2_mvp1(mdx_path: Path, run_id: Optional[str] = None) -> Path:
|
|||||||
"zones_count": len(zones_data),
|
"zones_count": len(zones_data),
|
||||||
"unit_count": len(units),
|
"unit_count": len(units),
|
||||||
"layout_candidates": layout_candidates_list,
|
"layout_candidates": layout_candidates_list,
|
||||||
|
# Step 7-A axis : user override trace
|
||||||
|
"layout_override_applied": layout_override_applied,
|
||||||
|
"auto_layout_preset": auto_layout_preset,
|
||||||
},
|
},
|
||||||
step_status="partial",
|
step_status="partial",
|
||||||
pipeline_path_connected=True,
|
pipeline_path_connected=True,
|
||||||
@@ -2059,6 +2237,12 @@ def run_phase_z2_mvp1(mdx_path: Path, run_id: Optional[str] = None) -> Path:
|
|||||||
unit.v4_candidates[0].template_id if has_v4 else None
|
unit.v4_candidates[0].template_id if has_v4 else None
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Step 7-A axis 보강 — reject 포함 모든 V4 judgments (frontend UI 가
|
||||||
|
# 모든 frame 의 png 를 카드로 보여주기 위함).
|
||||||
|
# unit_id = source_section_ids join. parent_merged 는 첫 section 의
|
||||||
|
# judgments 사용 (parent V4 entry 가 그 section 에 있으므로).
|
||||||
|
v4_all_for_unit = lookup_v4_all_judgments(v4, unit.source_section_ids[0])
|
||||||
|
|
||||||
# application_candidates : V4 후보 zip 으로 application_mode 변환
|
# application_candidates : V4 후보 zip 으로 application_mode 변환
|
||||||
app_candidates = []
|
app_candidates = []
|
||||||
for c in unit.v4_candidates:
|
for c in unit.v4_candidates:
|
||||||
@@ -2094,6 +2278,23 @@ def run_phase_z2_mvp1(mdx_path: Path, run_id: Optional[str] = None) -> Path:
|
|||||||
}
|
}
|
||||||
for c in unit.v4_candidates
|
for c in unit.v4_candidates
|
||||||
],
|
],
|
||||||
|
# Step 7-A axis 보강 (사용자 lock 2026-05-08) — frontend UI 가 reject
|
||||||
|
# 포함 모든 V4 후보를 시각 차별 (회색) 로 보여줄 수 있도록 trace.
|
||||||
|
# length = 0~32. label 별 count : v4_candidates 는 non-reject only,
|
||||||
|
# v4_all_judgments 는 reject 포함.
|
||||||
|
# catalog_registered = frame_contracts.yaml 에 contract 있는지 여부.
|
||||||
|
# false 면 사용자가 override 시도해도 Step 7-A 가 skip (render path 미연결).
|
||||||
|
"v4_all_judgments": [
|
||||||
|
{
|
||||||
|
"template_id": c.template_id,
|
||||||
|
"frame_id": c.frame_id,
|
||||||
|
"frame_number": c.frame_number,
|
||||||
|
"confidence": c.confidence,
|
||||||
|
"label": c.label,
|
||||||
|
"catalog_registered": get_contract(c.template_id) is not None,
|
||||||
|
}
|
||||||
|
for c in v4_all_for_unit
|
||||||
|
],
|
||||||
"application_candidates": app_candidates,
|
"application_candidates": app_candidates,
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -2109,6 +2310,9 @@ def run_phase_z2_mvp1(mdx_path: Path, run_id: Optional[str] = None) -> Path:
|
|||||||
"candidate_status_summary": {
|
"candidate_status_summary": {
|
||||||
"units_with_no_v4_candidate": units_with_no_v4,
|
"units_with_no_v4_candidate": units_with_no_v4,
|
||||||
},
|
},
|
||||||
|
# Step 7-A axis : user override trace
|
||||||
|
"frame_overrides_applied": frame_overrides_applied,
|
||||||
|
"frame_overrides_skipped": frame_overrides_skipped,
|
||||||
"v0_lock_note": (
|
"v0_lock_note": (
|
||||||
"Step 9 v0 passive (사용자 lock 2026-05-08). "
|
"Step 9 v0 passive (사용자 lock 2026-05-08). "
|
||||||
"Step 6 default 그대로 사용 — runtime 결과 byte-동일. "
|
"Step 6 default 그대로 사용 — runtime 결과 byte-동일. "
|
||||||
@@ -2130,7 +2334,9 @@ def run_phase_z2_mvp1(mdx_path: Path, run_id: Optional[str] = None) -> Path:
|
|||||||
"변환을 side-by-side 로 기록. v0 invariant 5 가지 (status.md §4) 만족. "
|
"변환을 side-by-side 로 기록. v0 invariant 5 가지 (status.md §4) 만족. "
|
||||||
"Step 6 의 default 결정 그대로 (current_default_candidate). "
|
"Step 6 의 default 결정 그대로 (current_default_candidate). "
|
||||||
"auto decision 은 Step 9 v1 (별 axis). region/display 후보는 Step 8-conn "
|
"auto decision 은 Step 9 v1 (별 axis). region/display 후보는 Step 8-conn "
|
||||||
"의 placeholder signal 종속 (Step 3/4 부재)."
|
"의 placeholder signal 종속 (Step 3/4 부재). "
|
||||||
|
"Step 7-A axis (2026-05-08): frame_overrides_applied 가 사용자 LayoutPanel/"
|
||||||
|
"FramePanel 선택값을 강제 적용한 trace."
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -2548,9 +2754,78 @@ def run_phase_z2_mvp1(mdx_path: Path, run_id: Optional[str] = None) -> Path:
|
|||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
if len(sys.argv) < 2:
|
import argparse
|
||||||
print("Usage: python phase_z2_pipeline.py <mdx_path> [run_id]", file=sys.stderr)
|
|
||||||
|
parser = argparse.ArgumentParser(
|
||||||
|
prog="python -m src.phase_z2_pipeline",
|
||||||
|
description="Phase Z-2 MVP-1.5b pipeline (MDX → 1 slide HTML).",
|
||||||
|
)
|
||||||
|
parser.add_argument("mdx_path", type=Path, help="MDX 파일 경로")
|
||||||
|
parser.add_argument(
|
||||||
|
"run_id", nargs="?", default=None,
|
||||||
|
help="run_id (출력 디렉토리 이름). 없으면 자동 생성 (basename + timestamp).",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--override-layout", dest="override_layout", default=None,
|
||||||
|
metavar="PRESET",
|
||||||
|
help=(
|
||||||
|
"layout_preset 강제 (8 preset 중 하나 — single, horizontal-2, vertical-2, "
|
||||||
|
"top-1-bottom-2, top-2-bottom-1, left-1-right-2, left-2-right-1, grid-2x2). "
|
||||||
|
"없으면 자동 결정 (count-based v0)."
|
||||||
|
),
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--override-frame", dest="override_frames", action="append", default=[],
|
||||||
|
metavar="UNIT_ID=TEMPLATE_ID",
|
||||||
|
help=(
|
||||||
|
"unit_id 의 frame template 강제 변경. UNIT_ID 는 \"+\".join(source_section_ids) "
|
||||||
|
"(e.g., 03-1 또는 03-1+03-2). multiple 가능: --override-frame 03-1=foo "
|
||||||
|
"--override-frame 03-2=bar"
|
||||||
|
),
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--override-zone-geometry", dest="override_zone_geometries", action="append", default=[],
|
||||||
|
metavar="ZONE_ID=X,Y,W,H",
|
||||||
|
help=(
|
||||||
|
"zone position (top/bottom/left/right/...) 의 slide-body 내부 비율 (0~1) "
|
||||||
|
"강제. horizontal-2 / vertical-2 만 지원. multiple 가능: "
|
||||||
|
"--override-zone-geometry top=0,0,1,0.3 --override-zone-geometry bottom=0,0.3,1,0.7"
|
||||||
|
),
|
||||||
|
)
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
overrides_frames: dict[str, str] = {}
|
||||||
|
for ov in args.override_frames:
|
||||||
|
if "=" not in ov:
|
||||||
|
print(
|
||||||
|
f"[error] --override-frame must be UNIT_ID=TEMPLATE_ID, got: '{ov}'",
|
||||||
|
file=sys.stderr,
|
||||||
|
)
|
||||||
sys.exit(2)
|
sys.exit(2)
|
||||||
mdx = Path(sys.argv[1])
|
k, v = ov.split("=", 1)
|
||||||
rid = sys.argv[2] if len(sys.argv) > 2 else None
|
overrides_frames[k.strip()] = v.strip()
|
||||||
run_phase_z2_mvp1(mdx, rid)
|
|
||||||
|
overrides_geoms: dict[str, dict] = {}
|
||||||
|
for ov in args.override_zone_geometries:
|
||||||
|
if "=" not in ov:
|
||||||
|
print(f"[error] --override-zone-geometry must be ZONE_ID=X,Y,W,H, got: '{ov}'", file=sys.stderr)
|
||||||
|
sys.exit(2)
|
||||||
|
zid, vals = ov.split("=", 1)
|
||||||
|
parts = vals.split(",")
|
||||||
|
if len(parts) != 4:
|
||||||
|
print(f"[error] --override-zone-geometry expects 4 floats X,Y,W,H, got: '{vals}'", file=sys.stderr)
|
||||||
|
sys.exit(2)
|
||||||
|
try:
|
||||||
|
x, y, w, h = (float(p) for p in parts)
|
||||||
|
except ValueError:
|
||||||
|
print(f"[error] --override-zone-geometry floats parse fail: '{vals}'", file=sys.stderr)
|
||||||
|
sys.exit(2)
|
||||||
|
overrides_geoms[zid.strip()] = {"x": x, "y": y, "w": w, "h": h}
|
||||||
|
|
||||||
|
run_phase_z2_mvp1(
|
||||||
|
args.mdx_path,
|
||||||
|
args.run_id,
|
||||||
|
override_layout=args.override_layout,
|
||||||
|
override_frames=overrides_frames or None,
|
||||||
|
override_zone_geometries=overrides_geoms or None,
|
||||||
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user