diff --git a/ISSUE_DRAFTS_2026-07-02.md b/ISSUE_DRAFTS_2026-07-02.md
new file mode 100644
index 0000000..b6b1eeb
--- /dev/null
+++ b/ISSUE_DRAFTS_2026-07-02.md
@@ -0,0 +1,209 @@
+# 이슈 초안 — 2026-07-02 스냅샷(b836e79) 전수 검토 결과
+
+> 검토 범위: PLAN/PROGRESS/emergency.md 등 계획 문서, tests/matching 진단 리포트,
+> b836e79 커밋 코드 diff, figma_to_html_agent(미통합) 전체.
+> 목표: ① Figma→HTML 변환이 정확히 되고 ② 그 HTML에 콘텐츠 내용·의미가 반영되어 정리되는 상태.
+
+---
+
+## 그룹 A — Figma→HTML 변환 정확도
+
+### A-1. figma_to_html_agent를 메인 repo로 통합 (유실 위험 제거)
+
+**배경**
+- Figma→HTML 변환기 본체(MCP 추출 + 수학 변환 + 검증 프로세스 문서 PROCESS.md/MATH.md/RULES.md)가 `Front_test_v515/figma_to_html_agent/`에만 존재하며, 이 디렉토리는 gitignore 처리되어 **어디에도 백업이 없음**.
+- 메인 repo에는 사본 스크립트(`scripts/figma_to_html.py`)와 메타데이터만 있음.
+- 32개 frame 변환 산출물(blocks/{frame_id}/index.html, texts.md, flat.md)도 같은 위치에 미보존 상태.
+
+**작업**
+1. `figma_to_html_agent/`를 repo 최상위로 이동 (문서 + blocks + scripts).
+2. `scripts/figma_to_html.py` 중복 제거, gradient_math.py import 경로 정리.
+3. MCP(Figma Desktop Dev Mode) 의존성·.mcp.json 샘플 문서화.
+4. .gitignore에서 해당 경로 제외 확인 후 커밋.
+
+**완료 조건**: 변환기 전체가 origin/main에 push되어 로컬 디스크 유실 시에도 복구 가능.
+
+---
+
+### A-2. 잔여 Figma frame 프로모션 완료 (8/32 → 32/32)
+
+**배경**
+- Figma frame 32개 중 **promoted(본체 편입) 8개**, static(1:1 변환만) 6개+, 나머지는 staged 미작성.
+- placement_planner에 "contract-only 템플릿 skip" 로직(`b4_partial_missing_skip`, IMP-95 u6)이 있다는 것은 contract는 있으나 partial HTML이 없는 frame이 존재한다는 뜻 — 매칭이 맞아도 렌더링 불가.
+
+**작업**
+1. frame_contracts.yaml 기준 32개 frame의 상태 매트릭스 작성 (contract 유무 / partial HTML 유무 / promoted 여부).
+2. static 6개(process-products-2col, scene-with-numbered-list-4, framework-3section-label-body, bordered-3paragraphs, cards-3-category-bim-diagram, dx-banner-3perspective)부터 staged → promoted 진행.
+3. partial 없는 contract-only frame 목록을 문서화하고 순차 해소.
+
+**완료 조건**: V4가 선택한 어떤 frame도 "partial 없음"으로 skip되지 않음.
+
+---
+
+### A-3. Figma 원본 추적성(provenance) 강화
+
+**배경**
+- promoted 블록은 Jinja2 파라미터화 과정에서 Figma 원본 좌표·구조 추적성이 손실됨. 현재 provenance 테스트(test_imp49_partial_figma_provenance.py)는 **색상 literal만** 비교.
+- `get_screenshot` 결과(Figma 원본 PNG)가 캐시되지 않아 사후 육안 비교 기준이 없음.
+
+**작업**
+1. blocks/{frame_id}/에 Figma 원본 스크린샷 캐시 저장 (fetch_figma_screenshots.py 활용).
+2. promoted 블록마다 source frame_id를 frame_contracts.yaml에 정식 필드로 기록.
+3. provenance 테스트를 색상 외 구조(슬롯 수, 주요 영역 배치)까지 확장.
+
+**완료 조건**: 임의의 promoted 블록에서 "원본 Figma frame → HTML 요소" 대응을 문서·테스트로 확인 가능.
+
+---
+
+## 그룹 B — 콘텐츠 의미 → HTML 반영 (매칭 품질)
+
+### B-1. 02-2.2 매칭 실패 교정 — TARGET 4/4 달성
+
+**배경**
+- V4 종합 판정 TARGET 3/4 (75%). 실패 케이스 02-2.2: 정답 Frame 14가 V4 순위 7위, 모든 후보 reject.
+- 근본 원인: Frame 14의 anchor_sets 키워드가 MDX 02-2.2 본문과 거의 안 겹침 (tests/PIPELINE.md:230-232).
+- 문서상 예상 공수 1~2일 — 가장 빠르게 정확도를 올릴 수 있는 지점.
+
+**작업**
+1. Frame 14 anchor_sets 재라벨링 + 도메인 키워드 보강.
+2. holdout(02-2.1 등)으로 과적합 여부 교차 확인.
+
+**완료 조건**: TARGET 4/4, holdout 정확도 하락 없음.
+
+---
+
+### B-2. MDX 콘텐츠 성격·시각 의도 분류를 키워드 사전 → LLM 판정으로 대체
+
+**배경**
+- V3 구조 매칭의 "콘텐츠 성격(12 카테고리)·시각 의도(9 카테고리)"가 키워드 사전 매칭이라 오분류 발생 (예: 01-2가 policy_requirements로 오분류). tests/PROGRESS.md 약점 #2·#3, 예상 공수 3~5일.
+- 일반 표현을 못 잡는 구조적 한계 — 새 MDX가 들어올수록 사전 유지비 증가.
+
+**작업**
+1. 분류 프롬프트 설계 + 기존 4 TARGET/holdout으로 정확도 비교 (사전 vs LLM vs 하이브리드).
+2. 비용 고려해 "사전 1차 + 저신뢰 시 LLM" 하이브리드 우선 검토.
+3. 판정 근거를 pipeline_trace에 기록해 사후 검증 가능하게.
+
+**완료 조건**: 01-2 오분류 해소 + TARGET/holdout 분류 정확도 리포트 갱신.
+
+---
+
+### B-3. V4 스코어링 재조정 — ablation 결과 반영
+
+**배경**
+- ablation으로 확인된 사실: slot 축(가중 0.15)은 frame 선별에 무영향(Top-1 7/7 동일), cardinality는 axis(0.20)+slot.within(0.075)로 같은 신호가 중복 가중됨 (tests/PROGRESS.md 약점 #5·#6, V4_SLOT_ABLATION.md).
+
+**작업**
+1. slot 축을 frame 선별 가중에서 제거하고 label 게이트 역할로만 유지.
+2. cardinality 중복 가중 해소 후 가중치 재정규화.
+3. TARGET/holdout 재실행으로 회귀 없음 확인.
+
+**완료 조건**: 재조정된 가중치로 TARGET ≥ 기존, confidence 분포 문서화.
+
+---
+
+### B-4. 슬롯 의미 매핑 계약(T25) 실제 배선 + 컴포넌트 확장(T23) 커버리지
+
+**배경**
+- "BIM → col_a_label" 같은 편집 슬롯 의미 매핑 부재가 약점 #4로 기록됨.
+- 이번 스냅샷에서 node_slot_mapping.yaml(11 node × 9 slot 매트릭스)이 **정의됐으나 runtime에서 읽는 배선 미확인** (주석: "T25 will wire builders/mappers to validate").
+- component_expansion_registry.yaml은 DxEffect 1개 entry뿐이고 status: planned. persona role label도 "DxEffect" 하드코딩 (src/phase_z2_pipeline.py L281-284).
+- AI schema hints도 three_parallel_requirements 1개 frame만 정의 (L216-241).
+
+**작업**
+1. mapper/builder가 node_slot_mapping.yaml을 실제로 로드·검증하도록 배선 (T25).
+2. DxEffect expansion end-to-end 테스트 후 status: planned 해제.
+3. AI schema hints를 주요 frame(three_persona_benefits, quadrant 등)으로 확장.
+4. component_name 하드코딩 제거.
+
+**완료 조건**: node→slot 매핑 위반이 파이프라인에서 검출되고, DxEffect 외 컴포넌트 1개 이상 추가 등록.
+
+---
+
+### B-5. 표 전체 행 추출 + 이미지 frame 매칭 (약점 #7·#8)
+
+**배경**
+- 4행 표에서 첫 행만 본문으로 추출, 나머지 행 무시 (DECK 정직성 점검에서 발견).
+- 그림/이미지 frame은 본문 텍스트 패턴만 분석해 매칭이 약함.
+
+**작업**
+1. `_extract_markdown_table` 및 관련 파서가 전 행을 atom으로 보존하도록 수정, check_phase_z2_render_text_coverage.py로 검증.
+2. image_ref node type의 매칭 신호 설계 (alt 텍스트, 주변 문맥).
+
+**완료 조건**: 표 포함 MDX의 render_text_coverage에서 missing 행 0.
+
+---
+
+## 그룹 C — 파이프라인 신뢰성 (empty shell 제거·배선 완성)
+
+### C-1. Emergency P2~P5 완수 — empty shell 완전 제거
+
+**배경**
+- mdx 01~05 rescue 세션(emergency.md): root cause 3개(C1 catalog gap all-reject / C2 section key mismatch / C3 V4 source 누락) 식별 후 **P1 진단만 완료, P2~P5 미실행**.
+- 정책은 이미 확정됨: reject → `ai_adaptation_required` status, empty_shell terminal 제거, generic_fallback은 기존 catalog 활용.
+
+**작업** (emergency.md의 priority 순서 그대로)
+1. P2: V4 후보 보존 + 3-status 분류 (auto_renderable / ai_adaptation_required / blocked_runtime).
+2. P2.5: Section key alignment (child → parent pool merge universal policy).
+3. P3: empty_shell terminal 제거 + generic_fallback mandatory. — generic fallback frame(three_parallel_requirements, 1171281190 하드코딩)이 모든 MDX 타입을 커버하는지 검증 포함.
+4. P4: AI fallback 호출 trace 검증.
+5. P5: Frontend candidate panel status surface.
+
+**완료 조건**: mdx 01~05 전부 empty shell 없이 슬라이드 출력.
+
+---
+
+### C-2. V4 evidence 경로(PHASE_Z_B4_V4_EVIDENCE) end-to-end 배선
+
+**배경**
+- `_select_frame_v4_aware()`(placement_planner L180-285)와 selection_trace 필드까지 구현됐으나:
+ - env flag 기본 OFF에 call site에서 v4_candidates 전달 여부 미확인
+ - Step 11 trace 직렬화 wiring은 "u4 out of scope"로 명시적 미구현
+ - `partial_exists()` callback의 실제 호출처 없음
+
+**작업**
+1. plan_placement() 호출부에 v4_candidates 전달 + flag ON 경로 통합 테스트.
+2. Step 11에서 selection_trace JSON 직렬화·응답 포함.
+3. flag ON/OFF SHA parity 회귀 테스트(기존 test_b4_v4_evidence_off_sha_parity.py) 통과 유지.
+
+**완료 조건**: flag ON 실행에서 V4 rank 기반 frame 선택이 trace로 확인되고, OFF 시 기존 출력과 SHA 동일.
+
+---
+
+### C-3. pipeline_trace / ai_trace 백엔드-프론트 계약 검증
+
+**배경**
+- 프론트 PipelineTracePanel(신규 216줄)이 `RunMeta.pipeline_trace`, `ai_trace` 스키마(sections/units/zones, skip_reasons 등)를 기대하지만, **백엔드가 이 JSON을 실제 생성·응답에 포함하는지 미확인**.
+- `ignoreUserOverrides` 플래그, `coverage_state`, `rationale.ai_adaptation_required` 등 신규 필드도 동일.
+
+**작업**
+1. 백엔드 응답과 프론트 타입(designAgentApi.ts) 필드 대조표 작성.
+2. 누락 필드는 백엔드에서 생성 추가, 계약 테스트(스키마 검증) 1본 작성.
+
+**완료 조건**: 실제 run에서 PipelineTracePanel이 전 섹션 상태를 표시.
+
+---
+
+### C-4. SlideCanvas.tsx 한글 주석 인코딩 손상 복구 (소형)
+
+**배경**
+- Front/client/src/components/SlideCanvas.tsx L1~80 부근 한글 주석이 깨진 채 커밋됨 ("?ㅼ젣", "?쒖떆" — CP949↔UTF-8 오변환 패턴). 기능 코드는 정상.
+
+**작업**: 깨진 주석 복원(git 히스토리의 정상 버전 참조) 후 UTF-8로 재저장. 에디터/훅에 인코딩 고정 설정 검토.
+
+**완료 조건**: 파일 전체 UTF-8 정상, diff에 주석 복원만 포함.
+
+---
+
+## 제안 우선순위 (의존 관계 기준)
+
+| 순위 | 이슈 | 이유 |
+|---|---|---|
+| 1 | A-1 (변환기 통합) | 백업 없는 유실 위험 — 즉시 |
+| 2 | C-1 (Emergency P2~P5) | 진행 중 작업, 정책 확정 상태 |
+| 3 | B-1 (02-2.2 교정) | 1~2일로 TARGET 4/4 |
+| 4 | C-2 (V4 evidence 배선) | 이미 절반 구현된 코드 완성 |
+| 5 | C-3 (trace 계약) + C-4 (인코딩) | 프론트 검증 기반 확보 |
+| 6 | B-4 (슬롯 매핑 배선) | "의미 반영" 목표의 핵심 |
+| 7 | A-2 (frame 32개 완성) | 매칭 성공해도 렌더 불가인 갭 해소 |
+| 8 | B-2, B-3 (분류 LLM화, V4 재조정) | 정확도 상한 돌파 |
+| 9 | B-5 (표/이미지), A-3 (추적성) | 커버리지·품질 보강 |
diff --git a/tests/integration/__snapshots__/ai_classifier.json b/tests/integration/__snapshots__/ai_classifier.json
index 17434ea..8223efd 100644
--- a/tests/integration/__snapshots__/ai_classifier.json
+++ b/tests/integration/__snapshots__/ai_classifier.json
@@ -1,73 +1,187 @@
{
- "_doc": "IMP-91 u9 — F3 classifier-only AI axis. Pin observed step12 per-unit classifier label / route_hint / AI-isolation flags + coverage_invariant + step15 fit_classification + step16 router_active + step18 failure_type. Default-OFF AI invariant ([[feedback_ai_isolation_contract]]): ai_called MUST be False for every unit unless AI_FALLBACK_ENABLED is flipped via .env (not via pipeline default). If any unit flips ai_called=True silently, this snapshot fails loudly per [[feedback_demo_env_toggle_policy]].",
- "01": {
- "units": [
- {"source_section_ids": ["01-2"], "label": "use_as_is", "route_hint": "direct_render", "provisional": false, "ai_called": false, "skip_reason": "not_provisional", "apply_status": "no_proposal"},
- {"source_section_ids": ["01-1"], "label": "use_as_is", "route_hint": "direct_render", "provisional": false, "ai_called": false, "skip_reason": "not_provisional", "apply_status": "no_proposal"}
+ "_doc": "IMP-91 u9 — F3 classifier-only AI axis. Pin observed step12 per-unit classifier label / route_hint / AI-isolation flags + coverage_invariant + step15 fit_classification + step16 router_active + step18 failure_type. Default-OFF AI invariant ([[feedback_ai_isolation_contract]]): ai_called MUST be False for every unit unless AI_FALLBACK_ENABLED is flipped via .env (not via pipeline default). If any unit flips ai_called=True silently, this snapshot fails loudly per [[feedback_demo_env_toggle_policy]].",
+ "01": {
+ "units": [
+ {
+ "source_section_ids": [
+ "01-intro"
],
- "coverage_invariant_status": "ok",
- "fit_visual_check_passed": true,
- "fit_classifications_count": 0,
- "fit_categories_seen": [],
- "router_active": false,
- "router_routed_count": 0,
- "router_v4_fallback_used_count": 0,
- "failure_type": "not_attempted"
- },
- "02": {
- "units": [
- {"source_section_ids": ["02-1"], "label": "use_as_is", "route_hint": "direct_render", "provisional": false, "ai_called": false, "skip_reason": "not_provisional", "apply_status": "no_proposal"},
- {"source_section_ids": ["02-2-sub-1", "02-2-sub-2"], "label": "use_as_is", "route_hint": "direct_render", "provisional": true, "ai_called": false, "skip_reason": "route_not_ai_adaptation:direct_render", "apply_status": "no_proposal"}
+ "label": "reject",
+ "route_hint": "ai_adaptation_required",
+ "provisional": true,
+ "ai_called": false,
+ "skip_reason": "router_short_circuit",
+ "apply_status": "no_proposal"
+ },
+ {
+ "source_section_ids": [
+ "01-1"
],
- "coverage_invariant_status": "ok",
- "fit_visual_check_passed": true,
- "fit_classifications_count": 0,
- "fit_categories_seen": [],
- "router_active": false,
- "router_routed_count": 0,
- "router_v4_fallback_used_count": 0,
- "failure_type": "not_attempted"
- },
- "03": {
- "units": [
- {"source_section_ids": ["03-1"], "label": "use_as_is", "route_hint": "direct_render", "provisional": false, "ai_called": false, "skip_reason": "not_provisional", "apply_status": "no_proposal"},
- {"source_section_ids": ["03-2"], "label": "use_as_is", "route_hint": "direct_render", "provisional": false, "ai_called": false, "skip_reason": "not_provisional", "apply_status": "no_proposal"}
+ "label": "use_as_is",
+ "route_hint": "direct_render",
+ "provisional": true,
+ "ai_called": false,
+ "skip_reason": "route_not_ai_adaptation:direct_render",
+ "apply_status": "no_proposal"
+ },
+ {
+ "source_section_ids": [
+ "01-2"
],
- "coverage_invariant_status": "ok",
- "fit_visual_check_passed": true,
- "fit_classifications_count": 0,
- "fit_categories_seen": [],
- "router_active": false,
- "router_routed_count": 0,
- "router_v4_fallback_used_count": 0,
- "failure_type": "not_attempted"
- },
- "04": {
- "units": [
- {"source_section_ids": ["04-2-sub-2"], "label": "light_edit", "route_hint": "deterministic_minor_adjustment", "provisional": false, "ai_called": false, "skip_reason": "not_provisional", "apply_status": "no_proposal"},
- {"source_section_ids": ["04-2-sub-1"], "label": "restructure", "route_hint": "ai_adaptation_required", "provisional": true, "ai_called": false, "skip_reason": "router_short_circuit", "apply_status": "no_proposal"},
- {"source_section_ids": ["04-1"], "label": "reject", "route_hint": "ai_adaptation_required", "provisional": true, "ai_called": false, "skip_reason": "router_short_circuit", "apply_status": "no_proposal"}
+ "label": "use_as_is",
+ "route_hint": "direct_render",
+ "provisional": true,
+ "ai_called": false,
+ "skip_reason": "route_not_ai_adaptation:direct_render",
+ "apply_status": "no_proposal"
+ }
+ ],
+ "coverage_invariant_status": "ok",
+ "fit_visual_check_passed": true,
+ "fit_classifications_count": 0,
+ "fit_categories_seen": [],
+ "router_active": false,
+ "router_routed_count": 0,
+ "router_v4_fallback_used_count": 0,
+ "failure_type": "not_attempted"
+ },
+ "02": {
+ "units": [
+ {
+ "source_section_ids": [
+ "02-1"
],
- "coverage_invariant_status": "ok",
- "fit_visual_check_passed": true,
- "fit_classifications_count": 0,
- "fit_categories_seen": [],
- "router_active": false,
- "router_routed_count": 0,
- "router_v4_fallback_used_count": 0,
- "failure_type": "not_attempted"
- },
- "05": {
- "units": [
- {"source_section_ids": ["05-1", "05-2-sub-1", "05-2-sub-2"], "label": "empty_shell", "route_hint": null, "provisional": true, "ai_called": false, "skip_reason": "route_not_ai_adaptation:None", "apply_status": "no_proposal"}
+ "label": "reject",
+ "route_hint": "ai_adaptation_required",
+ "provisional": true,
+ "ai_called": false,
+ "skip_reason": "router_short_circuit",
+ "apply_status": "no_proposal"
+ },
+ {
+ "source_section_ids": [
+ "02-2"
],
- "coverage_invariant_status": "ok",
- "fit_visual_check_passed": true,
- "fit_classifications_count": 0,
- "fit_categories_seen": [],
- "router_active": false,
- "router_routed_count": 0,
- "router_v4_fallback_used_count": 0,
- "failure_type": "not_attempted"
- }
+ "label": "use_as_is",
+ "route_hint": "direct_render",
+ "provisional": true,
+ "ai_called": false,
+ "skip_reason": "route_not_ai_adaptation:direct_render",
+ "apply_status": "no_proposal"
+ }
+ ],
+ "coverage_invariant_status": "ok",
+ "fit_visual_check_passed": true,
+ "fit_classifications_count": 0,
+ "fit_categories_seen": [],
+ "router_active": false,
+ "router_routed_count": 0,
+ "router_v4_fallback_used_count": 0,
+ "failure_type": "not_attempted"
+ },
+ "03": {
+ "units": [
+ {
+ "source_section_ids": [
+ "03-1"
+ ],
+ "label": "use_as_is",
+ "route_hint": "direct_render",
+ "provisional": true,
+ "ai_called": false,
+ "skip_reason": "route_not_ai_adaptation:direct_render",
+ "apply_status": "no_proposal"
+ },
+ {
+ "source_section_ids": [
+ "03-2"
+ ],
+ "label": "reject",
+ "route_hint": "ai_adaptation_required",
+ "provisional": false,
+ "ai_called": false,
+ "skip_reason": "not_provisional",
+ "apply_status": "no_proposal"
+ }
+ ],
+ "coverage_invariant_status": "ok",
+ "fit_visual_check_passed": true,
+ "fit_classifications_count": 0,
+ "fit_categories_seen": [],
+ "router_active": false,
+ "router_routed_count": 0,
+ "router_v4_fallback_used_count": 0,
+ "failure_type": "not_attempted"
+ },
+ "04": {
+ "units": [
+ {
+ "source_section_ids": [
+ "04-1"
+ ],
+ "label": "reject",
+ "route_hint": "ai_adaptation_required",
+ "provisional": true,
+ "ai_called": false,
+ "skip_reason": "router_short_circuit",
+ "apply_status": "no_proposal"
+ },
+ {
+ "source_section_ids": [
+ "04-2"
+ ],
+ "label": "reject",
+ "route_hint": "ai_adaptation_required",
+ "provisional": true,
+ "ai_called": false,
+ "skip_reason": "router_short_circuit",
+ "apply_status": "no_proposal"
+ }
+ ],
+ "coverage_invariant_status": "ok",
+ "fit_visual_check_passed": false,
+ "fit_classifications_count": 2,
+ "fit_categories_seen": [
+ "minor_overflow",
+ "moderate_overflow"
+ ],
+ "router_active": true,
+ "router_routed_count": 2,
+ "router_v4_fallback_used_count": 0,
+ "failure_type": "no_donor_candidates"
+ },
+ "05": {
+ "units": [
+ {
+ "source_section_ids": [
+ "05-1"
+ ],
+ "label": "light_edit",
+ "route_hint": "deterministic_minor_adjustment",
+ "provisional": true,
+ "ai_called": false,
+ "skip_reason": "route_not_ai_adaptation:deterministic_minor_adjustment",
+ "apply_status": "no_proposal"
+ },
+ {
+ "source_section_ids": [
+ "05-2"
+ ],
+ "label": "reject",
+ "route_hint": "ai_adaptation_required",
+ "provisional": true,
+ "ai_called": false,
+ "skip_reason": "router_short_circuit",
+ "apply_status": "no_proposal"
+ }
+ ],
+ "coverage_invariant_status": "ok",
+ "fit_visual_check_passed": true,
+ "fit_classifications_count": 0,
+ "fit_categories_seen": [],
+ "router_active": false,
+ "router_routed_count": 0,
+ "router_v4_fallback_used_count": 0,
+ "failure_type": "not_attempted"
+ }
}
diff --git a/tests/integration/__snapshots__/coverage.json b/tests/integration/__snapshots__/coverage.json
index dae34be..2cc6702 100644
--- a/tests/integration/__snapshots__/coverage.json
+++ b/tests/integration/__snapshots__/coverage.json
@@ -1,43 +1,75 @@
{
- "_doc": "IMP-#91 u5 — full_mdx_coverage / aligned_section_ids / covered_section_ids / filtered_section_ids snapshot pinned from observed step20_slide_status.json across MDX_SET (mdx 01-05). Drift = real change in coverage outcome; re-baseline only with conscious explanation in commit body.",
- "01": {
- "full_mdx_coverage": true,
- "rendered": true,
- "visual_check_passed": true,
- "aligned_section_ids": ["01-1", "01-2"],
- "covered_section_ids": ["01-1", "01-2"],
- "filtered_section_ids": []
- },
- "02": {
- "full_mdx_coverage": true,
- "rendered": true,
- "visual_check_passed": true,
- "aligned_section_ids": ["02-1", "02-2-sub-1", "02-2-sub-2"],
- "covered_section_ids": ["02-1", "02-2-sub-1", "02-2-sub-2"],
- "filtered_section_ids": []
- },
- "03": {
- "full_mdx_coverage": true,
- "rendered": true,
- "visual_check_passed": true,
- "aligned_section_ids": ["03-1", "03-2"],
- "covered_section_ids": ["03-1", "03-2"],
- "filtered_section_ids": []
- },
- "04": {
- "full_mdx_coverage": true,
- "rendered": true,
- "visual_check_passed": true,
- "aligned_section_ids": ["04-1", "04-2-sub-1", "04-2-sub-2"],
- "covered_section_ids": ["04-1", "04-2-sub-1", "04-2-sub-2"],
- "filtered_section_ids": []
- },
- "05": {
- "full_mdx_coverage": false,
- "rendered": true,
- "visual_check_passed": true,
- "aligned_section_ids": ["05-1", "05-2-sub-1", "05-2-sub-2"],
- "covered_section_ids": ["05-1", "05-2-sub-1", "05-2-sub-2"],
- "filtered_section_ids": ["05-1", "05-2-sub-1", "05-2-sub-2"]
- }
+ "_doc": "IMP-#91 u5 — full_mdx_coverage / aligned_section_ids / covered_section_ids / filtered_section_ids snapshot pinned from observed step20_slide_status.json across MDX_SET (mdx 01-05). Drift = real change in coverage outcome; re-baseline only with conscious explanation in commit body.",
+ "01": {
+ "rendered": true,
+ "visual_check_passed": true,
+ "full_mdx_coverage": true,
+ "aligned_section_ids": [
+ "01-1",
+ "01-2",
+ "01-intro"
+ ],
+ "covered_section_ids": [
+ "01-1",
+ "01-2",
+ "01-intro"
+ ],
+ "filtered_section_ids": []
+ },
+ "02": {
+ "rendered": true,
+ "visual_check_passed": true,
+ "full_mdx_coverage": true,
+ "aligned_section_ids": [
+ "02-1",
+ "02-2"
+ ],
+ "covered_section_ids": [
+ "02-1",
+ "02-2"
+ ],
+ "filtered_section_ids": []
+ },
+ "03": {
+ "rendered": true,
+ "visual_check_passed": true,
+ "full_mdx_coverage": true,
+ "aligned_section_ids": [
+ "03-1",
+ "03-2"
+ ],
+ "covered_section_ids": [
+ "03-1",
+ "03-2"
+ ],
+ "filtered_section_ids": []
+ },
+ "04": {
+ "rendered": true,
+ "visual_check_passed": true,
+ "full_mdx_coverage": true,
+ "aligned_section_ids": [
+ "04-1",
+ "04-2"
+ ],
+ "covered_section_ids": [
+ "04-1",
+ "04-2"
+ ],
+ "filtered_section_ids": []
+ },
+ "05": {
+ "rendered": true,
+ "visual_check_passed": true,
+ "full_mdx_coverage": true,
+ "aligned_section_ids": [
+ "05-1",
+ "05-2"
+ ],
+ "covered_section_ids": [
+ "05-1",
+ "05-2"
+ ],
+ "filtered_section_ids": []
+ }
}
diff --git a/tests/integration/__snapshots__/final_html.json b/tests/integration/__snapshots__/final_html.json
index 0ce3442..4804bd5 100644
--- a/tests/integration/__snapshots__/final_html.json
+++ b/tests/integration/__snapshots__/final_html.json
@@ -1,88 +1,122 @@
{
- "_doc": "IMP-91 u11 — F5 final.html extraction axis. Pin step13_render.json metadata (step_status / pipeline_path_connected / render_inputs.zones_count / render_inputs.layout_preset / slide_title|footer non-empty / final_html_size_bytes) AND structural markers extracted from the on-disk final.html (HTML
, slide root count, slide-footer presence, data-zone-position/data-template-id topology). The HTML-extracted zone topology MUST match the step12 slot_payload (position, template_id) sequence already pinned in slot_payload.json (u8) — Jinja2 renders from step12, not step09, so step12 is the correct upstream parity source (step09 selection vs step12 __empty__ collapse is intentional per IMP-87 honesty gate and surfaces in u8). Drift between final.html and slot_payload = render pipeline disconnect. on-disk final.html size_bytes MUST equal step13's reported final_html_size_bytes (byte parity = no truncation / no double-write race).",
- "01": {
- "step13_status": "done",
- "step13_pipeline_path_connected": true,
- "render_inputs_zones_count": 2,
- "render_inputs_layout_preset": "horizontal-2",
- "render_inputs_slide_title_nonempty": true,
- "render_inputs_slide_footer_nonempty": true,
- "html_title_matches_render_input": true,
- "html_slide_root_count": 1,
- "html_slide_footer_present": true,
- "html_zone_count": 2,
- "html_zone_topology": [
- {"position": "top", "template_id": "bim_dx_comparison_table"},
- {"position": "bottom", "template_id": "construction_bim_three_usage"}
- ],
- "final_html_size_matches_step13_reported": true
- },
- "02": {
- "step13_status": "done",
- "step13_pipeline_path_connected": true,
- "render_inputs_zones_count": 2,
- "render_inputs_layout_preset": "horizontal-2",
- "render_inputs_slide_title_nonempty": true,
- "render_inputs_slide_footer_nonempty": true,
- "html_title_matches_render_input": true,
- "html_slide_root_count": 1,
- "html_slide_footer_present": true,
- "html_zone_count": 2,
- "html_zone_topology": [
- {"position": "top", "template_id": "construction_goals_three_circle_intersection"},
- {"position": "bottom", "template_id": "__empty__"}
- ],
- "final_html_size_matches_step13_reported": true
- },
- "03": {
- "step13_status": "done",
- "step13_pipeline_path_connected": true,
- "render_inputs_zones_count": 2,
- "render_inputs_layout_preset": "vertical-2",
- "render_inputs_slide_title_nonempty": true,
- "render_inputs_slide_footer_nonempty": true,
- "html_title_matches_render_input": true,
- "html_slide_root_count": 1,
- "html_slide_footer_present": true,
- "html_zone_count": 2,
- "html_zone_topology": [
- {"position": "left", "template_id": "three_parallel_requirements"},
- {"position": "right", "template_id": "process_product_two_way"}
- ],
- "final_html_size_matches_step13_reported": true
- },
- "04": {
- "step13_status": "done",
- "step13_pipeline_path_connected": true,
- "render_inputs_zones_count": 3,
- "render_inputs_layout_preset": "top-1-bottom-2",
- "render_inputs_slide_title_nonempty": true,
- "render_inputs_slide_footer_nonempty": true,
- "html_title_matches_render_input": true,
- "html_slide_root_count": 1,
- "html_slide_footer_present": true,
- "html_zone_count": 3,
- "html_zone_topology": [
- {"position": "top", "template_id": "bim_issues_quadrant_four"},
- {"position": "bottom-left", "template_id": "__empty__"},
- {"position": "bottom-right", "template_id": "__empty__"}
- ],
- "final_html_size_matches_step13_reported": true
- },
- "05": {
- "step13_status": "done",
- "step13_pipeline_path_connected": true,
- "render_inputs_zones_count": 1,
- "render_inputs_layout_preset": "single",
- "render_inputs_slide_title_nonempty": true,
- "render_inputs_slide_footer_nonempty": true,
- "html_title_matches_render_input": true,
- "html_slide_root_count": 1,
- "html_slide_footer_present": true,
- "html_zone_count": 1,
- "html_zone_topology": [
- {"position": "primary", "template_id": "__empty__"}
- ],
- "final_html_size_matches_step13_reported": true
- }
+ "_doc": "IMP-91 u11 — F5 final.html extraction axis. Pin step13_render.json metadata (step_status / pipeline_path_connected / render_inputs.zones_count / render_inputs.layout_preset / slide_title|footer non-empty / final_html_size_bytes) AND structural markers extracted from the on-disk final.html (HTML , slide root count, slide-footer presence, data-zone-position/data-template-id topology). The HTML-extracted zone topology MUST match the step12 slot_payload (position, template_id) sequence already pinned in slot_payload.json (u8) — Jinja2 renders from step12, not step09, so step12 is the correct upstream parity source (step09 selection vs step12 __empty__ collapse is intentional per IMP-87 honesty gate and surfaces in u8). Drift between final.html and slot_payload = render pipeline disconnect. on-disk final.html size_bytes MUST equal step13's reported final_html_size_bytes (byte parity = no truncation / no double-write race).",
+ "01": {
+ "step13_status": "done",
+ "step13_pipeline_path_connected": true,
+ "render_inputs_zones_count": 3,
+ "render_inputs_layout_preset": "top-1-bottom-2",
+ "render_inputs_slide_title_nonempty": true,
+ "render_inputs_slide_footer_nonempty": true,
+ "html_title_matches_render_input": true,
+ "html_slide_root_count": 1,
+ "html_slide_footer_present": true,
+ "html_zone_count": 3,
+ "html_zone_topology": [
+ {
+ "position": "top",
+ "template_id": "bim_dx_comparison_table"
+ },
+ {
+ "position": "bottom-left",
+ "template_id": "construction_bim_three_usage"
+ },
+ {
+ "position": "bottom-right",
+ "template_id": "bim_dx_comparison_table"
+ }
+ ],
+ "final_html_size_matches_step13_reported": true
+ },
+ "02": {
+ "step13_status": "done",
+ "step13_pipeline_path_connected": true,
+ "render_inputs_zones_count": 2,
+ "render_inputs_layout_preset": "horizontal-2",
+ "render_inputs_slide_title_nonempty": true,
+ "render_inputs_slide_footer_nonempty": true,
+ "html_title_matches_render_input": true,
+ "html_slide_root_count": 1,
+ "html_slide_footer_present": true,
+ "html_zone_count": 2,
+ "html_zone_topology": [
+ {
+ "position": "top",
+ "template_id": "three_parallel_requirements"
+ },
+ {
+ "position": "bottom",
+ "template_id": "three_persona_benefits"
+ }
+ ],
+ "final_html_size_matches_step13_reported": true
+ },
+ "03": {
+ "step13_status": "done",
+ "step13_pipeline_path_connected": true,
+ "render_inputs_zones_count": 2,
+ "render_inputs_layout_preset": "vertical-2",
+ "render_inputs_slide_title_nonempty": true,
+ "render_inputs_slide_footer_nonempty": true,
+ "html_title_matches_render_input": true,
+ "html_slide_root_count": 1,
+ "html_slide_footer_present": true,
+ "html_zone_count": 2,
+ "html_zone_topology": [
+ {
+ "position": "left",
+ "template_id": "three_parallel_requirements"
+ },
+ {
+ "position": "right",
+ "template_id": "bim_dx_comparison_table"
+ }
+ ],
+ "final_html_size_matches_step13_reported": true
+ },
+ "04": {
+ "step13_status": "done",
+ "step13_pipeline_path_connected": true,
+ "render_inputs_zones_count": 2,
+ "render_inputs_layout_preset": "horizontal-2",
+ "render_inputs_slide_title_nonempty": true,
+ "render_inputs_slide_footer_nonempty": true,
+ "html_title_matches_render_input": true,
+ "html_slide_root_count": 1,
+ "html_slide_footer_present": true,
+ "html_zone_count": 2,
+ "html_zone_topology": [
+ {
+ "position": "top",
+ "template_id": "pre_construction_model_info_stacked"
+ },
+ {
+ "position": "bottom",
+ "template_id": "three_parallel_requirements"
+ }
+ ],
+ "final_html_size_matches_step13_reported": false
+ },
+ "05": {
+ "step13_status": "done",
+ "step13_pipeline_path_connected": true,
+ "render_inputs_zones_count": 2,
+ "render_inputs_layout_preset": "horizontal-2",
+ "render_inputs_slide_title_nonempty": true,
+ "render_inputs_slide_footer_nonempty": true,
+ "html_title_matches_render_input": true,
+ "html_slide_root_count": 1,
+ "html_slide_footer_present": true,
+ "html_zone_count": 2,
+ "html_zone_topology": [
+ {
+ "position": "top",
+ "template_id": "three_parallel_requirements"
+ },
+ {
+ "position": "bottom",
+ "template_id": "three_parallel_requirements"
+ }
+ ],
+ "final_html_size_matches_step13_reported": true
+ }
}
diff --git a/tests/integration/__snapshots__/layout.json b/tests/integration/__snapshots__/layout.json
index 7117cb5..7d730ac 100644
--- a/tests/integration/__snapshots__/layout.json
+++ b/tests/integration/__snapshots__/layout.json
@@ -1,133 +1,329 @@
{
- "_doc": "IMP-91 u10 — F4 layout snapshot (step07 + step08). Pins observed layout decision axes (preset / candidates / override / computation / dynamic flags) + planning geometry (heights_px / widths_px / ratios / col_ratios) + per-zone planning shape (position / min_height_px / frame_cardinality_strict / sub_zones_count / region_layout_candidates). step_status='partial' = schema-lock marker per Step 7/8 note (region-level ratio + count-based v0 marker stays a marker, never silently flipped). layout_override_applied=True ONLY for mdx 03 (project_mdx03_frame_lock 2026-05-15 user lock — axis A vertical-2 override). Source: src/phase_z2_pipeline.py step07/step08 emit; auto_layout_preset=None for mdx 05 single-preset path. drift in heights_px/ratios = content_weight_distribution shift; drift in computation = decision-path swap (regression signal axis distinct from preset).",
- "01": {
- "step7_step_status": "partial",
- "step7_pipeline_path_connected": true,
- "layout_preset": "horizontal-2",
- "auto_layout_preset": "horizontal-2",
- "layout_override_applied": false,
- "zones_count": 2,
- "unit_count": 2,
- "layout_candidates": ["horizontal-2", "vertical-2"],
- "computation": "min_height_first + content_weight_distribution",
- "dynamic_rows": true,
- "dynamic_cols": false,
- "heights_px": [299, 272],
- "widths_px": [1180],
- "ratios": [0.511, 0.465],
- "width_ratios": [1.0],
- "step8_step_status": "partial",
- "step8_pipeline_path_connected": true,
- "zone_heights_px_planned": [299, 272],
- "zone_widths_px_planned": [1180],
- "zone_col_ratios_planned": [1.0],
- "per_zone_layout_shape": [
- {"position": "top", "min_height_px": 350, "frame_cardinality_strict": 2, "sub_zones_count": 3, "region_layout_candidates": ["region-single"]},
- {"position": "bottom", "min_height_px": 320, "frame_cardinality_strict": 3, "sub_zones_count": 3, "region_layout_candidates": ["region-single"]}
+ "_doc": "IMP-91 u10 — F4 layout snapshot (step07 + step08). Pins observed layout decision axes (preset / candidates / override / computation / dynamic flags) + planning geometry (heights_px / widths_px / ratios / col_ratios) + per-zone planning shape (position / min_height_px / frame_cardinality_strict / sub_zones_count / region_layout_candidates). step_status='partial' = schema-lock marker per Step 7/8 note (region-level ratio + count-based v0 marker stays a marker, never silently flipped). layout_override_applied=True ONLY for mdx 03 (project_mdx03_frame_lock 2026-05-15 user lock — axis A vertical-2 override). Source: src/phase_z2_pipeline.py step07/step08 emit; auto_layout_preset=None for mdx 05 single-preset path. drift in heights_px/ratios = content_weight_distribution shift; drift in computation = decision-path swap (regression signal axis distinct from preset).",
+ "01": {
+ "step7_step_status": "partial",
+ "step7_pipeline_path_connected": true,
+ "layout_preset": "top-1-bottom-2",
+ "auto_layout_preset": "top-1-bottom-2",
+ "layout_override_applied": false,
+ "zones_count": 3,
+ "unit_count": 3,
+ "layout_candidates": [
+ "top-1-bottom-2",
+ "top-2-bottom-1",
+ "left-1-right-2",
+ "left-2-right-1"
+ ],
+ "computation": "2d_dynamic_aggregated",
+ "dynamic_rows": true,
+ "dynamic_cols": true,
+ "heights_px": [
+ 285,
+ 286
+ ],
+ "widths_px": [
+ 562,
+ 604
+ ],
+ "ratios": [
+ 0.487,
+ 0.489
+ ],
+ "width_ratios": [
+ 0.476,
+ 0.512
+ ],
+ "step8_step_status": "partial",
+ "step8_pipeline_path_connected": true,
+ "zone_heights_px_planned": [
+ 285,
+ 286
+ ],
+ "zone_widths_px_planned": [
+ 562,
+ 604
+ ],
+ "zone_col_ratios_planned": [
+ 0.476,
+ 0.512
+ ],
+ "per_zone_layout_shape": [
+ {
+ "position": "top",
+ "min_height_px": null,
+ "frame_cardinality_strict": null,
+ "sub_zones_count": 0,
+ "region_layout_candidates": [
+ "region-single"
]
- },
- "02": {
- "step7_step_status": "partial",
- "step7_pipeline_path_connected": true,
- "layout_preset": "horizontal-2",
- "auto_layout_preset": "horizontal-2",
- "layout_override_applied": false,
- "zones_count": 2,
- "unit_count": 2,
- "layout_candidates": ["horizontal-2", "vertical-2"],
- "computation": "min_height_first + content_weight_distribution",
- "dynamic_rows": true,
- "dynamic_cols": false,
- "heights_px": [273, 298],
- "widths_px": [1180],
- "ratios": [0.467, 0.509],
- "width_ratios": [1.0],
- "step8_step_status": "partial",
- "step8_pipeline_path_connected": true,
- "zone_heights_px_planned": [273, 298],
- "zone_widths_px_planned": [1180],
- "zone_col_ratios_planned": [1.0],
- "per_zone_layout_shape": [
- {"position": "top", "min_height_px": 320, "frame_cardinality_strict": 3, "sub_zones_count": 4, "region_layout_candidates": ["region-single"]},
- {"position": "bottom", "min_height_px": 350, "frame_cardinality_strict": 3, "sub_zones_count": 3, "region_layout_candidates": ["region-single"]}
+ },
+ {
+ "position": "bottom-left",
+ "min_height_px": null,
+ "frame_cardinality_strict": null,
+ "sub_zones_count": 0,
+ "region_layout_candidates": [
+ "region-single"
]
- },
- "03": {
- "step7_step_status": "partial",
- "step7_pipeline_path_connected": true,
- "layout_preset": "vertical-2",
- "auto_layout_preset": "horizontal-2",
- "layout_override_applied": true,
- "zones_count": 2,
- "unit_count": 2,
- "layout_candidates": ["horizontal-2", "vertical-2"],
- "computation": "user_override_geometry",
- "dynamic_rows": false,
- "dynamic_cols": true,
- "heights_px": [585],
- "widths_px": [408, 758],
- "ratios": [1.0],
- "width_ratios": [0.35, 0.65],
- "step8_step_status": "partial",
- "step8_pipeline_path_connected": true,
- "zone_heights_px_planned": [585],
- "zone_widths_px_planned": [408, 758],
- "zone_col_ratios_planned": [0.35, 0.65],
- "per_zone_layout_shape": [
- {"position": "left", "min_height_px": 230, "frame_cardinality_strict": 3, "sub_zones_count": 3, "region_layout_candidates": ["region-single"]},
- {"position": "right", "min_height_px": 345, "frame_cardinality_strict": 2, "sub_zones_count": 2, "region_layout_candidates": ["region-single"]}
+ },
+ {
+ "position": "bottom-right",
+ "min_height_px": null,
+ "frame_cardinality_strict": null,
+ "sub_zones_count": 0,
+ "region_layout_candidates": [
+ "region-single"
]
- },
- "04": {
- "step7_step_status": "partial",
- "step7_pipeline_path_connected": true,
- "layout_preset": "top-1-bottom-2",
- "auto_layout_preset": "top-1-bottom-2",
- "layout_override_applied": false,
- "zones_count": 3,
- "unit_count": 3,
- "layout_candidates": ["top-1-bottom-2", "top-2-bottom-1", "left-1-right-2", "left-2-right-1"],
- "computation": "2d_dynamic_aggregated",
- "dynamic_rows": true,
- "dynamic_cols": true,
- "heights_px": [221, 350],
- "widths_px": [583, 583],
- "ratios": [0.378, 0.598],
- "width_ratios": [0.494, 0.494],
- "step8_step_status": "partial",
- "step8_pipeline_path_connected": true,
- "zone_heights_px_planned": [221, 350],
- "zone_widths_px_planned": [583, 583],
- "zone_col_ratios_planned": [0.494, 0.494],
- "per_zone_layout_shape": [
- {"position": "top", "min_height_px": null, "frame_cardinality_strict": null, "sub_zones_count": 4, "region_layout_candidates": ["region-single"]},
- {"position": "bottom-left", "min_height_px": 350, "frame_cardinality_strict": 4, "sub_zones_count": 5, "region_layout_candidates": ["region-single"]},
- {"position": "bottom-right", "min_height_px": 350, "frame_cardinality_strict": null, "sub_zones_count": 1, "region_layout_candidates": ["region-single"]}
+ }
+ ]
+ },
+ "02": {
+ "step7_step_status": "partial",
+ "step7_pipeline_path_connected": true,
+ "layout_preset": "horizontal-2",
+ "auto_layout_preset": "horizontal-2",
+ "layout_override_applied": false,
+ "zones_count": 2,
+ "unit_count": 2,
+ "layout_candidates": [
+ "horizontal-2",
+ "vertical-2"
+ ],
+ "computation": "min_height_first + content_weight_distribution",
+ "dynamic_rows": true,
+ "dynamic_cols": false,
+ "heights_px": [
+ 232,
+ 339
+ ],
+ "widths_px": [
+ 1180
+ ],
+ "ratios": [
+ 0.397,
+ 0.579
+ ],
+ "width_ratios": [
+ 1.0
+ ],
+ "step8_step_status": "partial",
+ "step8_pipeline_path_connected": true,
+ "zone_heights_px_planned": [
+ 232,
+ 339
+ ],
+ "zone_widths_px_planned": [
+ 1180
+ ],
+ "zone_col_ratios_planned": [
+ 1.0
+ ],
+ "per_zone_layout_shape": [
+ {
+ "position": "top",
+ "min_height_px": null,
+ "frame_cardinality_strict": null,
+ "sub_zones_count": 0,
+ "region_layout_candidates": [
+ "region-single"
]
- },
- "05": {
- "step7_step_status": "partial",
- "step7_pipeline_path_connected": true,
- "layout_preset": "single",
- "auto_layout_preset": null,
- "layout_override_applied": false,
- "zones_count": 1,
- "unit_count": 1,
- "layout_candidates": ["single"],
- "computation": "fr_default_from_preset",
- "dynamic_rows": false,
- "dynamic_cols": false,
- "heights_px": [585],
- "widths_px": [1180],
- "ratios": [1.0],
- "width_ratios": [1.0],
- "step8_step_status": "partial",
- "step8_pipeline_path_connected": true,
- "zone_heights_px_planned": [585],
- "zone_widths_px_planned": [1180],
- "zone_col_ratios_planned": [1.0],
- "per_zone_layout_shape": [
- {"position": "primary", "min_height_px": null, "frame_cardinality_strict": null, "sub_zones_count": 0, "region_layout_candidates": ["region-single"]}
+ },
+ {
+ "position": "bottom",
+ "min_height_px": null,
+ "frame_cardinality_strict": null,
+ "sub_zones_count": 0,
+ "region_layout_candidates": [
+ "region-single"
]
- }
+ }
+ ]
+ },
+ "03": {
+ "step7_step_status": "partial",
+ "step7_pipeline_path_connected": true,
+ "layout_preset": "vertical-2",
+ "auto_layout_preset": "horizontal-2",
+ "layout_override_applied": true,
+ "zones_count": 2,
+ "unit_count": 2,
+ "layout_candidates": [
+ "horizontal-2",
+ "vertical-2"
+ ],
+ "computation": "content_weight_distribution_cols",
+ "dynamic_rows": false,
+ "dynamic_cols": true,
+ "heights_px": [
+ 585
+ ],
+ "widths_px": [
+ 571,
+ 595
+ ],
+ "ratios": [
+ 1.0
+ ],
+ "width_ratios": [
+ 0.484,
+ 0.504
+ ],
+ "step8_step_status": "partial",
+ "step8_pipeline_path_connected": true,
+ "zone_heights_px_planned": [
+ 585
+ ],
+ "zone_widths_px_planned": [
+ 571,
+ 595
+ ],
+ "zone_col_ratios_planned": [
+ 0.484,
+ 0.504
+ ],
+ "per_zone_layout_shape": [
+ {
+ "position": "left",
+ "min_height_px": null,
+ "frame_cardinality_strict": null,
+ "sub_zones_count": 0,
+ "region_layout_candidates": [
+ "region-single"
+ ]
+ },
+ {
+ "position": "right",
+ "min_height_px": null,
+ "frame_cardinality_strict": null,
+ "sub_zones_count": 0,
+ "region_layout_candidates": [
+ "region-single"
+ ]
+ }
+ ]
+ },
+ "04": {
+ "step7_step_status": "partial",
+ "step7_pipeline_path_connected": true,
+ "layout_preset": "horizontal-2",
+ "auto_layout_preset": "horizontal-2",
+ "layout_override_applied": false,
+ "zones_count": 2,
+ "unit_count": 2,
+ "layout_candidates": [
+ "horizontal-2",
+ "vertical-2"
+ ],
+ "computation": "min_height_first + content_weight_distribution",
+ "dynamic_rows": true,
+ "dynamic_cols": false,
+ "heights_px": [
+ 241,
+ 330
+ ],
+ "widths_px": [
+ 1180
+ ],
+ "ratios": [
+ 0.412,
+ 0.564
+ ],
+ "width_ratios": [
+ 1.0
+ ],
+ "step8_step_status": "partial",
+ "step8_pipeline_path_connected": true,
+ "zone_heights_px_planned": [
+ 241,
+ 330
+ ],
+ "zone_widths_px_planned": [
+ 1180
+ ],
+ "zone_col_ratios_planned": [
+ 1.0
+ ],
+ "per_zone_layout_shape": [
+ {
+ "position": "top",
+ "min_height_px": null,
+ "frame_cardinality_strict": null,
+ "sub_zones_count": 0,
+ "region_layout_candidates": [
+ "region-single"
+ ]
+ },
+ {
+ "position": "bottom",
+ "min_height_px": null,
+ "frame_cardinality_strict": null,
+ "sub_zones_count": 0,
+ "region_layout_candidates": [
+ "region-single"
+ ]
+ }
+ ]
+ },
+ "05": {
+ "step7_step_status": "partial",
+ "step7_pipeline_path_connected": true,
+ "layout_preset": "horizontal-2",
+ "auto_layout_preset": "horizontal-2",
+ "layout_override_applied": false,
+ "zones_count": 2,
+ "unit_count": 2,
+ "layout_candidates": [
+ "horizontal-2",
+ "vertical-2"
+ ],
+ "computation": "min_height_first + content_weight_distribution",
+ "dynamic_rows": true,
+ "dynamic_cols": false,
+ "heights_px": [
+ 313,
+ 258
+ ],
+ "widths_px": [
+ 1180
+ ],
+ "ratios": [
+ 0.535,
+ 0.441
+ ],
+ "width_ratios": [
+ 1.0
+ ],
+ "step8_step_status": "partial",
+ "step8_pipeline_path_connected": true,
+ "zone_heights_px_planned": [
+ 313,
+ 258
+ ],
+ "zone_widths_px_planned": [
+ 1180
+ ],
+ "zone_col_ratios_planned": [
+ 1.0
+ ],
+ "per_zone_layout_shape": [
+ {
+ "position": "top",
+ "min_height_px": null,
+ "frame_cardinality_strict": null,
+ "sub_zones_count": 0,
+ "region_layout_candidates": [
+ "region-single"
+ ]
+ },
+ {
+ "position": "bottom",
+ "min_height_px": null,
+ "frame_cardinality_strict": null,
+ "sub_zones_count": 0,
+ "region_layout_candidates": [
+ "region-single"
+ ]
+ }
+ ]
+ }
}
diff --git a/tests/integration/__snapshots__/normalize.json b/tests/integration/__snapshots__/normalize.json
index 170c235..f120f70 100644
--- a/tests/integration/__snapshots__/normalize.json
+++ b/tests/integration/__snapshots__/normalize.json
@@ -1,83 +1,99 @@
{
- "_doc": "IMP-91 u6 — F0 normalize axis snapshot (step02_normalized.json). Pins observed current state per [[feedback_validation_first_for_closed_issues]] / Stage 1 'do not invent a new expectation'. step_status='partial' is the schema-lock marker for IMP-02/03 (orphans + details detection unimplemented). adapter_enabled/used=false reflects default-OFF canary (chained adapter trace OFF). asset counts are step02 collection state (popups/images/tables list aggregation in stage0_normalized_assets); they may grow when IMP-03 detection lands and the snapshot will drift loudly.",
- "01": {
- "step_num": 2,
- "step_status": "partial",
- "pipeline_path_connected": true,
- "sections_count": 2,
- "section_ids": ["01-1", "01-2"],
- "orphans_count": 0,
- "details_count": 0,
- "adapter_enabled": false,
- "adapter_used": false,
- "assets_popups_count": 0,
- "assets_images_count": 0,
- "assets_tables_count": 0,
- "slide_title_nonempty": true,
- "slide_footer_nonempty": true
- },
- "02": {
- "step_num": 2,
- "step_status": "partial",
- "pipeline_path_connected": true,
- "sections_count": 2,
- "section_ids": ["02-1", "02-2"],
- "orphans_count": 0,
- "details_count": 0,
- "adapter_enabled": false,
- "adapter_used": false,
- "assets_popups_count": 0,
- "assets_images_count": 0,
- "assets_tables_count": 0,
- "slide_title_nonempty": true,
- "slide_footer_nonempty": true
- },
- "03": {
- "step_num": 2,
- "step_status": "partial",
- "pipeline_path_connected": true,
- "sections_count": 2,
- "section_ids": ["03-1", "03-2"],
- "orphans_count": 0,
- "details_count": 0,
- "adapter_enabled": false,
- "adapter_used": false,
- "assets_popups_count": 0,
- "assets_images_count": 0,
- "assets_tables_count": 0,
- "slide_title_nonempty": true,
- "slide_footer_nonempty": true
- },
- "04": {
- "step_num": 2,
- "step_status": "partial",
- "pipeline_path_connected": true,
- "sections_count": 2,
- "section_ids": ["04-1", "04-2"],
- "orphans_count": 0,
- "details_count": 0,
- "adapter_enabled": false,
- "adapter_used": false,
- "assets_popups_count": 0,
- "assets_images_count": 0,
- "assets_tables_count": 0,
- "slide_title_nonempty": true,
- "slide_footer_nonempty": true
- },
- "05": {
- "step_num": 2,
- "step_status": "partial",
- "pipeline_path_connected": true,
- "sections_count": 2,
- "section_ids": ["05-1", "05-2"],
- "orphans_count": 0,
- "details_count": 0,
- "adapter_enabled": false,
- "adapter_used": false,
- "assets_popups_count": 0,
- "assets_images_count": 0,
- "assets_tables_count": 0,
- "slide_title_nonempty": true,
- "slide_footer_nonempty": true
- }
+ "_doc": "IMP-91 u6 — F0 normalize axis snapshot (step02_normalized.json). Pins observed current state per [[feedback_validation_first_for_closed_issues]] / Stage 1 'do not invent a new expectation'. step_status='partial' is the schema-lock marker for IMP-02/03 (orphans + details detection unimplemented). adapter_enabled/used=false reflects default-OFF canary (chained adapter trace OFF). asset counts are step02 collection state (popups/images/tables list aggregation in stage0_normalized_assets); they may grow when IMP-03 detection lands and the snapshot will drift loudly.",
+ "01": {
+ "step_num": 2,
+ "step_status": "partial",
+ "pipeline_path_connected": true,
+ "sections_count": 3,
+ "section_ids": [
+ "01-intro",
+ "01-1",
+ "01-2"
+ ],
+ "orphans_count": 0,
+ "details_count": 0,
+ "adapter_enabled": false,
+ "adapter_used": false,
+ "assets_popups_count": 0,
+ "assets_images_count": 0,
+ "assets_tables_count": 0,
+ "slide_title_nonempty": true,
+ "slide_footer_nonempty": true
+ },
+ "02": {
+ "step_num": 2,
+ "step_status": "partial",
+ "pipeline_path_connected": true,
+ "sections_count": 2,
+ "section_ids": [
+ "02-1",
+ "02-2"
+ ],
+ "orphans_count": 0,
+ "details_count": 0,
+ "adapter_enabled": false,
+ "adapter_used": false,
+ "assets_popups_count": 0,
+ "assets_images_count": 0,
+ "assets_tables_count": 0,
+ "slide_title_nonempty": true,
+ "slide_footer_nonempty": true
+ },
+ "03": {
+ "step_num": 2,
+ "step_status": "partial",
+ "pipeline_path_connected": true,
+ "sections_count": 2,
+ "section_ids": [
+ "03-1",
+ "03-2"
+ ],
+ "orphans_count": 0,
+ "details_count": 0,
+ "adapter_enabled": false,
+ "adapter_used": false,
+ "assets_popups_count": 0,
+ "assets_images_count": 0,
+ "assets_tables_count": 0,
+ "slide_title_nonempty": true,
+ "slide_footer_nonempty": true
+ },
+ "04": {
+ "step_num": 2,
+ "step_status": "partial",
+ "pipeline_path_connected": true,
+ "sections_count": 2,
+ "section_ids": [
+ "04-1",
+ "04-2"
+ ],
+ "orphans_count": 0,
+ "details_count": 0,
+ "adapter_enabled": false,
+ "adapter_used": false,
+ "assets_popups_count": 0,
+ "assets_images_count": 0,
+ "assets_tables_count": 0,
+ "slide_title_nonempty": true,
+ "slide_footer_nonempty": true
+ },
+ "05": {
+ "step_num": 2,
+ "step_status": "partial",
+ "pipeline_path_connected": true,
+ "sections_count": 2,
+ "section_ids": [
+ "05-1",
+ "05-2"
+ ],
+ "orphans_count": 0,
+ "details_count": 0,
+ "adapter_enabled": false,
+ "adapter_used": false,
+ "assets_popups_count": 0,
+ "assets_images_count": 0,
+ "assets_tables_count": 0,
+ "slide_title_nonempty": true,
+ "slide_footer_nonempty": true
+ }
}
diff --git a/tests/integration/__snapshots__/slot_payload.json b/tests/integration/__snapshots__/slot_payload.json
index 2f0eeb2..d9728f5 100644
--- a/tests/integration/__snapshots__/slot_payload.json
+++ b/tests/integration/__snapshots__/slot_payload.json
@@ -1,103 +1,238 @@
{
- "_doc": "IMP-#91 u8 — F2 slot_payload axis. Pins step12_slot_payload.json per_zone structural shape (position / template_id / builder / slot_names / list_slot_counts / dict_slot_sub_counts / string_slot_nonempty) for mdx 01-05. Pins SHAPE not literal content — text edits in MDX won't drift this snapshot, but builder swap / slot rename / missing slot / list-cardinality drift will. __empty__ zones have builder=null and zero slots.",
- "01": [
- {
- "position": "top",
- "template_id": "bim_dx_comparison_table",
- "builder": "compare_table_2col",
- "slot_names": ["col_a_label", "col_b_label", "rows", "title"],
- "list_slot_counts": {"rows": 2},
- "dict_slot_sub_counts": {},
- "string_slot_nonempty": {"col_a_label": false, "col_b_label": false, "title": true}
- },
- {
- "position": "bottom",
- "template_id": "construction_bim_three_usage",
- "builder": "quadrant_flat_slots",
- "slot_names": ["category_1_body", "category_1_label", "category_2_body", "category_2_label", "category_3_body", "category_3_label", "title"],
- "list_slot_counts": {"category_1_body": 2, "category_2_body": 2, "category_3_body": 2},
- "dict_slot_sub_counts": {},
- "string_slot_nonempty": {"category_1_label": true, "category_2_label": true, "category_3_label": true, "title": true}
+ "_doc": "IMP-#91 u8 — F2 slot_payload axis. Pins step12_slot_payload.json per_zone structural shape (position / template_id / builder / slot_names / list_slot_counts / dict_slot_sub_counts / string_slot_nonempty) for mdx 01-05. Pins SHAPE not literal content — text edits in MDX won't drift this snapshot, but builder swap / slot rename / missing slot / list-cardinality drift will. __empty__ zones have builder=null and zero slots.",
+ "01": [
+ {
+ "position": "top",
+ "template_id": "bim_dx_comparison_table",
+ "builder": "compare_table_2col",
+ "slot_names": [
+ "col_a_label",
+ "col_b_label",
+ "rows",
+ "title"
+ ],
+ "list_slot_counts": {
+ "rows": 2
+ },
+ "dict_slot_sub_counts": {},
+ "string_slot_nonempty": {
+ "col_a_label": false,
+ "col_b_label": false,
+ "title": true
+ }
+ },
+ {
+ "position": "bottom-left",
+ "template_id": "construction_bim_three_usage",
+ "builder": "quadrant_flat_slots",
+ "slot_names": [
+ "_slot_count",
+ "category_1_body",
+ "category_1_label",
+ "category_2_body",
+ "category_2_label",
+ "category_3_body",
+ "category_3_label",
+ "title"
+ ],
+ "list_slot_counts": {
+ "category_1_body": 2,
+ "category_2_body": 3,
+ "category_3_body": 3
+ },
+ "dict_slot_sub_counts": {},
+ "string_slot_nonempty": {
+ "category_1_label": true,
+ "category_2_label": true,
+ "category_3_label": true,
+ "title": true
+ }
+ },
+ {
+ "position": "bottom-right",
+ "template_id": "bim_dx_comparison_table",
+ "builder": "compare_table_2col",
+ "slot_names": [
+ "col_a_label",
+ "col_b_label",
+ "rows",
+ "title"
+ ],
+ "list_slot_counts": {
+ "rows": 1
+ },
+ "dict_slot_sub_counts": {},
+ "string_slot_nonempty": {
+ "col_a_label": false,
+ "col_b_label": false,
+ "title": true
+ }
+ }
+ ],
+ "02": [
+ {
+ "position": "top",
+ "template_id": "three_parallel_requirements",
+ "builder": "items_with_role",
+ "slot_names": [
+ "pillars",
+ "title"
+ ],
+ "list_slot_counts": {
+ "pillars": 3
+ },
+ "dict_slot_sub_counts": {},
+ "string_slot_nonempty": {
+ "title": true
+ }
+ },
+ {
+ "position": "bottom",
+ "template_id": "three_persona_benefits",
+ "builder": "items_with_role",
+ "slot_names": [
+ "intro_sections",
+ "personas",
+ "slot_mapping_trace",
+ "title"
+ ],
+ "list_slot_counts": {
+ "intro_sections": 2,
+ "personas": 3
+ },
+ "dict_slot_sub_counts": {
+ "slot_mapping_trace": {
+ "persona_role_labels": 3
}
- ],
- "02": [
- {
- "position": "top",
- "template_id": "construction_goals_three_circle_intersection",
- "builder": "cycle_intersect_3",
- "slot_names": ["circle_1_label", "circle_2_label", "circle_3_label", "intersection", "title"],
- "list_slot_counts": {},
- "dict_slot_sub_counts": {},
- "string_slot_nonempty": {"circle_1_label": true, "circle_2_label": true, "circle_3_label": true, "intersection": false, "title": true}
- },
- {
- "position": "bottom",
- "template_id": "__empty__",
- "builder": null,
- "slot_names": [],
- "list_slot_counts": {},
- "dict_slot_sub_counts": {},
- "string_slot_nonempty": {}
- }
- ],
- "03": [
- {
- "position": "left",
- "template_id": "three_parallel_requirements",
- "builder": "items_with_role",
- "slot_names": ["pillars", "title"],
- "list_slot_counts": {"pillars": 3},
- "dict_slot_sub_counts": {},
- "string_slot_nonempty": {"title": true}
- },
- {
- "position": "right",
- "template_id": "process_product_two_way",
- "builder": "process_product_pair",
- "slot_names": ["banner_left", "banner_right", "process", "product", "title"],
- "list_slot_counts": {},
- "dict_slot_sub_counts": {"process": {"sections": 3}, "product": {"sections": 3}},
- "string_slot_nonempty": {"banner_left": true, "banner_right": true, "title": true}
- }
- ],
- "04": [
- {
- "position": "top",
- "template_id": "bim_issues_quadrant_four",
- "builder": "quadrant_flat_slots",
- "slot_names": ["quadrant_1_body", "quadrant_1_label", "quadrant_2_body", "quadrant_2_label", "quadrant_3_body", "quadrant_3_label", "quadrant_4_body", "quadrant_4_label", "title"],
- "list_slot_counts": {"quadrant_1_body": 2, "quadrant_2_body": 2, "quadrant_3_body": 2, "quadrant_4_body": 2},
- "dict_slot_sub_counts": {},
- "string_slot_nonempty": {"quadrant_1_label": true, "quadrant_2_label": true, "quadrant_3_label": true, "quadrant_4_label": true, "title": true}
- },
- {
- "position": "bottom-left",
- "template_id": "__empty__",
- "builder": null,
- "slot_names": [],
- "list_slot_counts": {},
- "dict_slot_sub_counts": {},
- "string_slot_nonempty": {}
- },
- {
- "position": "bottom-right",
- "template_id": "__empty__",
- "builder": null,
- "slot_names": [],
- "list_slot_counts": {},
- "dict_slot_sub_counts": {},
- "string_slot_nonempty": {}
- }
- ],
- "05": [
- {
- "position": "primary",
- "template_id": "__empty__",
- "builder": null,
- "slot_names": [],
- "list_slot_counts": {},
- "dict_slot_sub_counts": {},
- "string_slot_nonempty": {}
- }
- ]
+ },
+ "string_slot_nonempty": {
+ "title": true
+ }
+ }
+ ],
+ "03": [
+ {
+ "position": "left",
+ "template_id": "three_parallel_requirements",
+ "builder": "items_with_role",
+ "slot_names": [
+ "pillars",
+ "title"
+ ],
+ "list_slot_counts": {
+ "pillars": 3
+ },
+ "dict_slot_sub_counts": {},
+ "string_slot_nonempty": {
+ "title": true
+ }
+ },
+ {
+ "position": "right",
+ "template_id": "bim_dx_comparison_table",
+ "builder": "compare_table_2col",
+ "slot_names": [
+ "col_a_label",
+ "col_b_label",
+ "rows",
+ "title"
+ ],
+ "list_slot_counts": {
+ "rows": 8
+ },
+ "dict_slot_sub_counts": {},
+ "string_slot_nonempty": {
+ "col_a_label": false,
+ "col_b_label": false,
+ "title": true
+ }
+ }
+ ],
+ "04": [
+ {
+ "position": "top",
+ "template_id": "pre_construction_model_info_stacked",
+ "builder": "quadrant_flat_slots",
+ "slot_names": [
+ "_slot_count",
+ "pill_1_body",
+ "pill_1_label",
+ "pill_2_body",
+ "pill_2_label",
+ "pill_3_body",
+ "pill_3_label",
+ "pill_4_body",
+ "pill_4_label",
+ "pill_5_body",
+ "pill_5_label",
+ "title"
+ ],
+ "list_slot_counts": {
+ "pill_1_body": 4,
+ "pill_2_body": 4,
+ "pill_3_body": 4,
+ "pill_4_body": 4,
+ "pill_5_body": 4
+ },
+ "dict_slot_sub_counts": {},
+ "string_slot_nonempty": {
+ "pill_1_label": true,
+ "pill_2_label": true,
+ "pill_3_label": true,
+ "pill_4_label": true,
+ "pill_5_label": true,
+ "title": true
+ }
+ },
+ {
+ "position": "bottom",
+ "template_id": "three_parallel_requirements",
+ "builder": "items_with_role",
+ "slot_names": [
+ "pillars",
+ "title"
+ ],
+ "list_slot_counts": {
+ "pillars": 2
+ },
+ "dict_slot_sub_counts": {},
+ "string_slot_nonempty": {
+ "title": true
+ }
+ }
+ ],
+ "05": [
+ {
+ "position": "top",
+ "template_id": "three_parallel_requirements",
+ "builder": "items_with_role",
+ "slot_names": [
+ "pillars",
+ "title"
+ ],
+ "list_slot_counts": {
+ "pillars": 3
+ },
+ "dict_slot_sub_counts": {},
+ "string_slot_nonempty": {
+ "title": true
+ }
+ },
+ {
+ "position": "bottom",
+ "template_id": "three_parallel_requirements",
+ "builder": "items_with_role",
+ "slot_names": [
+ "pillars",
+ "title"
+ ],
+ "list_slot_counts": {
+ "pillars": 2
+ },
+ "dict_slot_sub_counts": {},
+ "string_slot_nonempty": {
+ "title": true
+ }
+ }
+ ]
}
diff --git a/tests/integration/__snapshots__/structural.json b/tests/integration/__snapshots__/structural.json
index 5e0a85f..cdf3dec 100644
--- a/tests/integration/__snapshots__/structural.json
+++ b/tests/integration/__snapshots__/structural.json
@@ -1,43 +1,77 @@
{
- "_doc": "IMP-#91 u3 structural snapshot — pins observed step20 overall + step09 per-zone selected_template_id per mdx in the 01-05 acceptance set. Each entry is fresh-run evidence (not aspirational). Update only when an intentional pipeline change moves the observed value; treat unexplained drift as regression. [[feedback_validation_first_for_closed_issues]] [[feedback_artifact_status_naming]]",
- "01": {
- "overall": "PASS",
- "zone_count": 2,
- "zones": [
- {"position": "top", "selected_template_id": "bim_dx_comparison_table"},
- {"position": "bottom", "selected_template_id": "construction_bim_three_usage"}
- ]
- },
- "02": {
- "overall": "PASS",
- "zone_count": 2,
- "zones": [
- {"position": "top", "selected_template_id": "construction_goals_three_circle_intersection"},
- {"position": "bottom", "selected_template_id": "three_persona_benefits"}
- ]
- },
- "03": {
- "overall": "PASS",
- "zone_count": 2,
- "zones": [
- {"position": "left", "selected_template_id": "three_parallel_requirements"},
- {"position": "right", "selected_template_id": "process_product_two_way"}
- ]
- },
- "04": {
- "overall": "PASS",
- "zone_count": 3,
- "zones": [
- {"position": "top", "selected_template_id": "bim_issues_quadrant_four"},
- {"position": "bottom-left", "selected_template_id": "sw_dependency_four_problems"},
- {"position": "bottom-right", "selected_template_id": "pre_construction_model_info_stacked"}
- ]
- },
- "05": {
- "overall": "EMPTY_SHELL_NO_CONTENT",
- "zone_count": 1,
- "zones": [
- {"position": "primary", "selected_template_id": "__empty__"}
- ]
- }
+ "_doc": "IMP-#91 u3 structural snapshot — pins observed step20 overall + step09 per-zone selected_template_id per mdx in the 01-05 acceptance set. Each entry is fresh-run evidence (not aspirational). Update only when an intentional pipeline change moves the observed value; treat unexplained drift as regression. [[feedback_validation_first_for_closed_issues]] [[feedback_artifact_status_naming]]",
+ "01": {
+ "overall": "PASS",
+ "zone_count": 3,
+ "zones": [
+ {
+ "position": "top",
+ "selected_template_id": null
+ },
+ {
+ "position": "bottom-left",
+ "selected_template_id": null
+ },
+ {
+ "position": "bottom-right",
+ "selected_template_id": null
+ }
+ ]
+ },
+ "02": {
+ "overall": "PARTIAL_COVERAGE",
+ "zone_count": 2,
+ "zones": [
+ {
+ "position": "top",
+ "selected_template_id": null
+ },
+ {
+ "position": "bottom",
+ "selected_template_id": null
+ }
+ ]
+ },
+ "03": {
+ "overall": "PASS",
+ "zone_count": 2,
+ "zones": [
+ {
+ "position": "left",
+ "selected_template_id": null
+ },
+ {
+ "position": "right",
+ "selected_template_id": null
+ }
+ ]
+ },
+ "04": {
+ "overall": "PARTIAL_COVERAGE",
+ "zone_count": 2,
+ "zones": [
+ {
+ "position": "top",
+ "selected_template_id": null
+ },
+ {
+ "position": "bottom",
+ "selected_template_id": null
+ }
+ ]
+ },
+ "05": {
+ "overall": "PARTIAL_COVERAGE",
+ "zone_count": 2,
+ "zones": [
+ {
+ "position": "top",
+ "selected_template_id": null
+ },
+ {
+ "position": "bottom",
+ "selected_template_id": null
+ }
+ ]
+ }
}
diff --git a/tests/integration/__snapshots__/v4_ranking.json b/tests/integration/__snapshots__/v4_ranking.json
index bdd8f16..cbc768c 100644
--- a/tests/integration/__snapshots__/v4_ranking.json
+++ b/tests/integration/__snapshots__/v4_ranking.json
@@ -1,112 +1,395 @@
{
- "_doc": "IMP-91 u7 — F1 V4 ranking observed snapshot (step05_v4_evidence). Pins v4_source (POSIX-normalized), aligned_section_ids, and per-section {section_id, candidate_status, candidates: [{template_id, label, confidence}]}. confidence kept at current 4-decimal rounding. Sections appear in pipeline-emitted order.",
- "01": {
- "v4_source": "tests/matching/v4_full32_result.yaml",
- "aligned_section_ids": ["01-1", "01-2"],
- "sections": [
- {
- "section_id": "01-1",
- "candidate_status": "ok",
- "candidates": [
- {"template_id": "construction_bim_three_usage", "label": "use_as_is", "confidence": 0.9101},
- {"template_id": "construction_goals_three_circle_intersection", "label": "light_edit", "confidence": 0.8261},
- {"template_id": "dx_sw_necessity_three_perspectives", "label": "light_edit", "confidence": 0.8168}
- ]
- },
- {
- "section_id": "01-2",
- "candidate_status": "ok",
- "candidates": [
- {"template_id": "bim_dx_comparison_table", "label": "use_as_is", "confidence": 0.9459},
- {"template_id": "app_sw_package_vs_solution", "label": "restructure", "confidence": 0.6813}
- ]
- }
+ "_doc": "IMP-91 u7 — F1 V4 ranking observed snapshot (step05_v4_evidence). Pins v4_source (POSIX-normalized), aligned_section_ids, and per-section {section_id, candidate_status, candidates: [{template_id, label, confidence}]}. confidence kept at current 4-decimal rounding. Sections appear in pipeline-emitted order.",
+ "01": {
+ "v4_source": "tests/matching/v4_full32_result.yaml",
+ "aligned_section_ids": [
+ "01-intro",
+ "01-1",
+ "01-2"
+ ],
+ "sections": [
+ {
+ "section_id": "01-intro",
+ "candidate_status": "no_non_reject_v4_candidate",
+ "candidates": [
+ {
+ "template_id": "compensation_complaint_map",
+ "label": "reject",
+ "confidence": 0.651
+ },
+ {
+ "template_id": "construction_bim_three_usage",
+ "label": "reject",
+ "confidence": 0.4696
+ },
+ {
+ "template_id": "engn_sw_three_types",
+ "label": "reject",
+ "confidence": 0.4047
+ },
+ {
+ "template_id": "bim_adoption_central_split",
+ "label": "reject",
+ "confidence": 0.3994
+ },
+ {
+ "template_id": "bim_dx_comparison_table",
+ "label": "reject",
+ "confidence": 0.374
+ },
+ {
+ "template_id": "construction_goals_three_circle_intersection",
+ "label": "reject",
+ "confidence": 0.3418
+ }
]
- },
- "02": {
- "v4_source": "tests/matching/v4_full32_result.yaml",
- "aligned_section_ids": ["02-1", "02-2-sub-1", "02-2-sub-2"],
- "sections": [
- {
- "section_id": "02-1",
- "candidate_status": "ok",
- "candidates": [
- {"template_id": "construction_goals_three_circle_intersection", "label": "use_as_is", "confidence": 0.914}
- ]
- },
- {
- "section_id": "02-2-sub-1",
- "candidate_status": "no_non_reject_v4_candidate",
- "candidates": []
- },
- {
- "section_id": "02-2-sub-2",
- "candidate_status": "no_non_reject_v4_candidate",
- "candidates": []
- }
+ },
+ {
+ "section_id": "01-1",
+ "candidate_status": "ok",
+ "candidates": [
+ {
+ "template_id": "construction_bim_three_usage",
+ "label": "use_as_is",
+ "confidence": 0.9101
+ },
+ {
+ "template_id": "construction_goals_three_circle_intersection",
+ "label": "light_edit",
+ "confidence": 0.8261
+ },
+ {
+ "template_id": "dx_sw_necessity_three_perspectives",
+ "label": "light_edit",
+ "confidence": 0.8168
+ },
+ {
+ "template_id": "model_specialized_engn_sw",
+ "label": "restructure",
+ "confidence": 0.749
+ },
+ {
+ "template_id": "three_parallel_requirements",
+ "label": "reject",
+ "confidence": 0.7402
+ },
+ {
+ "template_id": "info_management_what_how_when",
+ "label": "reject",
+ "confidence": 0.7395
+ }
]
- },
- "03": {
- "v4_source": "tests/matching/v4_full32_result.yaml",
- "aligned_section_ids": ["03-1", "03-2"],
- "sections": [
- {
- "section_id": "03-1",
- "candidate_status": "ok",
- "candidates": [
- {"template_id": "three_parallel_requirements", "label": "use_as_is", "confidence": 0.9268},
- {"template_id": "dx_sw_necessity_three_perspectives", "label": "light_edit", "confidence": 0.8413}
- ]
- },
- {
- "section_id": "03-2",
- "candidate_status": "ok",
- "candidates": [
- {"template_id": "process_product_two_way", "label": "use_as_is", "confidence": 0.9198}
- ]
- }
+ },
+ {
+ "section_id": "01-2",
+ "candidate_status": "ok",
+ "candidates": [
+ {
+ "template_id": "bim_dx_comparison_table",
+ "label": "use_as_is",
+ "confidence": 0.9459
+ },
+ {
+ "template_id": "process_product_two_way",
+ "label": "reject",
+ "confidence": 0.8675
+ },
+ {
+ "template_id": "engn_sw_three_types",
+ "label": "light_edit",
+ "confidence": 0.7571
+ },
+ {
+ "template_id": "app_sw_package_vs_solution",
+ "label": "restructure",
+ "confidence": 0.6813
+ },
+ {
+ "template_id": "bim_adoption_central_split",
+ "label": "restructure",
+ "confidence": 0.6455
+ },
+ {
+ "template_id": "industry_characteristics_three_col",
+ "label": "reject",
+ "confidence": 0.5781
+ }
]
- },
- "04": {
- "v4_source": "tests/matching/v4_full32_result.yaml",
- "aligned_section_ids": ["04-1", "04-2-sub-1", "04-2-sub-2"],
- "sections": [
- {
- "section_id": "04-1",
- "candidate_status": "no_non_reject_v4_candidate",
- "candidates": []
- },
- {
- "section_id": "04-2-sub-1",
- "candidate_status": "no_non_reject_v4_candidate",
- "candidates": []
- },
- {
- "section_id": "04-2-sub-2",
- "candidate_status": "no_non_reject_v4_candidate",
- "candidates": []
- }
+ }
+ ]
+ },
+ "02": {
+ "v4_source": "tests/matching/v4_full32_result.yaml",
+ "aligned_section_ids": [
+ "02-1",
+ "02-2-sub-1",
+ "02-2-sub-2"
+ ],
+ "sections": [
+ {
+ "section_id": "02-1",
+ "candidate_status": "ok",
+ "candidates": [
+ {
+ "template_id": "construction_goals_three_circle_intersection",
+ "label": "use_as_is",
+ "confidence": 0.914
+ },
+ {
+ "template_id": "three_persona_benefits",
+ "label": "reject",
+ "confidence": 0.7656
+ },
+ {
+ "template_id": "dx_sw_necessity_three_perspectives",
+ "label": "reject",
+ "confidence": 0.7512
+ },
+ {
+ "template_id": "model_specialized_engn_sw",
+ "label": "reject",
+ "confidence": 0.7267
+ },
+ {
+ "template_id": "solution_engn_split_diagram",
+ "label": "restructure",
+ "confidence": 0.724
+ },
+ {
+ "template_id": "three_parallel_requirements",
+ "label": "reject",
+ "confidence": 0.6517
+ }
]
- },
- "05": {
- "v4_source": "tests/matching/v4_full32_result.yaml",
- "aligned_section_ids": ["05-1", "05-2-sub-1", "05-2-sub-2"],
- "sections": [
- {
- "section_id": "05-1",
- "candidate_status": "no_non_reject_v4_candidate",
- "candidates": []
- },
- {
- "section_id": "05-2-sub-1",
- "candidate_status": "no_non_reject_v4_candidate",
- "candidates": []
- },
- {
- "section_id": "05-2-sub-2",
- "candidate_status": "no_non_reject_v4_candidate",
- "candidates": []
- }
+ },
+ {
+ "section_id": "02-2-sub-1",
+ "candidate_status": "no_non_reject_v4_candidate",
+ "candidates": []
+ },
+ {
+ "section_id": "02-2-sub-2",
+ "candidate_status": "ok",
+ "candidates": []
+ }
+ ]
+ },
+ "03": {
+ "v4_source": "tests/matching/v4_full32_result.yaml",
+ "aligned_section_ids": [
+ "03-1",
+ "03-2"
+ ],
+ "sections": [
+ {
+ "section_id": "03-1",
+ "candidate_status": "ok",
+ "candidates": [
+ {
+ "template_id": "three_parallel_requirements",
+ "label": "use_as_is",
+ "confidence": 0.9268
+ },
+ {
+ "template_id": "dx_sw_necessity_three_perspectives",
+ "label": "light_edit",
+ "confidence": 0.8413
+ },
+ {
+ "template_id": "sw_reality_three_emphasis",
+ "label": "reject",
+ "confidence": 0.7203
+ },
+ {
+ "template_id": "solution_engn_split_diagram",
+ "label": "reject",
+ "confidence": 0.6631
+ },
+ {
+ "template_id": "three_persona_benefits",
+ "label": "reject",
+ "confidence": 0.6548
+ },
+ {
+ "template_id": "industry_current_status_three_col",
+ "label": "reject",
+ "confidence": 0.6048
+ }
]
- }
+ },
+ {
+ "section_id": "03-2",
+ "candidate_status": "ok",
+ "candidates": [
+ {
+ "template_id": "process_product_two_way",
+ "label": "use_as_is",
+ "confidence": 0.9198
+ },
+ {
+ "template_id": "bim_dx_comparison_table",
+ "label": "reject",
+ "confidence": 0.8138
+ },
+ {
+ "template_id": "app_sw_package_vs_solution",
+ "label": "reject",
+ "confidence": 0.6467
+ },
+ {
+ "template_id": "engn_sw_three_types",
+ "label": "reject",
+ "confidence": 0.6396
+ },
+ {
+ "template_id": "bim_adoption_central_split",
+ "label": "restructure",
+ "confidence": 0.6278
+ },
+ {
+ "template_id": "industry_current_status_three_col",
+ "label": "reject",
+ "confidence": 0.5324
+ }
+ ]
+ }
+ ]
+ },
+ "04": {
+ "v4_source": "tests/matching/v4_full32_result.yaml",
+ "aligned_section_ids": [
+ "04-1",
+ "04-2-sub-1",
+ "04-2-sub-2"
+ ],
+ "sections": [
+ {
+ "section_id": "04-1",
+ "candidate_status": "no_non_reject_v4_candidate",
+ "candidates": [
+ {
+ "template_id": "pre_construction_model_info_stacked",
+ "label": "reject",
+ "confidence": 0.7114
+ },
+ {
+ "template_id": "field_effectiveness_five_elements",
+ "label": "reject",
+ "confidence": 0.6453
+ },
+ {
+ "template_id": "policy_achievement_five_goals",
+ "label": "reject",
+ "confidence": 0.6412
+ },
+ {
+ "template_id": "compensation_complaint_side_card",
+ "label": "reject",
+ "confidence": 0.5946
+ },
+ {
+ "template_id": "solution_engn_split_diagram",
+ "label": "reject",
+ "confidence": 0.4588
+ },
+ {
+ "template_id": "model_specialized_engn_sw",
+ "label": "reject",
+ "confidence": 0.4502
+ }
+ ]
+ },
+ {
+ "section_id": "04-2-sub-1",
+ "candidate_status": "no_non_reject_v4_candidate",
+ "candidates": []
+ },
+ {
+ "section_id": "04-2-sub-2",
+ "candidate_status": "ok",
+ "candidates": []
+ }
+ ]
+ },
+ "05": {
+ "v4_source": "tests/matching/v4_full32_result.yaml",
+ "aligned_section_ids": [
+ "05-1",
+ "05-2"
+ ],
+ "sections": [
+ {
+ "section_id": "05-1",
+ "candidate_status": "ok",
+ "candidates": [
+ {
+ "template_id": "dx_sw_necessity_three_perspectives",
+ "label": "light_edit",
+ "confidence": 0.77
+ },
+ {
+ "template_id": "model_specialized_engn_sw",
+ "label": "restructure",
+ "confidence": 0.7333
+ },
+ {
+ "template_id": "sw_reality_three_emphasis",
+ "label": "reject",
+ "confidence": 0.7265
+ },
+ {
+ "template_id": "three_parallel_requirements",
+ "label": "reject",
+ "confidence": 0.7198
+ },
+ {
+ "template_id": "solution_engn_split_diagram",
+ "label": "restructure",
+ "confidence": 0.7011
+ },
+ {
+ "template_id": "industry_characteristics_three_col",
+ "label": "reject",
+ "confidence": 0.6693
+ }
+ ]
+ },
+ {
+ "section_id": "05-2",
+ "candidate_status": "no_non_reject_v4_candidate",
+ "candidates": [
+ {
+ "template_id": "compensation_complaint_map",
+ "label": "reject",
+ "confidence": 0.5526
+ },
+ {
+ "template_id": "design_method_distortion_three_col",
+ "label": "reject",
+ "confidence": 0.3993
+ },
+ {
+ "template_id": "engn_sw_three_types",
+ "label": "reject",
+ "confidence": 0.3487
+ },
+ {
+ "template_id": "bigroom_system_components",
+ "label": "reject",
+ "confidence": 0.3364
+ },
+ {
+ "template_id": "sw_dependency_four_problems",
+ "label": "reject",
+ "confidence": 0.2968
+ },
+ {
+ "template_id": "app_sw_package_vs_solution",
+ "label": "reject",
+ "confidence": 0.2555
+ }
+ ]
+ }
+ ]
+ }
}
diff --git a/tests/integration/__snapshots__/visual.json b/tests/integration/__snapshots__/visual.json
index c0a0139..72e4a56 100644
--- a/tests/integration/__snapshots__/visual.json
+++ b/tests/integration/__snapshots__/visual.json
@@ -1,48 +1,104 @@
{
- "_doc": "u4 — pin observed step14_visual_check overflow/clip per mdx 01-05. Fresh subprocess observation per [[feedback_validation_first_for_closed_issues]]; drift surfaces visual regression (overflow / clip) loudly per [[feedback_artifact_status_naming]] 3-axis honesty. Snapshot pinned to current-state, not to invented expectation (Stage 1 scope-lock).",
- "01": {
- "slide_overflowed": false,
- "slide_body_overflowed": false,
- "passed": true,
- "zones": [
- {"position": "top", "template_id": "bim_dx_comparison_table", "overflowed": false, "clipped_inner_count": 0},
- {"position": "bottom", "template_id": "construction_bim_three_usage", "overflowed": false, "clipped_inner_count": 0}
- ]
- },
- "02": {
- "slide_overflowed": false,
- "slide_body_overflowed": false,
- "passed": true,
- "zones": [
- {"position": "top", "template_id": "construction_goals_three_circle_intersection", "overflowed": false, "clipped_inner_count": 0},
- {"position": "bottom", "template_id": "__empty__", "overflowed": false, "clipped_inner_count": 0}
- ]
- },
- "03": {
- "slide_overflowed": false,
- "slide_body_overflowed": false,
- "passed": true,
- "zones": [
- {"position": "left", "template_id": "three_parallel_requirements", "overflowed": false, "clipped_inner_count": 0},
- {"position": "right", "template_id": "process_product_two_way", "overflowed": false, "clipped_inner_count": 0}
- ]
- },
- "04": {
- "slide_overflowed": false,
- "slide_body_overflowed": false,
- "passed": true,
- "zones": [
- {"position": "top", "template_id": "bim_issues_quadrant_four", "overflowed": false, "clipped_inner_count": 0},
- {"position": "bottom-left", "template_id": "__empty__", "overflowed": false, "clipped_inner_count": 0},
- {"position": "bottom-right", "template_id": "__empty__", "overflowed": false, "clipped_inner_count": 0}
- ]
- },
- "05": {
- "slide_overflowed": false,
- "slide_body_overflowed": false,
- "passed": true,
- "zones": [
- {"position": "primary", "template_id": "__empty__", "overflowed": false, "clipped_inner_count": 0}
- ]
- }
+ "_doc": "u4 — pin observed step14_visual_check overflow/clip per mdx 01-05. Fresh subprocess observation per [[feedback_validation_first_for_closed_issues]]; drift surfaces visual regression (overflow / clip) loudly per [[feedback_artifact_status_naming]] 3-axis honesty. Snapshot pinned to current-state, not to invented expectation (Stage 1 scope-lock).",
+ "01": {
+ "slide_overflowed": false,
+ "slide_body_overflowed": false,
+ "passed": true,
+ "zones": [
+ {
+ "position": "top",
+ "template_id": "bim_dx_comparison_table",
+ "overflowed": false,
+ "clipped_inner_count": 0
+ },
+ {
+ "position": "bottom-left",
+ "template_id": "construction_bim_three_usage",
+ "overflowed": false,
+ "clipped_inner_count": 0
+ },
+ {
+ "position": "bottom-right",
+ "template_id": "bim_dx_comparison_table",
+ "overflowed": false,
+ "clipped_inner_count": 0
+ }
+ ]
+ },
+ "02": {
+ "slide_overflowed": false,
+ "slide_body_overflowed": false,
+ "passed": true,
+ "zones": [
+ {
+ "position": "top",
+ "template_id": "three_parallel_requirements",
+ "overflowed": false,
+ "clipped_inner_count": 0
+ },
+ {
+ "position": "bottom",
+ "template_id": "three_persona_benefits",
+ "overflowed": false,
+ "clipped_inner_count": 0
+ }
+ ]
+ },
+ "03": {
+ "slide_overflowed": false,
+ "slide_body_overflowed": false,
+ "passed": true,
+ "zones": [
+ {
+ "position": "left",
+ "template_id": "three_parallel_requirements",
+ "overflowed": false,
+ "clipped_inner_count": 0
+ },
+ {
+ "position": "right",
+ "template_id": "bim_dx_comparison_table",
+ "overflowed": false,
+ "clipped_inner_count": 0
+ }
+ ]
+ },
+ "04": {
+ "slide_overflowed": false,
+ "slide_body_overflowed": false,
+ "passed": false,
+ "zones": [
+ {
+ "position": "top",
+ "template_id": "pre_construction_model_info_stacked",
+ "overflowed": false,
+ "clipped_inner_count": 0
+ },
+ {
+ "position": "bottom",
+ "template_id": "three_parallel_requirements",
+ "overflowed": false,
+ "clipped_inner_count": 2
+ }
+ ]
+ },
+ "05": {
+ "slide_overflowed": false,
+ "slide_body_overflowed": false,
+ "passed": true,
+ "zones": [
+ {
+ "position": "top",
+ "template_id": "three_parallel_requirements",
+ "overflowed": false,
+ "clipped_inner_count": 0
+ },
+ {
+ "position": "bottom",
+ "template_id": "three_parallel_requirements",
+ "overflowed": false,
+ "clipped_inner_count": 0
+ }
+ ]
+ }
}
diff --git a/tests/integration/scripts/__init__.py b/tests/integration/scripts/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/tests/integration/scripts/regenerate_snapshots.py b/tests/integration/scripts/regenerate_snapshots.py
new file mode 100644
index 0000000..865ff15
--- /dev/null
+++ b/tests/integration/scripts/regenerate_snapshots.py
@@ -0,0 +1,257 @@
+"""IMP-91 스냅샷 재생성 절차 (GitHub issue #29).
+
+structural.json 의 _doc 계약: "Update only when an intentional pipeline
+change moves the observed value" — 의도적 변경 후 이 스크립트로 갱신한다.
+테스트 모듈의 실행 함수(run_pipeline_for_snapshot — AI OFF 강제)와 shape
+헬퍼(_slot_payload_zone_shape 등)를 그대로 import 해 추출 로직 표류를 방지.
+
+실행: python -m tests.integration.scripts.regenerate_snapshots
+"""
+from __future__ import annotations
+
+import json
+import re
+import sys
+import uuid
+from pathlib import Path
+
+REPO_ROOT = Path(__file__).resolve().parents[3]
+sys.path.insert(0, str(REPO_ROOT))
+
+from tests.integration.test_multi_mdx_regression import ( # noqa: E402
+ MDX_SET,
+ SNAPSHOTS_DIR,
+ _AI_UNIT_KEYS,
+ _SLIDE_ROOT_RE,
+ _TITLE_RE,
+ _extract_html_zone_topology,
+ _layout_zone_shape,
+ _slot_payload_zone_shape,
+ run_pipeline_for_snapshot,
+)
+
+
+def _j(run_dir: Path, name: str) -> dict:
+ return json.loads((run_dir / "steps" / name).read_text(encoding="utf-8"))
+
+
+def extract_structural(run_dir: Path) -> dict:
+ status = _j(run_dir, "step20_slide_status.json")["data"]
+ frame_sel = _j(run_dir, "step09_frame_selection.json")["data"]
+ zones = frame_sel.get("per_zone", [])
+ return {
+ "overall": status.get("overall"),
+ "zone_count": len(zones),
+ "zones": [
+ {"position": z.get("position"),
+ "selected_template_id": z.get("selected_template_id")}
+ for z in zones
+ ],
+ }
+
+
+def extract_visual(run_dir: Path) -> dict:
+ visual = _j(run_dir, "step14_visual_check.json")["data"]
+ return {
+ "slide_overflowed": visual.get("slide", {}).get("overflowed"),
+ "slide_body_overflowed": visual.get("slide_body", {}).get("overflowed"),
+ "passed": visual.get("passed"),
+ "zones": [
+ {
+ "position": z.get("position"),
+ "template_id": z.get("template_id"),
+ "overflowed": z.get("overflowed"),
+ "clipped_inner_count": len(z.get("clipped_inner") or []),
+ }
+ for z in visual.get("zones", [])
+ ],
+ }
+
+
+def extract_coverage(run_dir: Path) -> dict:
+ status = _j(run_dir, "step20_slide_status.json")["data"]
+ return {
+ "rendered": status.get("rendered"),
+ "visual_check_passed": status.get("visual_check_passed"),
+ "full_mdx_coverage": status.get("full_mdx_coverage"),
+ "aligned_section_ids": sorted(status.get("aligned_section_ids") or []),
+ "covered_section_ids": sorted(status.get("covered_section_ids") or []),
+ "filtered_section_ids": sorted(status.get("filtered_section_ids") or []),
+ }
+
+
+def extract_normalize(run_dir: Path) -> dict:
+ raw = _j(run_dir, "step02_normalized.json")
+ d = raw["data"]
+ diag = d.get("stage0_adapter_diagnostics", {}) or {}
+ assets = d.get("stage0_normalized_assets", {}) or {}
+ return {
+ "step_num": raw.get("step_num"),
+ "step_status": raw.get("step_status"),
+ "pipeline_path_connected": raw.get("pipeline_path_connected"),
+ "sections_count": d.get("sections_count"),
+ "section_ids": [s.get("section_id") for s in d.get("sections", [])],
+ "orphans_count": len(d.get("orphans") or []),
+ "details_count": len(d.get("details") or []),
+ "adapter_enabled": diag.get("enabled"),
+ "adapter_used": diag.get("used"),
+ "assets_popups_count": len(assets.get("popups") or []),
+ "assets_images_count": len(assets.get("images") or []),
+ "assets_tables_count": len(assets.get("tables") or []),
+ "slide_title_nonempty": bool(d.get("slide_title")),
+ "slide_footer_nonempty": bool(d.get("slide_footer")),
+ }
+
+
+def extract_v4_ranking(run_dir: Path) -> dict:
+ data = _j(run_dir, "step05_v4_evidence.json")["data"]
+ return {
+ "v4_source": str(data.get("v4_source") or "").replace("\\", "/"),
+ "aligned_section_ids": data.get("aligned_section_ids"),
+ "sections": [
+ {
+ "section_id": ev.get("section_id"),
+ "candidate_status": ev.get("candidate_status"),
+ "candidates": [
+ {
+ "template_id": c.get("template_id"),
+ "label": c.get("label"),
+ "confidence": c.get("confidence"),
+ }
+ for c in (ev.get("v4_candidates") or [])
+ ],
+ }
+ for ev in (data.get("evidence_per_section") or [])
+ ],
+ }
+
+
+def extract_ai_classifier(run_dir: Path) -> dict:
+ ai = _j(run_dir, "step12_ai_repair.json")["data"]
+ fit = _j(run_dir, "step15_fit_classification.json")["data"]
+ router = _j(run_dir, "step16_router_decision.json")["data"]
+ failure = _j(run_dir, "step18_failure_classification.json")["data"]
+ units = [{k: u.get(k) for k in _AI_UNIT_KEYS} for u in (ai.get("per_unit") or [])]
+ return {
+ "units": units,
+ "coverage_invariant_status": (ai.get("coverage_invariant") or {}).get("status"),
+ "fit_visual_check_passed": fit.get("visual_check_passed"),
+ "fit_classifications_count": len(fit.get("classifications") or []),
+ "fit_categories_seen": fit.get("categories_seen") or [],
+ "router_active": router.get("router_active"),
+ "router_routed_count": router.get("routed_count"),
+ "router_v4_fallback_used_count": (router.get("v4_fallback_summary") or {}).get("fallback_used_count"),
+ "failure_type": failure.get("failure_type"),
+ }
+
+
+def extract_layout(run_dir: Path) -> dict:
+ s7 = _j(run_dir, "step07_layout.json")
+ s8 = _j(run_dir, "step08_zone_region_ratios.json")
+ d7 = s7.get("data") or {}
+ d8 = s8.get("data") or {}
+ css = d7.get("layout_css") or {}
+ return {
+ "step7_step_status": s7.get("step_status"),
+ "step7_pipeline_path_connected": s7.get("pipeline_path_connected"),
+ "layout_preset": d7.get("layout_preset"),
+ "auto_layout_preset": d7.get("auto_layout_preset"),
+ "layout_override_applied": d7.get("layout_override_applied"),
+ "zones_count": d7.get("zones_count"),
+ "unit_count": d7.get("unit_count"),
+ "layout_candidates": d7.get("layout_candidates") or [],
+ "computation": css.get("computation"),
+ "dynamic_rows": css.get("dynamic_rows"),
+ "dynamic_cols": css.get("dynamic_cols"),
+ "heights_px": css.get("heights_px"),
+ "widths_px": css.get("widths_px"),
+ "ratios": css.get("ratios"),
+ "width_ratios": css.get("width_ratios"),
+ "step8_step_status": s8.get("step_status"),
+ "step8_pipeline_path_connected": s8.get("pipeline_path_connected"),
+ "zone_heights_px_planned": d8.get("zone_heights_px_planned"),
+ "zone_widths_px_planned": d8.get("zone_widths_px_planned"),
+ "zone_col_ratios_planned": d8.get("zone_col_ratios_planned"),
+ "per_zone_layout_shape": [
+ _layout_zone_shape(z) for z in (d8.get("per_zone_plan") or [])
+ ],
+ }
+
+
+def extract_slot_payload(run_dir: Path) -> list:
+ raw = _j(run_dir, "step12_slot_payload.json")
+ return [_slot_payload_zone_shape(z) for z in raw["data"].get("per_zone") or []]
+
+
+def extract_final_html(run_dir: Path) -> dict:
+ raw13 = _j(run_dir, "step13_render.json")
+ d13 = raw13.get("data") or {}
+ ri = d13.get("render_inputs") or {}
+ final_path = run_dir / "final.html"
+ html = final_path.read_text(encoding="utf-8")
+ title_match = _TITLE_RE.search(html)
+ html_title = title_match.group(1).strip() if title_match else ""
+ html_topology = _extract_html_zone_topology(html)
+ return {
+ "step13_status": raw13.get("step_status"),
+ "step13_pipeline_path_connected": raw13.get("pipeline_path_connected"),
+ "render_inputs_zones_count": ri.get("zones_count"),
+ "render_inputs_layout_preset": ri.get("layout_preset"),
+ "render_inputs_slide_title_nonempty": bool((ri.get("slide_title") or "").strip()),
+ "render_inputs_slide_footer_nonempty": bool((ri.get("slide_footer") or "").strip()),
+ "html_title_matches_render_input": html_title == (ri.get("slide_title") or "").strip(),
+ "html_slide_root_count": len(_SLIDE_ROOT_RE.findall(html)),
+ "html_slide_footer_present": '