diff --git a/IMPROVEMENT-REDESIGN.md b/IMPROVEMENT-REDESIGN.md new file mode 100644 index 0000000..0e3884f --- /dev/null +++ b/IMPROVEMENT-REDESIGN.md @@ -0,0 +1,578 @@ +# Pipeline 개선 — 매칭 시스템 통합 (Redesign) + +> **작성일**: 2026-04-28 +> **목적**: 매칭 시스템 (V1~V4) 테스트 결과를 기존 design_agent pipeline 에 통합. AI 호출을 줄이고 결정론적 매칭 + 정밀한 공간 관리를 결합. +> **상태**: 설계 단계. 실행은 사용자 승인 후 단계적 진행. + +--- + +## 1. 배경 / 문제의식 + +### 기존 pipeline 의 한계 + +| 문제 | 현황 | +|---|---| +| AI 호출 과다 | Stage 1A / 1B / 1B-ST / 1.7 / 1.8 / 2 / 4 — 5~6 회 호출 | +| Type A/B/B'/B'' 하드코딩 | 콘텐츠별 전용 코드 분기 (오답노트 #2 위반 우려) | +| 블록 catalog 50+ 개 vs 실제 사용 7.9% | Phase P 결과 — 다양성 부족 | +| 유령 블록 / overflow 출력 | Phase Q 에서 일부 해결 | + +### 매칭 시스템 (V1~V4) 테스트 결과 + +`tests/matching/` 에서 별도 검증한 결과: + +| 항목 | 결과 | +|---|---| +| TARGET 정답률 | 3/4 (75%) — Logistic Regression 가중치 학습 + LOOCV 4/4 검증 | +| BM25 / IDF 비교 | 현 방식 우위 (4/4 vs 3/4) | +| V4 slot 축 ablation | Top-1 매칭 7/7 동일 — slot 축 frame 선별 무영향 확인 | +| 발견된 약점 | 8 가지 (`tests/PROGRESS.md` 참조) | + +### 통합 필요성 + +매칭 시스템이 **콘텐츠 분석 / 디자인 추천** 부분을 코드로 대체 가능함을 확인. 단, 1280×720 안 정밀 공간 관리는 매칭 시스템이 다루지 않으므로 기존 pipeline 의 공간 관리 로직 (`space_allocator`, `fit_verifier`, `slide_measurer`) 은 유지 필요. + +→ **콘텐츠 분석은 매칭 시스템, 공간 관리는 기존 pipeline** 으로 책임 분리. + +--- + +## 2. 영역 분리 — 3 가지 + +| 영역 | 무엇을 하나 | 누가 담당 | +|---|---|---| +| **A. 콘텐츠 분석 / 디자인 추천** | "이 MDX 콘텐츠에 어떤 디자인이 어울리나?" | **매칭 시스템 V1~V4 (신규)** | +| **B. 공간 관리 / 사이즈 조정** | "1280×720 안에 어떻게 끼워 넣나? overflow 안 나게?" | **기존 pipeline (유지)** | +| **C. HTML 생성 / 슬롯 채움** | "선택된 디자인에 MDX 텍스트를 어떻게 넣나?" | **기존 + V4 label 분기** | + +### 핵심 원칙 + +- A 는 매칭 시스템으로 완전 대체 (AI 호출 줄임) +- B 는 절대 손대지 않음 (정밀한 공간 관리는 기존 로직이 검증됨) +- C 는 매칭 결과의 label 에 따라 분기 (코드 / AI 1회 / Sonnet 재구성) + +--- + +## 3. 핵심 결정 사항 (확정) + +### 3.1 처리 단위 + +``` +MDX 1 파일 = 대목차 1개 = 슬라이드 1장 + └ 중목차들 = 슬라이드 안의 frame 들 (조합) + └ 소목차들 = 더 작은 단위 frame 또는 슬롯 +``` + +### 3.2 매칭 알고리즘 — 소→중→대 합치기 룰 + +``` +[1단계] 모든 소목차에 V1~V4 매칭 시도 + 각 소목차의 best frame 도출 + +[2단계] 같은 중목차 내 소목차 매칭이 비슷하면 → 중목차로 묶어서 다시 매칭 + 더 큰 frame 단위로 합치기 가능한지 확인 + +[3단계] 중목차들이 비슷하면 → 대목차로 묶어서 매칭 + 슬라이드 1장 전체를 1 frame 으로 처리 가능한지 확인 + +→ 가장 적합한 합치기 단위 + frame 조합 채택 +``` + +### 3.3 레이아웃 프리셋 + +기존 design_agent 의 4 가지 Type 으로 시작: + +| 프리셋 | 조건 | 비고 | +|---|---|---| +| Type A | 참조 자료 (용어 정의 등) 별도 존재 | 기존 그대로 유지 (오답노트 #7) | +| Type B | 본문 흐름만 | 기존 그대로 | +| Type B' | 카드형 + 표 | 기존 그대로 | +| Type B'' | 색상바 + 여백 분리 | 기존 그대로 | + +→ 4 개로 시작. 운영하면서 부족하면 추가. + +### 3.4 V4 label 분기 + +매칭 결과의 label 이 후속 작업 비용을 결정: + +| label | confidence | 후속 작업 | 비용 | +|---|---|---|---| +| use_as_is | ≥ 0.90 | 슬롯에 텍스트만 매핑 | 자동, AI 0회 | +| light_edit | ≥ 0.75 | AI 가 슬롯 텍스트 다듬기 | AI 1회 | +| restructure | ≥ 0.60 | 가장 유사 frame 참고 + Sonnet 변형 | 사람 + AI | +| reject | < 0.60 | 대안 frame 시도 → 그래도 안 되면 유사 frame 참고 | 사람 + AI | + +### 3.5 자유 HTML 생성 금지 + +→ 항상 frame DB 의 어떤 frame 을 참고하여 생성. Phase R' 식 자유 디자인은 X. + +### 3.6 텍스트 원문 보존 원칙 (절대 룰) + +> **슬라이드 본문은 preview / 요약 가능하지만, MDX 원문은 popup / detail 에 무손실 보존한다.** + +- 슬라이드 본문에 표시되는 것 = 일부 / 핵심 / preview 가능 +- 단, 원문이 잘리거나 사라지지 않음 — 항상 팝업 / "자세히 보기" 에 무손실 저장 +- "텍스트 압축 / trim / restructure" 같은 처리 금지 (오답노트 #5 준수) +- 안 들어가면 슬라이드를 키우는 게 아니라 → 본문은 preview, 팝업에 원문 보존 +- AI 가 텍스트를 "줄여서 채워 넣지" 않는다 — preview 로 가공 후, 원문은 detail 에 보존 + +### 3.7 팝업 처리 룰 + +- MDX 에 팝업 콘텐츠 (`
`) 가 있으면 별도 보존 +- 슬라이드 1장 룰 절대 유지 (2장으로 분리 X) +- 처리: **본문 요약 / 일부만 표시 + "자세히 보기" 클릭 → 팝업으로 전체** +- 전체를 팝업으로 빼는 것 X +- 팝업 콘텐츠도 MDX 원문 무손실 (3.6 원칙 적용) + +--- + +## 4. 위계 + 용어 정리 + +### 4.1 슬라이드 위계 + +``` +[ 슬라이드 ] 1280 × 720 + │ + ├─ slide-title ← MDX 대목차 제목 (자동 매핑) + ├─ slide-divider (고정) + │ + ├─ slide-body ≈ 1200 × 590 ─ ★ 여기에 콘텐츠 + │ │ + │ └─ 레이아웃 (Type A / B / B' / B'') + │ │ + │ └─ Zone (top / bottom_l / bottom_r 등) + │ │ + │ └─ 프레임 (Figma 디자인) + │ + └─ slide-footer ← MDX 대목차 결론 (자동 매핑) +``` + +### 4.2 용어 + +| 용어 | 의미 | 단위 / 위치 | +|---|---|---| +| **슬라이드** | 1280×720 한 장 | 가장 큰 단위 | +| **slide-base** | 배경 + 제목 + 구분선 + 결론 pill (모든 슬라이드 공통 그릇) | `templates/blocks/slide-base.html` 고정 | +| **slide-body** | 본문 가용 영역 (≈ 1200×590) | slide-base 안 | +| **레이아웃 (Layout)** | Type A / B / B' / B'' (4 가지 프리셋) | slide-body 안 | +| **Zone (영역)** | 레이아웃이 결정한 콘텐츠 구역 (top / bottom 등) | 레이아웃 안 | +| **컨테이너 (Container)** | zone 의 px 명세 (코드 레벨 표현) | zone 의 구체화 | +| **프레임 (Frame)** | Figma 디자인 단위 (= 기존 "블록") | zone 안 | + +### 4.3 MDX → 슬라이드 매핑 + +| MDX 위치 | 슬라이드 위치 | +|---|---| +| `# 대목차 제목` | `slide-title` | +| 본문 (`##` / `###` 중·소목차) | `slide-body` 안 — 레이아웃 / zone / 프레임 | +| `# 대목차 결론` (있을 시) | `slide-footer` | +| `
` 팝업 콘텐츠 | 슬라이드 위 별도 레이어 | + +### 4.4 매칭 시스템 (V1~V4) 의 frame 단위 — 명확화 + +- 매칭 시스템에서 **frame 32 개를 슬라이드 1장 단위로 검증** 함 (예: Frame 18 = BIM/DX 비교 슬라이드 통째로) +- 새 흐름에서는 **frame = zone 안 디자인 단위** +- → frame DB 라벨링 재검토 필요 (사이즈 분류: "슬라이드 전체 / zone 1 개 / 작은 박스") +- 8.1 항목 (32 frame 사이즈 라벨링) 의 핵심 작업 + +--- + +## 5. 새 Pipeline Flow + +``` +═══════════════════════════════════════════════════════════════ +[전처리 — 한 번만, 결과 캐싱] +═══════════════════════════════════════════════════════════════ + +T-0. Figma frame DB 구축 + · 각 frame 의 키워드 (핵심/세트/연관) 라벨링 ✅ 완료 + · 각 frame 의 구조 (layout, family, relation_type, + cardinality, content_affinity, + structure_intent, slots, + alternative_patterns) 라벨링 ✅ 완료 + · 각 frame 의 사이즈 비율 (전체 / 절반 / 1/3 / 박스) ✅ 대체 완료 (Zone 적용 분류 — `docs/architecture/FRAME-INTEGRATION-MAP.md` 의 `zone_direct/adapt/extract/reference_only`) + · 각 frame 의 스타일 / 시각 언어 인벤토리 ✅ 완료 (`docs/architecture/PHASE-Z-FRAME-STYLE-INVENTORY.md`) + +T-1. (새 frame 추가 시) 자동 라벨링 룰 + · 새 Figma frame 입력 → V3 의 detect_mdx_v2_profile 로 자동 감지 + · 사람이 검수 후 DB 추가 + +T-2. 레이아웃 프리셋 정의 (4 개) + · 각 프리셋의 grid / area 정의 + · 어떤 frame 사이즈 조합에 어울리나 메타 정보 + +═══════════════════════════════════════════════════════════════ +[실행 — MDX 1 파일 처리 = 슬라이드 1 장 생성] +═══════════════════════════════════════════════════════════════ + +STAGE 1) MDX 분석 + 레이아웃 매칭 [코드] + · MDX 정규화 + 외부 컴포넌트 인라인 (Phase Y) + · 팝업 콘텐츠 (
) 별도 보존 + · 대 / 중 / 소목차 트리 추출 + · 콘텐츠 성격 분석 (V3 의 detect_mdx_v2_profile) + · 4 가지 레이아웃 (Type A / B / B' / B'') 중 1 개 결정 + · MDX 대목차 제목 → slide-title + · MDX 대목차 결론 → slide-footer + +STAGE 2) Zone 별 텍스트 1 차 배치 [코드] + · 레이아웃이 결정한 zone 들에 콘텐츠 분배 + · 어떤 중 / 소목차가 어떤 zone 에 갈지 결정 + · 컨테이너 (zone 의 px) 계산 — space_allocator.py + +STAGE 3) Zone 별 프레임 매칭 (V1~V4 매칭 시스템) [코드 + label 분기 AI] + ┌──────────────────────────────────────────────┐ + │ 각 zone 의 콘텐츠와 Figma 프레임 DB 매칭 │ + │ V1 키워드 → V2 의미 → V3 구조 → V4 종합 판정 │ + │ │ + │ 매칭 결과 분기: │ + │ ├ 매칭 완벽 (use_as_is) → 텍스트 업데이트 │ + │ ├ 매칭 어정쩡 (light_edit │ + │ │ / restructure) → 디자인 참고 │ + │ │ 재구성 (Sonnet) │ + │ └ 매칭 안 됨 (reject) → 디자인 컨셉 │ + │ 바탕 재구성 │ + │ │ + │ 자유 디자인 금지 — 항상 프레임 DB 참고 │ + └──────────────────────────────────────────────┘ + +STAGE 4) 프레임 내용 검토 + 컨테이너 조정 [코드 + AI fallback] + · zone 별 검증 (넘침 / 공란 / 적정 / 불균형) + · 보정: + ├ 넘침 → 본문 미리보기 + "자세히 보기" 팝업 + │ (MDX 원문 절대 줄이지 않음 — 3.6 원칙) + ├ 공란 → MDX 팝업 콘텐츠 있으면 일부 끌어옴 + ├ 적정 → 그대로 + └ 불균형 → 레이아웃 자체 부적절 → STAGE 1 회귀 + (5 차 Fallback — 6 장 참조) + +STAGE 5) HTML 조립 + 검증 + 출력 [코드 + AI] + · slide-base.html (배경 + 제목 + 구분선 + 결론 pill) + + slide-body 안 (레이아웃 + zone + 프레임) + + slide-title / slide-footer 채움 + + 팝업 레이어 + · Jinja2 렌더링 + · 실측 검증 (Selenium) — 넘침 차단 + · 시각 검증 (Vision — Opus) — 품질 평가 + · final.html 출력 +``` + +--- + +## 6. 컨테이너 검증 + Fallback 룰 (STAGE 4 상세) + +### 6.1 검증 케이스 분류 + +``` +[모든 컨테이너 검증 결과 종합] + +케이스 1) 모두 적정 + → 다음 단계 진행 + +케이스 2) 일부 Overflow + 나머지 적정 + → 본문 요약 + "자세히 보기" 팝업 처리 + +케이스 3) 일부 Underflow (특정 컨테이너만 공란) + 나머지 정상 + ❗ 레이아웃 잘못 → STAGE 1 회귀 (레이아웃 재선택) + +케이스 4) 일부 Overflow + 일부 Underflow 동시 + ❗ 레이아웃 잘못 (불균형) → STAGE 1 회귀 (레이아웃 재선택) + +케이스 5) 모두 Underflow + 채울 콘텐츠 없음 + ✅ 공란 허용 (콘텐츠 자체가 적은 슬라이드) +``` + +### 6.2 5 차 Fallback (불균형 검출 시) + +``` +[1차] 매칭 → 적합 프리셋 → 검증 + OK → 종료 + 불균형 → 2차 + +[2차] 다른 프리셋 시도 (예: Type B → Type B') + 또는 frame 합치기 단위 변경 (소→중 또는 중→대) + OK → 종료 + 불균형 → 3차 + +[3차] 매칭 결과 자체 변경 (Top-1 → Top-2 frame) + OK → 종료 + 불균형 → 4차 + +[4차] AI 콘텐츠 조정 의뢰 (Sonnet 1회) + 현재 매칭된 frame / zone / container 는 유지한 채, + zone 안 콘텐츠 분량과 슬롯 매핑만 AI 로 조정한다. + · 긴 텍스트 → 본문 preview + 팝업 원문 분리 제안 + · 슬롯 의미 매핑 미세 조정 + · 불필요한 반복 표현을 preview 에서 정리 + · 원문은 popup / detail 에 무손실 보존 + ⚠️ AI 는 zone 안 콘텐츠만 조정한다. + ⚠️ 레이아웃 / 프리셋 / 새 frame 결정 / HTML 구조 생성은 코드만 수행한다. + OK → 종료 + 안 되면 → 5차 + +[5차] 단일 프리셋 강제 (Type A 또는 가장 큰 frame 만 사용) + 나머지 콘텐츠 → "자세히 보기" 팝업 + OK → 종료 + 그래도 안 되면 → 사람 개입 알림 +``` + +### 6.3 Fallback 단계별 비율 추정 + +| 단계 | 종료 비율 (가설) | 비용 | +|---|---|---| +| 1차 | 80%+ | 낮음 (코드만) | +| 2~3차 | 15% | 낮음 (코드만) | +| 4차 (AI) | 4% | 중간 | +| 5차 (단일 + 팝업) | 1% 이하 | 낮음 | +| 사람 개입 | 매우 드물게 | 최후 수단 | + +→ 실제 비율은 MDX 데이터로 검증 필요. 일단 가설. + +--- + +## 7. 단계적 진행 계획 + +### Phase Z-0 — 매칭 시스템 (✅ 완료) + +``` +✅ V1~V4 매칭 시스템 구축 +✅ 32 frame 키워드 + 구조 라벨링 +✅ TARGET 3/4 정답 +✅ 보고서 (DECK 1~7) +``` + +### Phase Z-1 사전 작업 — 진행 중 + +``` +✅ 완료 (2026-04-28) + · Frame Integration Map (32 frame Zone 적용 분류) + → docs/architecture/FRAME-INTEGRATION-MAP.md + · Frame Style Inventory (32 frame + 18 token + 6 legacy) + → docs/architecture/PHASE-Z-FRAME-STYLE-INVENTORY.md + +⬜ 미진행 (Phase Z-1 본격 진입 전) + · catalog / runtime 설계 prep + · slide-base 검증 + +⚠️ 미실행 / 의도적으로 보류 (승인 전) + · 기존 templates/blocks 삭제 / 교체 + · catalog / runtime 구현 + · templates/styles/frame-patterns 신규 파일 생성 + · 새 token (gap_candidate) 추가 +``` + +### Phase Z-1 — 통합 prototype (본격) + +``` +[목표] MDX 03 (회귀 기준) 으로 매칭 시스템 + 기존 pipeline 통합 검증 + +작업: + · Stage 1.7 (블록 선택) 만 V4 로 교체 + · 32 frame ↔ Type B/B'/B'' 매핑 테이블 작성 + · MDX 03 결과 기존과 비교 (회귀 통과) + +완료 기준: + · MDX 03 기존 결과 대비 overflow 없음 + · 텍스트 누락 없음 (MDX 원문 무손실 보존, 3.6 원칙) + · AI 호출 수 감소 (Stage 1.7 의 Kei 1회 제거) + · 옵션 플래그 off 시 동일 경로 복귀 (회귀 방지) + +리스크: 낮음 (한 곳만 교체) +산출물: 매칭 시스템이 기존 pipeline 안에서 작동 확인 +``` + +### Phase Z-2 — 매칭 + 프리셋 통합 + +``` +[목표] Type A/B/B'/B'' 프리셋과 매칭 결과 연결 + +작업: + · 매칭 결과 → 4 프리셋 중 1 개 자동 선택 룰 + · 합치기 룰 (소→중→대) 구현 + · MDX 02, 01 적용 검증 + +완료 기준: + · MDX 03 / 02 / 01 모두 매칭 + 프리셋 자동 선택 작동 + · 합치기 룰이 소목차 → 중목차 → 대목차 순서로 작동 + · MDX 원문 무손실 유지 (3.6 원칙) + +리스크: 중간 +산출물: MDX 03/02/01 모두 매칭 + 프리셋 자동 +``` + +### Phase Z-3 — 컨테이너 검증 + Fallback + +``` +[목표] 컨테이너 별 검증 + 5 차 Fallback 로직 + +작업: + · 불균형 검출 룰 (Overflow / Underflow / 동시) + · STAGE 4 의 1~5 차 Fallback 단계 구현 + · 팝업 처리 룰 (안 들어가면 자세히 보기) + +완료 기준: + · 컨테이너 별 검증 (Overflow / Underflow / 불균형) 작동 + · Fallback 5 차 단계 작동 (각 단계 독립 검증) + · 팝업 처리 — preview 본문 + 무손실 팝업 + +리스크: 중간 ~ 높음 +산출물: 안정적 자동 처리 +``` + +### Phase Z-4 — 전체 통합 + 검증 + +``` +[목표] 새 pipeline 으로 모든 MDX 처리 + 회귀 확인 + +작업: + · MDX 03 / 02 / 01 회귀 + · Selenium + Vision 검증 + · AI 호출 수 / 처리 시간 측정 + +완료 기준: + · MDX 03 / 02 / 01 모두 새 pipeline 통과 + · Vision 품질 평가 합격 + · AI 호출 수 / 처리 시간 기존 대비 감소 + +산출물: 새 pipeline 완성 +``` + +--- + +## 8. 검토 / 결정 필요한 항목 + +### 8.1 32 frame 의 사이즈 라벨링 + +frame DB 에 "이 frame 은 슬라이드 전체 / 위 절반 / 좌측 1/3 / 작은 박스" 같은 사이즈 분류 필요. + +| 옵션 | 방법 | +|---|---| +| (a) 사용자 직접 분류 | 정확하지만 시간 소요 | +| (b) Figma 캔버스 사이즈 자동 추정 | 빠르지만 부정확 가능 | +| (c) 매칭 시스템의 기존 라벨 (layout) 활용 + 보완 | 절충 | + +→ **추천 (c)** : 기존 layout 라벨 (compare-rows / table-2col / cards-3col 등) 을 사이즈 라벨에 매핑하는 룰 작성. + +### 8.2 프리셋 자동 선택 룰 + +frame 들의 사이즈 / 개수 → 4 프리셋 중 1 개 선택하는 룰. + +예시: +- frame 1 개 + 사이즈 = 슬라이드 전체 → Type A 또는 Type B 단일 +- frame 2 개 + 사이즈 = 위/아래 절반 → Type B (상단 + 하단) +- frame 2 개 + 사이즈 = 좌/우 절반 → Type A (사이드바) + +### 8.3 합치기 룰 종료 조건 + +소→중→대 합치기 룰에서 어디까지 합칠지 결정: +- 소목차 매칭 결과가 "비슷" 하다는 기준은? +- 같은 frame_id ? 같은 layout family ? V4 axes 일부 일치 ? + +### 8.4 슬롯 매핑 — 코드 + AI 하이브리드 비율 + +- 어디까지 코드 휴리스틱? (제목 → title 같은 명확한 것) +- 어디부터 AI? (의미 매핑 / 사이즈 조정 / 텍스트 다듬기) + +--- + +## 9. 회귀 방지 / 안전 원칙 + +### 9.1 기존 작동 코드 보존 + +- **Type A 코드** (오답노트 #7) — 절대 건드리지 않음 +- **공간 관리 로직** (`space_allocator`, `fit_verifier`, `slide_measurer`) — 그대로 유지 +- **Phase Y 진행 중 작업** — MDX 외부 컴포넌트 처리 그대로 + +### 9.2 회귀 검증 + +- 단계마다 **MDX 03 (회귀 기준)** 결과 기존과 비교 +- 변경 전 / 변경 후 다음 항목 측정: + - 최종 HTML 의 시각 결과 (스크린샷 비교) + - AI 호출 수 + - 처리 시간 + - overflow 발생 여부 + +### 9.3 옵션 플래그 도입 + +``` +USE_MATCHING_SYSTEM = False # 기본값 +``` + +- 새 기능은 플래그로 도입 +- 문제 발생 시 즉시 기존 경로로 fallback +- 안정화 후 기본값 True + +### 9.4 핵심 원칙 (오답노트.md 준수) + +- **거짓말 금지** — 못 하면 못 한다고 명시 +- **하드코딩 금지** — 결과물 고치지 말고 프로세스 고침 +- **검증 없이 넘어가지 마라** — 단계마다 실측 + 회귀 +- **텍스트 원문 무손실 보존** — 슬라이드 본문은 preview / 일부만 표시 가능, MDX 원문은 팝업 / detail 에 무손실 보존 (3.6 원칙) +- **프로세스 만들어라** — 매번 사고하여 판단 (AI 든 코드든) + +--- + +## 10. 참고 문서 + +### 우선순위 — 충돌 시 어느 문서가 최신인가 + +**매칭 시스템 관련**: +- ✅ 최신 기준 = `tests/PIPELINE.md` (V1~V4 통합 정리) +- ⚠️ `tests/matching/CURRENT_STATUS.md` 등 옛 문서는 **과거 기록 (참조 X)** +- 옛 문서에 "V2/V3/V4 미완료" 로 적혀 있어도 무시. 실제는 모두 완료. + +**design_agent 전체**: +- ✅ 최신 기준 = `PIPELINE.md` + 이 문서 (`IMPROVEMENT-REDESIGN.md`) +- ⚠️ `ARCHITECTURE_OVERVIEW.md` 는 **deprecated** (2026-03-27 스냅샷, Type A/B 분기 등 미반영) + +### 매칭 시스템 (V1~V4) + +| 문서 | 위치 | +|---|---| +| 매칭 시스템 V1~V4 통합 정리 | `tests/PIPELINE.md` | +| 매칭 시스템 진행 / 약점 | `tests/PROGRESS.md` | +| 매칭 시스템 단계별 계획 | `tests/PLAN.md` | +| 매칭 시스템 코드 + 결과 + 보고서 | `tests/pipeline/` | +| 매칭 시스템 원본 (보존) | `tests/matching/` | + +### 기존 design_agent + +| 문서 | 위치 | +|---|---| +| 현재 pipeline 흐름 | `PIPELINE.md` | +| 전체 Phase 이력 | `PROGRESS.md` | +| 단계별 계획 | `PLAN.md` | +| 절대 원칙 | `오답노트.md` | +| 프로젝트 규칙 | `CLAUDE.md` | +| README | `README.md` | + +### 보고서 (임원 보고용) + +| 문서 | 위치 | +|---|---| +| DECK 1~7 (TARGET / Holdout / 방법 / DB / 키워드) | `tests/pipeline/reports/DECK_*.html` | + +--- + +## 11. 리스크 + 대응 방안 + +설계안에서 실행계획으로 넘어갈 때 고려해야 할 리스크. + +| # | 리스크 | 대응 방안 | +|---|---|---| +| 1 | **V4 가 디자인 적합성은 보지만 공간 적합성은 보지 않음** | 기존 공간 관리 (B 영역, `space_allocator` / `fit_verifier`) 그대로 유지. STAGE 4 컨테이너 검증으로 보완. V4 confidence 와 별개로 컨테이너 단위 overflow / underflow 측정 | +| 2 | **slot 의미 매핑 부재** (BIM → col_a_label 같은 매핑 없음) | STAGE 3 (zone 별 프레임 매칭) 의 슬롯 매핑에 AI 1회 호출 (Sonnet). 또는 frame DB 에 매핑 룰 사전 정의. 매칭 시스템의 slot 축 ablation 결과 — 이 영역은 별도 작업 필요 | +| 3 | **32 frame DB 의 사이즈 라벨 부족** | 8.1 항목 결정 후 라벨링 작업 (사용자 직접 또는 매칭 시스템의 layout 라벨 활용). Phase Z-1 시작 전 완료 필요 | +| 4 | **기존 Type B'/B'' 하드코딩 관성 재발 위험** | 4 프리셋 안에서만 조합. 새 콘텐츠별 전용 코드 절대 금지 (오답노트 #2). 새 변형 필요 시 — 코드 분기 추가 X, frame DB 또는 프리셋 추가로 해결 | +| 5 | **보고서용 매칭 성능 ≠ 실제 렌더 품질** | TARGET 3/4 정답률은 매칭 단독 성능. 실제 슬라이드 품질은 Phase Z-1 ~ Z-4 단계마다 MDX 03 실 렌더 + Vision 평가 회귀 | +| 6 | **MDX 분석 키워드 사전의 부정확성** (V3) | Hybrid 룰 — 코드 자동 + Kei fallback (신뢰도 낮을 때). 또는 추후 LLM 분석으로 교체 (`tests/PROGRESS.md` 의 Phase E.1, E.2) | +| 7 | **합치기 룰 (소→중→대) 무한 재시도 위험** | Fallback 5 차로 명확히 종료 (STAGE 4). 각 단계 최대 1 회 시도. 5 차 후엔 사람 개입 알림 | +| 8 | **매칭 시스템의 02-2.2 매칭 실패 사례** | 매칭 시스템 자체 약점 (`tests/PROGRESS.md` 약점 #1). Phase Z-1 진행 전 frame 14 의 anchor_sets 재라벨링 필요 (사전 작업) | + +--- + +## 다음 단계 + +1. **이 문서 검토** — 사용자 + 협업자 +2. **검토 사항 (7장) 결정** — 4 가지 항목 +3. **리스크 대응 (11장) 사전 작업** — 32 frame 사이즈 라벨링 + frame 14 재라벨링 +4. **Phase Z-1 시작** — 통합 prototype +5. **단계별 회귀 검증** — MDX 03 기준 diff --git a/PLAN.md b/PLAN.md index 706e97b..5bd6477 100644 --- a/PLAN.md +++ b/PLAN.md @@ -451,6 +451,50 @@ Phase V (적합성 검증): --- +## Phase Z: 매칭 시스템 통합 (2026-04-28 ~) + +> **상세 설계**: [IMPROVEMENT-REDESIGN.md](IMPROVEMENT-REDESIGN.md) +> +> 별도 검증된 매칭 시스템 (V1~V4, `tests/matching/`) 을 기존 pipeline 에 통합. + +### 단계적 진행 + +| 단계 | 내용 | 산출물 | +|---|---|---| +| **Phase Z-1** | 통합 prototype — Stage 1.7 (블록 선택) 만 V4 로 교체 | MDX 03 회귀 통과 | +| **Phase Z-2** | 매칭 + 4 프리셋 (Type A/B/B'/B'') 통합 | MDX 03/02/01 자동 매칭 | +| **Phase Z-3** | 컨테이너 검증 + 5 차 Fallback | 안정적 자동 처리 | +| **Phase Z-4** | 전체 통합 + 회귀 검증 | 새 pipeline 완성 | + +### 사전 결정 사항 (검토 완료) + +- **위계** : slide → slide-base → slide-body → 레이아웃 → Zone → 프레임 +- **5 단계 흐름** : MDX 분석/레이아웃 → Zone 텍스트 배치 → 프레임 매칭 → 검토 → 출력 +- **매칭 분기** : 완벽 / 어정쩡 / 안 됨 → 후속 작업 차등 +- **레이아웃 프리셋** : Type A / B / B' / B'' (기존 4 가지 활용) +- **절대 룰** : 텍스트 원문 무손실, 자유 디자인 금지, MDX 1 = 슬라이드 1 + +### 사전 작업 진행 상태 + +✅ **완료** (2026-04-28) +- 32 frame Zone 적용 분류 (`zone_direct` / `zone_adapt` / `zone_extract` / `reference_only`) — [docs/architecture/FRAME-INTEGRATION-MAP.md](docs/architecture/FRAME-INTEGRATION-MAP.md) +- Frame / Style Inventory (32 frame + 18 token + 6 legacy) — [docs/architecture/PHASE-Z-FRAME-STYLE-INVENTORY.md](docs/architecture/PHASE-Z-FRAME-STYLE-INVENTORY.md) + +⬜ **미진행** +- catalog / runtime 설계 prep +- slide-base 검증 + +⚠️ **미실행 / 의도적으로 보류** (승인 전) +- 기존 `templates/blocks/` 삭제 / 교체 +- catalog / runtime 구현 +- `templates/styles/frame-patterns/` 신규 파일 생성 +- 새 token (`gap_candidate`) 추가 +- legacy structures 6 파일 삭제 + +> ⚠️ **Phase Z-1 자체는 진행 중**. 위 ✅ 는 *사전 작업 중 일부* 완료 표시. + +--- + ## 기술 스택 | 역할 | 도구 | 비고 | diff --git a/PROCESS_OVERVIEW.html b/PROCESS_OVERVIEW.html new file mode 100644 index 0000000..7233d6a --- /dev/null +++ b/PROCESS_OVERVIEW.html @@ -0,0 +1,631 @@ + + + + +슬라이드 자동 생성 — 프로세스 개요 + + + + + + + +
+ +

슬라이드 자동 생성 프로세스

+ +
+ 전처리 · MDX 를 정규화하고, Figma 디자인을 HTML 화하여 DB 에 정리함. +
+ + +
+
+
1
+
+

MDX 분석 + 레이아웃 정리

+

MDX 의 대 / 중 / 소목차 와 콘텐츠 성격에 따라 4 가지 레이아웃 중 1 개를 정리

+
+
+
+
+
DX 시행 — 필수 요건 + 혁신 방안
+
+
상단 3 영역
+
+
+
+
+
하단 좌
+
하단 우
+
+
결론
+
+
+
+
+
+ + +
+
+
2
+
+

Figma 매칭

+

디자인 프레임 (HTML) 중 적합한 디자인 매칭

+

매칭 · 해당 프레임에 텍스트 업데이트

+

미매칭 · 가장 유사한 프레임 참고, 재구성

+
+
+
+ + +
+
매칭 — Frame 13 + Frame 29
+
+
+ Frame 13 +
Frame 13 — 필수 요건 (기술·사람·자연)
+
+
+ Frame 29 +
Frame 29 — Process / Product 혁신
+
+
+
+ + +
+
미매칭 — 유사 프레임 변형
+
+
DX 시행 — 필수 요건 + 혁신 방안
+
+
유사 카드 차용
+
+
+
+
+
유사 비교 차용
+
+
+
결론
+
+
+ 적합 프레임 없음 → 가장 유사한 프레임의 CSS / 구조만 차용 (자유 디자인 X) +
+
+ +
+
+
+
+ + +
+
+
3
+
+

재구성 — 공간 검토 + 팝업 정리

+

매칭된 프레임의 영역에 콘텐츠가 들어가도록 검토

+
+
+
+
+
03DX 시행을 위한 필수 요건 및 혁신 방안
+
+
+
기술
+
핵심 기술 + 도구
+
+
+
사람
+
전문 인력 + 교육
+
+
+
자연
+
데이터 + 인프라
+
+
+
+
+
Process 혁신
+
AS-IS2D 도면
+
TO-BE데이터 통합
+
+
+
Product 혁신
+
AS-IS3D 모델
+
TO-BE정보 연계
+
+
+
동시 추진
+
+
+
+
+
+ + +
+
+
4
+
+

출력

+

완성된 슬라이드를 1 장으로 검증 후 HTML 출력

+
+
+
+ MDX 03 최종 슬라이드 +
+
+
+
+ +
+ + + + +
+ +

실질 프로세스 구조도

+ + +
+
+
INPUT
+
MDX 1 파일
+
대목차 1 개 = 슬라이드 1 장
+
+
+
+ + +
+
STAGE 1MDX 분석 + 레이아웃 정리
+
+
정규화
외부 컴포넌트 인라인
코드
+ +
트리 추출
대 / 중 / 소목차
코드 (regex)
+ +
콘텐츠 분석
관계 / 항목수 / 성격
코드 (V3 룰)
+ +
레이아웃 결정
Type A / B / B' / B''
코드 (룰 매칭)
+
+
+
+ + +
+
STAGE 2Figma 매칭
+
+
키워드 매칭
3 계층 (핵심·세트·연관)
코드 (V1)
+ +
의미 매칭
임베딩 유사도
ko-sroberta
+ +
구조 매칭
레이아웃·관계·항목수
코드 (V3)
+ +
종합 판정
매칭 / 미매칭
코드 (V4)
+
+
+
+
+
매칭
+
콘텐츠 매핑
크기에 맞게 재구성
AI · Sonnet
+
+
+
미매칭
+
유사 프레임 변형
대안 시도 → 변형
AI · Sonnet
+
+
+
+
↓ 결과 통합 ↓
+ + +
+
STAGE 3재구성 — 공간 검토 + 팝업 정리
+
+
크기 계산
컨테이너 px 배분
코드 (space_allocator)
+ +
영역별 검증
적정 / 넘침 / 공란 / 불균형
코드 (fit_verifier)
+ +
보정
팝업 분리 또는 STAGE 1 회귀
코드 + 회귀
+
+
+
+ + +
+
STAGE 4HTML 조립 + 검증 + 출력
+
+
HTML 조립
프레임 + 콘텐츠 + 팝업
Jinja2
+ +
실측 검증
넘침 차단
Selenium
+ +
시각 검증
품질 평가
AI · Opus Vision
+ +
슬라이드 1 장
final.html
+
+
+ + +
+ 처리 주체 + 코드 결정론적 Python 처리 + AI Anthropic API (Sonnet / Opus) + Jinja2 템플릿 렌더링 + 도구 외부 도구 (Selenium / 임베딩 모델) +
+ + +
+ + + diff --git a/PROGRESS.md b/PROGRESS.md index d9062fb..426dee6 100644 --- a/PROGRESS.md +++ b/PROGRESS.md @@ -154,6 +154,15 @@ Phase R은 접근 C로 가기로 합의했으나, 구현에서 기존 블록 선 ## 📋 Phase R': 접근 C — 블록 CSS 참고 + AI 구조 결정 (설계 확정) +> ⚠️ **Legacy — Phase Z 로 대체됨 (2026-04-28)** +> +> Phase R' 의 "AI 가 HTML 구조를 직접 생성" 흐름은 Phase Z 에서 다음과 같이 변경됨: +> - **HTML 구조** = `slide-base.html` + 코드 (Jinja2) 가 결정 (AI 가 생성하지 않음) +> - **AI 의 역할** = zone 안의 콘텐츠 / 텍스트 매핑 / 텍스트 다듬기 / 디자인 변형만 +> - **자유 디자인 금지** — 항상 Figma 프레임 DB 참고 +> +> 아래 R' 설계 내용은 히스토리 / 참고용. 실제 구현은 [`IMPROVEMENT-REDESIGN.md`](IMPROVEMENT-REDESIGN.md) 의 5 단계 흐름 따름. + **상세:** [IMPROVEMENT-PHASE-R-PRIME.md](IMPROVEMENT-PHASE-R-PRIME.md) ### 핵심 전환 @@ -165,6 +174,8 @@ R' (접근 C): 콘텐츠가 구조를 결정 → 블록 CSS를 참고하여 HT ### 프로세스 변경 +> ⚠️ **아래 표는 폐기된 Phase R' 기록이며 신규 구현 지시가 아니다.** 새 구현은 [IMPROVEMENT-REDESIGN.md](IMPROVEMENT-REDESIGN.md) 의 Phase Z 5 단계 흐름 따름. + | 단계 | 현재 (P=Q=R) | R' (접근 C) | |------|-------------|------------| | 1단계 Kei 분석 | 유지 | 유지 | @@ -213,15 +224,115 @@ C_reference.png와 동일 수준의 결과를 **자동으로** 생성: --- +## Phase Z: 매칭 시스템 통합 설계 (2026-04-28) + +### 배경 +별도 검증한 매칭 시스템 (V1~V4) 을 기존 design_agent pipeline 에 통합하기 위한 설계. + +**매칭 시스템 (`tests/matching/`) 검증 결과**: +- V1 키워드 매칭 (Logistic Regression 가중치, TARGET 4/4 LOOCV 검증) +- V2 의미 매칭 (ko-sroberta cosine) +- V3 구조 매칭 (layout / content_affinity / structure_intent) +- V4 종합 판정 (5축 + 라벨) +- TARGET 정답률 3/4 (75%), BM25 / IDF 보다 우위 +- V4 slot 축 ablation: Top-1 매칭 7/7 동일 (slot 축 frame 선별 무영향) + +### 설계 결정 사항 + +**위계 + 용어 정리** : +``` +[ slide ] 1280×720 + ├─ slide-title ← MDX 대목차 제목 + ├─ slide-divider (고정) + ├─ slide-body ≈ 1200×590 ← 콘텐츠 영역 + │ └─ 레이아웃 (Type A/B/B'/B'') + │ └─ Zone (top/bottom_l/bottom_r 등) + │ └─ 프레임 (Figma 디자인 단위) + └─ slide-footer ← MDX 대목차 결론 +``` + +**5 단계 새 흐름**: +1. STAGE 1 — MDX 분석 + 레이아웃 매칭 (Type A/B/B'/B'') +2. STAGE 2 — Zone 별 텍스트 1차 배치 +3. STAGE 3 — Zone 별 프레임 매칭 (완벽 / 어정쩡 / 안 됨 분기) +4. STAGE 4 — 프레임 검토 + 컨테이너 조정 (5 차 Fallback) +5. STAGE 5 — HTML 조립 + 검증 + 출력 + +**핵심 원칙**: +- MDX 1 파일 = 대목차 1 개 = 슬라이드 1 장 +- 텍스트 원문 무손실 보존 (본문 미리보기 + 팝업 원문) +- 자유 디자인 금지 (항상 Figma 프레임 DB 참고) +- 불일치 시 레이아웃 회귀 (콘텐츠 줄이지 않고 그릇 변경) + +### 산출물 (이번 세션) + +| 파일 | 용도 | +|---|---| +| [`IMPROVEMENT-REDESIGN.md`](IMPROVEMENT-REDESIGN.md) | 매칭 시스템 통합 설계 문서 (전체 명세) | +| [`PROCESS_OVERVIEW.html`](PROCESS_OVERVIEW.html) | 임원 보고용 A4 2 페이지 (프로세스 + 구조도) | +| [`tests/PIPELINE.md`](tests/PIPELINE.md) | V1~V4 통합 정리 + frame 단위 명확화 | +| [`tests/pipeline/`](tests/pipeline/) | 매칭 시스템 코드 + 결과 + 보고서 (분류 복사본) | + +### 다음 단계 (구현) + +`IMPROVEMENT-REDESIGN.md` 의 **단계적 진행 계획** 참조. +- **Phase Z-1**: 통합 prototype (MDX 03 회귀 통과) +- **Phase Z-2**: 매칭 + 프리셋 통합 +- **Phase Z-3**: 컨테이너 검증 + Fallback +- **Phase Z-4**: 전체 통합 + 검증 + +### 발견된 약점 (8가지) — Phase Z 진행 시 대응 + +`IMPROVEMENT-REDESIGN.md` 11 장 (리스크) + `tests/PROGRESS.md` 약점 표 참조. + +핵심 : +- 02-2.2 매칭 실패 (Frame 14 anchor 재라벨링 필요) +- 32 frame DB 사이즈 라벨링 부족 (zone 단위 vs 슬라이드 단위) +- slot 의미 매핑 부재 +- V3 콘텐츠 성격 분류 부정확 (키워드 사전 한계) + +--- + +## Phase Z-1 사전 작업 진행 (2026-04-28) + +> **Phase Z-1 자체는 진행 중**. 본 entry 는 사전 작업 중 **Frame / Style Inventory 완료** 만 기록. + +### ✅ 완료 — Frame / Style Inventory (사전 작업 일부) + +| 산출 | 위치 | 내용 | +|---|---|---| +| Frame Integration Map | [`docs/architecture/FRAME-INTEGRATION-MAP.md`](docs/architecture/FRAME-INTEGRATION-MAP.md) | 32 frame Zone 적용 분류 (`zone_direct` / `zone_adapt` / `zone_extract` / `reference_only`). row 21~28 Figma ID 정정. 1171281171 부록 처리 | +| Frame Style Inventory | [`docs/architecture/PHASE-Z-FRAME-STYLE-INVENTORY.md`](docs/architecture/PHASE-Z-FRAME-STYLE-INVENTORY.md) | 32 frame (변환 14 + 미변환 18) + Token 18 행 (covered 7 / gap 5 / hierarchy 3 / hold 3) + Legacy 6 행 | + +### ⬜ 다음 — Phase Z-2 본격 (catalog / runtime 설계) + +- Phase Z-1 의 catalog / runtime 설계 prep 부분 (slide-base 검증 등) 은 미진행 +- Phase Z-2 본격 (매칭 + 4 프리셋 통합) 도 미진행 + +### ⚠️ 미실행 / 의도적으로 보류 + +- 기존 `templates/blocks/` 삭제 / 교체 실행 +- catalog / runtime 구현 +- `templates/styles/frame-patterns/` 신규 파일 생성 +- `templates/styles/tokens/` 의 `gap_candidate` token 추가 +- legacy structures 6 파일 삭제 + +→ 모두 **승인 전 보류**. Frame / Style Inventory 는 추출 / 검증 단계이고, 실제 변경은 별도 승인 단계. + +--- + ## 프로젝트 구조 | 항목 | 파일 | 상태 | |------|------|------| -| 프로젝트 규칙 | CLAUDE.md | Phase R' 반영 | -| 개선 계획 | IMPROVEMENT.md | Phase R' 반영 | -| 진행 추적 | PROGRESS.md | 이 파일 (2026-03-30 갱신) | +| 프로젝트 규칙 | CLAUDE.md | Phase Z 반영 (2026-04-28) | +| 개선 계획 (이전) | IMPROVEMENT.md | Phase R' 반영 | +| **개선 계획 (신규)** | **IMPROVEMENT-REDESIGN.md** | **Phase Z 매칭 시스템 통합 (2026-04-28)** | +| 임원 보고용 | PROCESS_OVERVIEW.html | Phase Z 흐름 반영 | +| 진행 추적 | PROGRESS.md | 이 파일 (2026-04-28 갱신) | | 전체 감사 | CLEANUP-AUDIT.md | 유효/무력화 분류 완료 | | Phase별 상세 | IMPROVEMENT-PHASE-{A~R'}.md | 각 Phase 기록 | | Phase R 실패 기록 | IMPROVEMENT-PHASE-R.md | 블록 선택 위에 variant 패치 — 실패 | | Phase R' 설계 | IMPROVEMENT-PHASE-R-PRIME.md | 접근 C 기반 재설계 | -| README | README.md | Phase R' 반영 | +| 매칭 시스템 (별도 검증) | tests/PIPELINE.md + tests/pipeline/ | V1~V4 검증 완료 (2026-04-27) | +| README | README.md | Phase Z 반영 | diff --git a/README.md b/README.md index 01ec04e..0ca51c4 100644 --- a/README.md +++ b/README.md @@ -19,9 +19,12 @@ MDX 입력 → 정규화 → 꼭지 추출(AI) → zone 구분 → BEPs 매칭 ### 구조 - **slide-base:** 1280×720 슬라이드 프레임. 대목차 + 구분선 + 본문 영역 + 핵심 인사이트(footer) -- **zone:** 본문 영역 안에서 중목차(##) 기준으로 나뉘는 영역 (top/bottom 등) -- **블록:** zone 안에 들어가는 디자인 단위. Figma에서 추출한 BEPs 디자인을 HTML/CSS로 변환한 것 -- **catalog:** 블록의 메타 정보 (구조, 슬롯, 매칭 조건) +- **레이아웃:** slide-body 안의 zone 분배 형태 (Type A/B/B'/B'') +- **zone:** 레이아웃이 정한 콘텐츠 영역 (top/bottom 등) +- **프레임 (구 "블록"):** zone 안에 들어가는 디자인 단위. Figma에서 추출한 BEPs 디자인을 HTML/CSS로 변환한 것 +- **catalog:** 프레임의 메타 정보 (구조, 슬롯, 매칭 조건) + +> ⚠️ Phase Z (2026-04-28) 부터 "블록" → "프레임" 으로 용어 통일. 기존 코드 (`block_reference.py`, `templates/blocks/`, `BLOCK-RULES.md` 등) 는 점진적 정리. ### 주요 파일 @@ -70,20 +73,32 @@ MDX 입력 → 정규화 → 꼭지 추출(AI) → zone 구분 → BEPs 매칭 ### AS-IS → TO-BE +> ⚠️ 아래 AS-IS / TO-BE 는 Phase Q ~ T 시점의 흐름. **현재 (Phase Z) 흐름은 [`IMPROVEMENT-REDESIGN.md`](IMPROVEMENT-REDESIGN.md)** 참조. + ``` -AS-IS: +AS-IS (Phase Q ~ T): AI가 먼저 꼭지를 추출하고 → 매칭 블록이 있으면 삽입, 없으면 코드가 1회 고정 렌더 → 빈 공간이 있어도 그냥 둠 → 블록마다 font-size, color가 직접 박혀있어서 섞이면 위계 안 맞음 -TO-BE: +TO-BE (Phase Q ~ T): 중목차 기준으로 zone을 먼저 나누고 → TF-IDF로 BEPs 매칭 시도 → 매칭되면 블록 삽입 + 크기 조절 (direct-fit) → 안 되면 AI가 꼭지 정리 + 유사 디자인으로 redesign + 반복 조정 (recipe) → 빈 공간/overflow를 자동 재분배 → 모든 블록이 토큰 기반이라 스타일 통일 + +NEW (Phase Z, 2026-04-28 ~): + STAGE 1) MDX 분석 + 레이아웃 매칭 (Type A/B/B'/B'') + STAGE 2) Zone 별 텍스트 1차 배치 + STAGE 3) Zone 별 프레임 매칭 — V1~V4 (완벽/어정쩡/안됨 분기) + STAGE 4) 프레임 검토 + 컨테이너 조정 (5차 Fallback) + STAGE 5) HTML 조립 (slide-base + Jinja2) + 검증 + 출력 + ⭐ AI = zone 안 콘텐츠만 / HTML 구조 = 코드 (Jinja2) + ⭐ 자유 디자인 금지 — 프레임 DB 참고 필수 + ⭐ MDX 원문 무손실 보존 ``` --- @@ -97,21 +112,33 @@ TO-BE: | MDX 01 (Type A, sidebar 구조) | ✅ 완료 (개별) | 미연결 | | 토큰 기반 CSS 체계 | - | ✅ 정의 완료, slide-base 적용 | | Figma 블록 추출 | - | 진행 중 (`figma_to_html_agent/blocks/`) | +| **매칭 시스템 (V1~V4)** | - | **✅ 별도 검증 완료** (`tests/`) — TARGET 3/4 | +| **Phase Z 통합 설계** | - | **✅ 설계 완료** (`IMPROVEMENT-REDESIGN.md`) — 구현 대기 | --- -## 다음 단계 방향 +## 다음 단계 방향 — Phase Z 매칭 시스템 통합 -| 순서 | 단계 | 내용 | -|------|------|------| -| 1 | 폴더 구조 정리 | structures/recipes/legacy 분리 | -| 2 | 기존 블록 점진 전환 | 분류(direct-fit/recipe/rewrite) → 토큰 기반 전환 | -| 3 | catalog 고도화 | 파일명 중심 → 속성 테이블 기반 매칭 | -| 4 | 파이프라인 연결 | TF-IDF 매칭 + recipe/composition 경로 | -| 5 | fit 루프 확장 | 빈 공간 재분배, preview 축약, 자동 조정 반복 | -| 6 | 시각 품질 검증 | 정렬, 위계, 가독성 검증 강화 | +상세: [IMPROVEMENT-REDESIGN.md](IMPROVEMENT-REDESIGN.md) -상세: [IMPROVEMENT-PLAN.md](docs/architecture/IMPROVEMENT-PLAN.md) +| 단계 | 내용 | +|------|------| +| Phase Z-1 | 통합 prototype — Stage 1.7 만 V4 로 교체, MDX 03 회귀 | +| Phase Z-2 | 매칭 + 프리셋 (Type A/B/B'/B'') 통합 | +| Phase Z-3 | 컨테이너 검증 + 5 차 Fallback | +| Phase Z-4 | 전체 통합 + 검증 | + +**핵심 위계** (Phase Z 정리) : +``` +slide → slide-base → slide-body → 레이아웃 → Zone → 프레임 +``` + +**5 단계 새 흐름** : +1. MDX 분석 + 레이아웃 매칭 +2. Zone 별 텍스트 배치 +3. Zone 별 프레임 매칭 (완벽 / 어정쩡 / 안 됨) +4. 프레임 검토 + 컨테이너 조정 +5. HTML 조립 + 검증 + 출력 --- @@ -119,6 +146,13 @@ TO-BE: | 문서 | 내용 | |------|------| -| [IMPROVEMENT-PLAN.md](docs/architecture/IMPROVEMENT-PLAN.md) | 개선 설계 (목표/방향/6단계 계획) | -| [TOKENS-v1.md](docs/architecture/TOKENS-v1.md) | 토큰 위계 기준표 초안 | -| [BLOCK-RULES.md](docs/architecture/BLOCK-RULES.md) | 블록 작성 규칙 (에이전트 간 계약서) | +| [IMPROVEMENT-REDESIGN.md](IMPROVEMENT-REDESIGN.md) | **Phase Z 통합 설계** (위계 / 용어 / 5 단계 흐름) | +| [docs/architecture/FRAME-INTEGRATION-MAP.md](docs/architecture/FRAME-INTEGRATION-MAP.md) | **32 frame Zone 적용 분류** (`zone_direct/adapt/extract/reference_only`) | +| [docs/architecture/PHASE-Z-FRAME-STYLE-INVENTORY.md](docs/architecture/PHASE-Z-FRAME-STYLE-INVENTORY.md) | **Frame / Style / Token 인벤토리** (32 frame + 18 token + 6 legacy) | +| [PROCESS_OVERVIEW.html](PROCESS_OVERVIEW.html) | 임원 보고용 A4 2 페이지 (프로세스 + 구조도) | +| [tests/PIPELINE.md](tests/PIPELINE.md) | 매칭 시스템 (V1~V4) 통합 정리 | +| [PROGRESS.md](PROGRESS.md) | 전체 Phase 이력 (Phase 1~T + R' + Z) | +| [CLAUDE.md](CLAUDE.md) | 프로젝트 규칙 + Phase Z 위계 | +| [IMPROVEMENT-PLAN.md](docs/architecture/IMPROVEMENT-PLAN.md) | 개선 설계 (이전 Phase Q 까지) | +| [TOKENS-v1.md](docs/architecture/TOKENS-v1.md) | 토큰 위계 기준표 | +| [BLOCK-RULES.md](docs/architecture/BLOCK-RULES.md) | 블록 작성 규칙 | diff --git a/docs/architecture/PHASE-Z-CHANGE-LOG.md b/docs/architecture/PHASE-Z-CHANGE-LOG.md new file mode 100644 index 0000000..c7e267b --- /dev/null +++ b/docs/architecture/PHASE-Z-CHANGE-LOG.md @@ -0,0 +1,226 @@ +# Phase Z — change log + +**역할** : axis-by-axis 의사결정 / reframe / 폐기 / lock 의 *history* 기록. + +**관련 문서** : +- [`PHASE-Z-PIPELINE-OVERVIEW.md`](PHASE-Z-PIPELINE-OVERVIEW.md) — 22-step 도면 (구조 lock) +- [`PHASE-Z-PIPELINE-STATUS-BOARD.md`](PHASE-Z-PIPELINE-STATUS-BOARD.md) — 현재 snapshot (자주 갱신) +- 본 문서 = 누적 history (newest-on-top, append-only) + +**본 문서의 목적** : +- "왜 이 안을 폐기했는지" / "왜 이 axis 로 쪼갰는지" 의 회귀 시 trace +- STATUS-BOARD 가 *현재* 라면 본 문서는 *과거* +- entry 단위 = 한 axis (한 결정 단위) + +**format** : + +``` +## YYYY-MM-DD — Step X-Y / axis 이름 + +scope: + - 무엇이 추가 / 변경 / 폐기됐나 + +lock: + - 사용자 결정 lock + +why: + - 의사결정 근거 / 회귀 방지 사실 + +next axis: + - 이어지는 다음 axis +``` + +--- + +## 2026-05-08 #2 — axis naming / scope 정정 (6-B 폐기 + F14 표현 정정 + label gate policy 분리) + +scope: +- **6-B (frame ownership transfer) 폐기** — misframed axis. +- **"F14 / F11 / F18 frame contract 등록" 표현 폐기** — `V4 frame 후보 → Phase Z render path 연결 확장` 으로 rename. +- **label gate policy 재검토 = 별 axis 로 분리** (= 6-B 안에 숨어 있던 진짜 content). +- forex/status.md §0 Refinement F + §3 진행 순서 + §5 갱신. +- `PHASE-Z-PIPELINE-STATUS-BOARD.md` §3 item 5 + §6 갱신. +- `PHASE-Z-PIPELINE-OVERVIEW.md` Step 6 Gap note 정정. + +lock: + +[6-B 폐기] +- V4 = frame 선택 (점수 + label). +- Step 6 = V4 rank-1 을 default 로 *전사* (선택 X, 전사). +- Step 9 = V4 후보를 application_plan 으로 *번역* (재선택 X, 번역). +- 따라서 "Step 6 의 frame 채택 책임을 Step 9 로 이전" = *허구* (Step 6 이 그런 책임을 원래부터 안 가짐). + +[F14 표현 정정 — 3 layer 분리 lock] +- Figma → HTML 변환 (`figma_to_html_agent/blocks/`) = 32 frame 모두 끝 (이미 layer). +- V4 catalog (`tests/matching/v4_full32_result.yaml`) = 32 frame 매칭 끝 (이미 layer). +- Phase Z render path = F13 / F29 / F16 만 연결 — 나머지 미연결 (작업 layer). +- 작업 = adapter 박기 (contract + partial + builder + fresh run 검증). *Figma 새 디자인 X / V4 새 매칭 X*. +- frame 당 4 가지 entry : + 1. `templates/phase_z2/catalog/frame_contracts.yaml` — contract entry. + 2. `templates/phase_z2/families/{template_id}.html` — Phase Z runtime partial. *`figma_to_html_agent/blocks/{frame_id}/index.html` source 와 별도 layer* — slot/payload 받게 변형. + 3. `src/phase_z2_mapper.py` — PAYLOAD_BUILDERS / ITEM_PARSERS entry. + 4. fresh run 검증. + +[label gate policy 분리] +- 현재 Step 6 의 `MVP1_ALLOWED_STATUSES = {matched_zone, adapt_matched_zone}` binary gate. +- restructure / reject label = 자동 drop (현재). +- 정책 question = restructure 도 unit 으로 살려둘지 / Step 9 v0 4-mode 해석으로 대체할지. +- 자체 가치는 Step 17 / 19 fallback 또는 Step 9 v1 활성화 후 발현 — 단독 axis 가치 X. +- 현 시점 = 별 axis 로 *명시* 만 (구현 axis X). + +why: +- mental model 정정 = layer 책임을 정확히 박기. +- 6-B 와 F14 표현 모두 layer 가 잘못 그려졌었음 — 같은 종류의 정정. +- label gate 는 6-B 안에 숨어 있던 *진짜 axis* — 분리해서 추적성 확보. +- compat 매트릭스 폐기 (2026-05-08 #1 entry) 와 같은 패턴 — *misframed axis 는 폐기 + history 박힘*. + +next axis (별 axis 후보 list): +- (A) V4 frame 후보 → Phase Z render path 연결 확장 (F14 / F11 / F18 등 미연결 frame adapter) +- (B) Step 17 details_popup_escalation 구현 +- (C) Step 4 unit_count 산출 logic +- (D) Step 3 / 4 render path 활성화 (Layer A activation) +- (E) label gate policy 재검토 (= 6-B 의 진짜 content 였던 것) +- (F) Step 9 v1 scoring + auto decision + +--- + +## 2026-05-08 — Step 5/6/9 boundary reframe + +scope: +- Step 9 의 *"compat 매트릭스" 안 폐기* (region × frame slot count 표) — V4 cardinality 재계산 위험. +- Step 9 = **application_plan** 으로 reframe — V4 후보 + layout/region/display 통합 *적용 계획* (V4 axis 재계산 X). +- Step 5/6/7/8/9 boundary 재정리 — 진행 순서 lock = `5 → 6-A → 7-conn → 8-conn → 6-B → 9`. +- Step 6 = `6-A` (additive, logic 무변) + `6-B` (logic 변화) 두 axis 로 분리. + +lock: +- V4 점수 재계산 X — V4 의 anchor / cardinality / relation / slot / content 산식은 Step 5 에서 끝남. +- V4 후보 삭제 X — Step 5 = non-reject max-6 후보 list (raw 32 entry 는 `tests/matching/v4_full32_result.yaml` 영속). +- V4 label 존중 — Step 9 = label (use_as_is / light_edit / restructure / reject) → application_mode (direct_insert / same_frame_with_adjustment / layout_or_region_change / exclude) 변환. +- 진행 순서 = 5 → 6-A → 7-conn → 8-conn → 6-B → 9 (risk 분산: 6-B 를 9 신설 직전으로 미룸). + +why: +- V4 confidence 산식 (`tests/matching/template_fit.py`) 의 5 axis 가 이미 frame 자체 적합도 평가. compat 매트릭스 = 동일 axis 재계산 → 폐기 결정. +- V4 가 *못* 보는 영역 = layout / zone / region / display / contract — 이게 Step 9 의 진짜 영역. +- Step 6 가 V4 rank-1 즉시 frame 채택 + layout 일부 결정해버려서 Step 9 가 받을 candidate list 없음 → Step 5 (rank-1 → top-N) + Step 6 (frame 채택 빼기) 선행 reframe 필요. +- 6-A / 6-B 분리 이유 = 6-A 는 schema 확장 (안전), 6-B 는 selection logic 변경 (위험). 함께 하면 위험 누적. + +next axis: +- Step 5 보완 = `lookup_v4_candidates()` 추가 (non-reject max-6) + `step05_v4_evidence.json` schema 확장 (`v4_candidates` list + `candidate_status`). +- backward compat 유지 = `lookup_v4_match()` (rank-1) 보존, Step 6 호출처 무변. + +--- + +## 2026-05-07 — Step 8-B-2 / display strategy candidate function + +scope: +- `select_display_strategy_candidates(content_type, long_text, large_table, fits_in_region)` 함수 추가. +- `load_display_strategies()` + `DISPLAY_STRATEGIES` 모듈 변수 + `_KNOWN_CONTENT_TYPES` frozenset. +- catalog (`templates/phase_z2/regions/display_strategies.yaml`) 의 `applies_to` / `forbidden_for` 직독 기반 hard filter. + +lock: +- `text_block / table / image / details` → `dropped` 절대 X (catalog `forbidden_for` 박혀 있음 — 원문 무손실). +- hard filter = catalog `applies_to` / `forbidden_for` 직독 (hardcoding X). +- escalate signal (`long_text` / `large_table` / `fits_in_region == False`) → `inline_preview_with_details` 우선. +- decorative_element 의 dropped 는 `inline_full` 후순위 (공간 부족 신호 전에는 일단 보여주기). +- unknown content_type → `ValueError` (catalog scope 위반). + +why: +- 8-A catalog 위에 candidate 함수 추가 = passive piece 패턴 (7-A/B + 8-A 와 일관). +- 원문 무손실 lock 의 코드 enforcement. +- Step 9 application_plan 의 display_strategy axis input. + +next axis: +- Step 9 진입 시도 (compat 매트릭스 안) → 폐기 → boundary reframe (2026-05-08 entry 참조). + +--- + +## 2026-05-07 — Step 8-B-1 / region layout candidate function + +scope: +- `load_region_layouts()` + `REGION_LAYOUTS` 모듈 변수. +- `select_region_layout_candidates(region_count, ..., ratio_asymmetric, ...)` 함수 추가 — SPEC §2.5 sequential first-match decision tree. +- 6 entry catalog 와 1:1 일치 (catalog 직독, hardcoding X). + +lock: +- `region_count < 1 or > 4` → `ValueError` (SPEC §2.5 vocabulary scope). +- `region-vertical-stack` 만 `default_fallback: true` (SPEC 박힘). +- `ratio_asymmetric` 게이트 = `region-main-support` 의 catalog `candidate_when` 과 1:1 일치 (initial 누락 → Codex 검출 → 박힘). +- `region_count == 1` → `[region-single]` only (fallback X). + +why: +- SPEC §2.5 결정 트리 6 entry 의 코드 enforcement. +- catalog 와 함수 1:1 일치 (drift 방지) — 초기 ratio_asymmetric 시그니처 누락 발견 후 정정. +- Step 9 application_plan 의 region axis input. + +next axis: +- Step 8-B-2 (display strategy candidate function). + +--- + +## 2026-05-07 — Step 8-A / regions catalog (region + display) + +scope: +- `templates/phase_z2/regions/region_layouts.yaml` 신설 — SPEC §2.5 6 entry (region-single / vertical-stack / horizontal-split / main-support / preview-details / grid-2x2). +- `templates/phase_z2/regions/display_strategies.yaml` 신설 — 4 entry (inline_full / inline_preview_with_details / details_only / dropped). +- `templates/phase_z2/regions/regions_preview.html` 신설 — 6 region card + 4 display strategy card 시각 검증. + +lock: +- **axis 분리** : region (structure) ≠ display (policy). 두 catalog 는 직교 — 같은 region 이 다른 display strategy 와 결합 가능. +- **single source of truth** : `preserves_original` 은 display_strategies 의 책임 (region_layouts 에서 제거 — 사용자 검토 후 박힘). +- `detail_trigger.placement: top-right` (사용자 lock — 본문 흐름 방해 X / 보조 동작 위치 / popup 진입 일관 위치). +- `dropped` 의 `applies_to: [decorative_element]` only / `forbidden_for: [text_block, table, image, details]` (사용자 절대 룰 — 원문 무손실). +- `inline_preview_with_details` / `details_only` 의 `preserves_original: true` (popup 안 원문 보존). + +why: +- region 구조 vocabulary 와 display 정책 vocabulary 가 다른 axis. 한 catalog 에 합치면 단일 enum 필드로 표현 안 됨. +- 사용자 절대 lock (text/table/image/details 절대 dropped X) 의 catalog enforcement. +- 자유 디자인 금지 lock 과 정합 — preview HTML 에서 사람이 두 axis 시각 검증 가능. + +next axis: +- Step 8-B-1 / 8-B-2 (각 catalog 의 candidate function). + +--- + +## 2026-05-07 — Step 7-B / layout candidate function + +scope: +- `select_layout_candidates(unit_count)` 함수 추가 — `templates/phase_z2/layouts/layouts.yaml` catalog 직독 + `unit_count` 매칭. +- 출력 정렬 = `default_selection: true` 우선 + catalog 순서. + +lock: +- 입력 = `unit_count` (Step 4 의 placement unit count = section_count + lead_orphan promotion). +- `default_selection: true` 인 entry 가 list 의 첫 위치. +- `unit_count` 매칭 entry 0 개면 빈 list (ValueError X — Step 9 가 fallback path 처리). + +why: +- 명명 정정 = `section_count` → `unit_count` (lead_orphan promotion 후 의미 정확). +- Step 9 application_plan 의 layout axis input (V4 후보 + layout 후보 통합 평가). +- single-decision (`select_layout_preset`) 은 backward compat 으로 유지 — runtime 호출처는 default 만 사용. + +next axis: +- Step 8-A (regions catalog). + +--- + +## 2026-05-07 — Step 7-A / layout catalog 분리 + +scope: +- `templates/phase_z2/layouts/layouts.yaml` 신설 — 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). +- `templates/phase_z2/layouts/layouts_preview.html` 신설 — 8 preset 시각 검증. +- `load_layout_presets()` 함수 추가 — catalog → 기존 `LAYOUT_PRESETS` 와 동일 dict shape 반환. +- `src/phase_z2_composition.py` 의 hardcoded `LAYOUT_PRESETS` dict → catalog 이전 (logic 무변, backward compat). + +lock: +- **필드 정정** = 단일 `status` enum (implemented / defined) → 두 직교 axis 필드로 분리: + - `render_ready: bool` — layout 의 grid 정의 + 검증된 렌더 path 존재 여부 + - `default_selection: bool` — `select_layout_preset()` 의 default 픽 여부 (Step 7-B / 9 의 single-decision 영역) +- `candidate_when` 필드 추가 — Step 7-B / Step 9 input (현재 single-decision logic 은 무시 — inert). +- backward compat 유지 = `load_layout_presets()` 이 같은 dict shape 반환 → `LAYOUT_PRESETS` 사용처 무변. + +why: +- 사용자 절대 lock = "모든 catalog data 는 yaml/HTML 에서 사람이 보고 modify 가능" — hardcoded dict 위반. +- `status` 단일 enum 은 *render 가능성* 과 *default 선택* 두 axis 를 합쳐서 모호 — render_ready / default_selection 분리 (사용자 검토 후 박힘). +- 모든 기존 8 preset 의 logic 은 무변 — runtime 결과 동일 (regression 0). + +next axis: +- Step 7-B (layout candidate function). diff --git a/docs/architecture/PHASE-Z-PIPELINE-OVERVIEW.md b/docs/architecture/PHASE-Z-PIPELINE-OVERVIEW.md index 1157835..16282ee 100644 --- a/docs/architecture/PHASE-Z-PIPELINE-OVERVIEW.md +++ b/docs/architecture/PHASE-Z-PIPELINE-OVERVIEW.md @@ -211,6 +211,7 @@ inline preview = 원문의 *일부* 만 빌려 보여주는 것. 원문은 detai - **Status** : ⚠ partial - **Code 위치** : `lookup_v4_match()` in `phase_z2_pipeline.py` - **Gap** : 현재 *rank-1 만* 반환. top-k 사용 안 됨. sibling group 후보도 없음. +- **Note (사용자 잠금 2026-05-08)** : Step 5 axis = *non-reject max-6 후보 list* 로 보완 (rank-1 → top-N). raw 32 entry 는 `v4_full32_result.yaml` 영속, `step05_v4_evidence.json` = 정제 list. `lookup_v4_match()` 유지 (Step 6 backward compat). Step 9 application_plan input. #### Step 6. Composition Planning 어떤 MDX 덩어리를 하나의 *slide-level zone unit* 으로 볼지 결정. child 따로 / sibling 묶기 / parent 단위. @@ -219,6 +220,8 @@ inline preview = 원문의 *일부* 만 빌려 보여주는 것. 원문은 detai - **Status** : ⚠ partial - **Code 위치** : `src/phase_z2_composition.py` (`plan_composition`, `parent_merged_inferred`, `capacity_fit` integration) - **Gap** : section_layout_signature / content_object 구조 input 부재 (step 3, 4 가 없어서). frame compatibility 도 rank-1 매칭만 활용. +- **Note (사용자 잠금 2026-05-08)** : Step 6 = *6-A 박힘*. **6-A** (additive, logic 무변) = `CompositionUnit` 에 `v4_candidates: list[V4Match]` 필드 추가, 기존 단일 frame 필드는 `candidates[0]` 호환 유지 (✓ 박힘). +- **Note (사용자 잠금 2026-05-08 #2)** : **6-B (frame ownership transfer) 폐기 = misframed axis**. 정정된 mental model = *V4 가 frame 선택, Step 6 은 V4 rank-1 을 default 로 전사, Step 9 는 V4 후보를 application_plan 으로 번역*. Step 6 은 frame 채택 책임을 *원래부터 안 가짐* — 따라서 "Step 6 frame 채택 책임 → Step 9 이전" = 허구. 6-B 의 *실제 가능한 코드 변화* = MVP1_ALLOWED_STATUSES binary gate 위치 결정 → 이건 별 axis (label gate policy 재검토). 자세한 사유 = `PHASE-Z-CHANGE-LOG.md` 2026-05-08 #2 entry. #### Step 7. Slide-Level Layout Planning composition unit 개수와 성격을 보고 slide 전체 layout 선택. *기존 Type A/B/B'/B'' 의 후속 — 8-vocabulary 로 명시화*. @@ -255,6 +258,7 @@ composition unit 개수와 성격을 보고 slide 전체 layout 선택. *기존 - **Status** : ⚠ partial — *step 5 와 분리되지 않음 + region-level 미구현 (zone 단위 만)* - **Code 위치** : `plan_composition()` 이 V4 rank-1 즉시 선택 (step 5 와 conflate, zone 단위) - **Gap** : top-k 활용 / composition 제약 반영한 final 단계가 없음. *region-level 매칭 부재* (현재 zone 단위만). restructure label 은 현재 *filter* (선택 X). MVP1_ALLOWED_STATUSES = {matched_zone, adapt_matched_zone} 만 통과. +- **Note (사용자 잠금 2026-05-08)** : Step 9 = **application_plan reframe**. V4 후보 (Step 5) + layout 후보 (Step 7-B) + region/display 후보 (Step 8-B-1/2) 통합 적용 계획. *V4 axis 재계산 X* — V4 의 anchor / cardinality / relation / slot / content 산식은 Step 5 에서 끝남. Step 9 = V4 label (use_as_is / light_edit / restructure / reject) → application_mode (direct_insert / same_frame_with_adjustment / layout_or_region_change / exclude) 변환 + layout/region/display 통합. 폐기 안 = "compat 매트릭스" (region × frame slot count) — V4 cardinality 재계산 위험 (PHASE-Z-CHANGE-LOG.md 2026-05-08 entry 참조). #### Step 10. Frame Contract 확인 선택된 frame 의 contract 읽어서 accepted_content_types / slots / sub_zones / cardinality / capacity / visual_hints / density envelope / asset 확인. diff --git a/docs/architecture/PHASE-Z-PIPELINE-STATUS-BOARD.md b/docs/architecture/PHASE-Z-PIPELINE-STATUS-BOARD.md index a9ad748..b90f0ce 100644 --- a/docs/architecture/PHASE-Z-PIPELINE-STATUS-BOARD.md +++ b/docs/architecture/PHASE-Z-PIPELINE-STATUS-BOARD.md @@ -1,6 +1,6 @@ # Phase Z — pipeline status board -**Snapshot date** : 2026-05-04 (B1~B5 + trace-only runtime 연결 closure 반영 — Layer A telemetry first activation) +**Snapshot date** : 2026-05-08 (Step 7-A/B + 8-A/B-1/B-2 catalog/candidate fn axis closure / Step 5-6-9 boundary reframe lock) **역할** : 현재 위치표 / grading snapshot. *지도 본문* 은 [`PHASE-Z-PIPELINE-OVERVIEW.md`](PHASE-Z-PIPELINE-OVERVIEW.md). | 문서 | 역할 | 변동 | @@ -43,8 +43,8 @@ Step 0 은 본체가 아닌 *준비 조건*. Step 1 (MDX 업로드) 부터가 ru | A | 4 | Section Internal Composition Planning | ⚠ partial (B2 v0 dormant module + trace-only runtime 호출, render path 미연결) | | A | 5 | Matching Evidence 생성 | ⚠ partial (rank-1 only) | | A | 6 | Composition Planning | ⚠ partial | -| A | 7 | Slide-Level Layout Planning | ⚠ partial (count-based) | -| A | 8 | Zone + Internal Region Ratio Planning | ⚠ partial (zone-level horizontal-2 만 dynamic, region-level 은 B2 안 partial) | +| A | 7 | Slide-Level Layout Planning | ⚠ partial (count-based / 7-A catalog + 7-B candidate fn 추가, runtime 호출처 X) | +| A | 8 | Zone + Internal Region Ratio Planning | ⚠ partial (zone-level horizontal-2 만 dynamic / 8-A region+display catalog + 8-B-1/2 candidate fn 추가, runtime 호출처 X / region-level 은 B2 안 partial) | | A | 9 | Region-Level Frame / Display Selection | ⚠ partial (B4 가 catalog cover + declaration order 로 frame 선택 분담 / V4 evidence 미통합 / Step 5 와 conflate 잔존) | | A | 10 | Frame Contract 확인 | ⚠ partial (B3 의 accepted_content_types + sub_zones 선언 추가 — B4 만 읽음, mapper 미읽음 / density envelope 별 axis) | | A | 11 | Content Unit / Child Group → Internal Region → Frame Slot Mapping | ⚠ partial (B4 v0 dormant 2-stage + region 1:1 sub_zone + narrowest first + trace-only runtime 호출, render path 미연결) | @@ -94,6 +94,20 @@ Step 0 은 본체가 아닌 *준비 조건*. Step 1 (MDX 업로드) 부터가 ru - tabular_overflow / image_aspect_mismatch 검사 부재 (Step 14) - layout_adjust / frame_reselect / details_popup_escalation / image_fit / frame_internal_fit_candidate (Step 17 missing actions) 미구현 + +5. Step 5 / 6 / 9 boundary axis breakdown (2026-05-08 lock + 2026-05-08 #2 정정) + - Step 5 = rank-1 only → non-reject max-6 후보 list 로 보완 (✓ 박힘) + - Step 6-A = CompositionUnit v4_candidates 필드 additive (✓ 박힘) + - Step 7-conn / Step 8-conn = layout / region / display candidates artifact 연결 (✓ 박힘, Step 8 placeholder signals) + - Step 9 v0 = passive application_plan artifact (✓ 박힘 — V4 후보 + layout/region/display + 통합 적용 계획, V4 axis 재계산 X) + - 폐기된 안 (2 가지) : + · "compat 매트릭스" (region × frame slot count) — V4 cardinality 재계산 위험 + (CHANGE-LOG.md 2026-05-08 entry 참조) + · "6-B (frame ownership transfer)" — misframed axis. V4 가 frame 선택, Step 6 은 + 전사, Step 9 는 번역. Step 6 의 frame 채택 책임 = 허구 (CHANGE-LOG.md + 2026-05-08 #2 entry 참조). 6-B 의 진짜 content (label gate policy) 는 별 axis. + - 닫힌 axis = 5 / 6-A / 7-conn / 8-conn / Step 9 v0 ``` **Cross-cutting Layer A — 진전 단계 정리** : @@ -147,7 +161,7 @@ Step 0 (사전 준비) 의 Figma → HTML 변환은 *precondition phase 의 작 ## 6. 현재 병목 (한 줄) -> 현재 Phase Z 의 *Layer A pre-render planning* (Step 3 / 4 / 11) 은 본 session 작업으로 ❌ → ⚠ partial 전이 (B1/B2/B4 dormant module + trace-only runtime 호출). *Layer A telemetry 의 first activation* — debug.json 의 placement_trace per-zone + frame_slot_metrics F29 partial 기록. 단 **render path 활성화는 미완** : B4 PlacementPlan 이 mapper output 을 *대체하지 않고* trace-only / region-id / content_unit_id marker 가 partial template 에 *미주입* / B4 frame_selection 이 V4 evidence *미통합*. 핵심 다음 axis = **(B5 후속) render path 의 placement_trace 활용 + region marker runtime activation + V4 통합**. *runtime contract-registered / verified frame set 이 text-frame 중심* 한계는 잔존 (frame inventory audit / refinement 별 axis). +> 현재 Phase Z 의 *Layer A pre-render planning* (Step 3 / 4 / 11) 은 본 session 작업으로 ❌ → ⚠ partial 전이 (B1/B2/B4 dormant module + trace-only runtime 호출). *Layer A telemetry 의 first activation* — debug.json 의 placement_trace per-zone + frame_slot_metrics F29 partial 기록. 단 **render path 활성화는 미완** : B4 PlacementPlan 이 mapper output 을 *대체하지 않고* trace-only / region-id / content_unit_id marker 가 partial template 에 *미주입* / B4 frame_selection 이 V4 evidence *미통합*. **Step 5/6/9 boundary axis breakdown 닫힘** (Step 5 / 6-A / 7-conn / 8-conn / Step 9 v0 박힘 — passive application_plan artifact). 6-B (frame ownership transfer) = misframed axis 폐기 (CHANGE-LOG.md 2026-05-08 #2). 핵심 다음 axis 후보 (별 axis lock) = **(A) V4 frame 후보 → Phase Z render path 연결 확장 (F14/F11/F18 등 미연결 frame adapter — contract + partial + builder, *figma 새 디자인 X / V4 새 매칭 X*)**, **(B) Step 17 details_popup_escalation**, **(C) Step 4 unit_count 산출**, **(D) Step 3/4 render path 활성화 (Layer A activation)**, **(E) label gate policy 재검토 (= 6-B 의 진짜 content 였던 것)**, **(F) Step 9 v1 scoring + auto decision**. *runtime contract-registered / verified frame set 이 text-frame 중심* 한계는 (A) 가 직접 푸는 axis. --- diff --git a/docs/tasks/EXTRACT-ALL-TEXTS.md b/docs/tasks/EXTRACT-ALL-TEXTS.md new file mode 100644 index 0000000..f065681 --- /dev/null +++ b/docs/tasks/EXTRACT-ALL-TEXTS.md @@ -0,0 +1,77 @@ +# 요청: Figma 전체 프레임 texts.md 추출 + +## 목표 + +Figma 파일의 **모든 프레임**에서 texts.md를 추출한다. +HTML, CSS, 이미지 등은 불필요. **텍스트만** 추출. + +## Figma 파일 + +- URL: https://www.figma.com/design/9S6LsQyO6zlRxtiqZccOUM/Untitled?node-id=29-373&t=DjVfH90i8r4YiiM6-1 + +## 이미 완료된 프레임 (14개) + +아래 프레임들은 이미 texts.md가 있으므로 **건너뛴다**. + +``` +1171281172, 1171281178, 1171281180, 1171281189, +1171281190, 1171281191, 1171281193, 1171281194, +1171281195, 1171281201, 1171281202, 1171281203, +1171281204, 1171281208 +``` + +## 작업 내용 + +1. Figma 파일에서 **최상위 프레임 전체 목록** 조회 +2. 이미 완료된 14개를 제외한 **나머지 프레임들**에 대해 +3. 각 프레임별로 `figma_to_html_agent/blocks/{frame_id}/texts.md` 생성 + +## texts.md 포맷 + +기존과 동일한 구조: + +```markdown +# Frame {frame_id} — 텍스트 (TF-IDF 매칭용) + +> 프레임 안의 모든 텍스트를 빠짐없이 추출. + +## 타이틀 +프레임의 메인 제목 + +## 서브헤더 (있으면) +서브 제목 + +## 열1: 라벨 (열 구조인 경우) +### 소제목 +텍스트 내용 + +## 행1 (행 구조인 경우) +라벨 +본문 텍스트 + +## 결론 (있으면) +결론 텍스트 +``` + +### 핵심 규칙 + +- 프레임 안의 **모든 텍스트 노드**를 빠짐없이 추출 +- 위치/크기 기준으로 **타이틀/서브/본문** 구분 +- 큰 텍스트(상단) → 타이틀 +- 중간 텍스트 → 서브헤더/라벨 +- 작은 텍스트(본문) → body +- 열/행 구조가 보이면 `## 열1`, `## 행1` 등으로 구분 +- MCP `get_metadata` + `get_design_context`로 텍스트 전수 대조 + +## 저장 위치 + +``` +figma_to_html_agent/blocks/{frame_id}/texts.md +``` + +프레임 폴더가 없으면 새로 생성. + +## 용도 + +이 texts.md는 나중에 **MDX 중목차/소목차와 TF-IDF 매칭**할 때 사용됩니다. +그래서 텍스트가 빠지면 매칭이 안 되므로, **빠짐없이** 추출하는 것이 중요합니다. diff --git a/run_mdx03_pipeline.py b/run_mdx03_pipeline.py index 8518907..c107992 100644 --- a/run_mdx03_pipeline.py +++ b/run_mdx03_pipeline.py @@ -1,32 +1,36 @@ -"""MDX 03을 기존 파이프라인으로 Stage 1.7까지 돌린 뒤, 산출물을 저장. +"""MDX 03을 기존 파이프라인 (Phase Q) 또는 Phase Z-2 MVP-1 로 실행. -파이프라인 코드 그대로 사용: -- Stage 0: mdx_normalizer -- Stage 1A: kei_client.classify_content (Kei API) -- Stage 1B: kei_client.refine_concepts + generate_structured_text -- Stage 1.5a: space_allocator (컨테이너 계산 + font_hierarchy) -- Stage 1.7: block_reference (블록 선택) +기본 (no flag) : 기존 Phase Q 파이프라인 — async, Kei API 사용 + - Stage 0: mdx_normalizer + - Stage 1A/1B: kei_client (분류 + 정제) + - Stage 1.5a: space_allocator + - Stage 1.7: block_reference + - 출력 : data/runs/{run_id}/final.html -Stage 2(조립)는 여기서 하지 않음 — 산출물만 저장. +`--phase-z2` flag : Phase Z-2 MVP-1 — sync, AI 미사용, 결정론적 + - matched_zone only (V4 use_as_is) — 그 외 abort + error.json + - 출력 : data/runs/{run_id}/phase_z2/final.html + - 상세 : src/phase_z2_pipeline.py + docs/architecture/PHASE-Z-CATALOG-RUNTIME-DESIGN.md § 16 """ +import argparse import asyncio -import json import sys import time from pathlib import Path sys.path.insert(0, str(Path(__file__).parent)) -from src.pipeline import generate_slide + +DEFAULT_MDX = Path("samples/mdx/03. DX 시행을 위한 필수 요건 및 혁신 방안.mdx") -async def main(): - mdx_path = Path("samples/mdx/03. DX 시행을 위한 필수 요건 및 혁신 방안.mdx") +async def run_phase_q(mdx_path: Path): + """기존 Phase Q 파이프라인 실행.""" + from src.pipeline import generate_slide + content = mdx_path.read_text(encoding="utf-8") - - print(f"MDX 03: {mdx_path.name}") - print(f"내용 길이: {len(content)}자") - print() + print(f"[Phase Q] MDX: {mdx_path.name}") + print(f" 내용 길이: {len(content)}자\n") start = time.time() async for event in generate_slide(content, base_path=str(mdx_path.parent)): @@ -39,10 +43,33 @@ async def main(): print(f"\n완료! ({elapsed:.1f}초)") if isinstance(data, dict): run_id = data.get("run_id", "") - print(f"run_id: {run_id}") - print(f"결과: data/runs/{run_id}/") + print(f" run_id: {run_id}") + print(f" 결과: data/runs/{run_id}/") elif ev_type == "error": - print(f" 에러: {data}") + print(f" 에러: {data}", file=sys.stderr) -asyncio.run(main()) +def run_phase_z2(mdx_path: Path, run_id: str = None): + """Phase Z-2 MVP-1 파이프라인 실행 (sync, AI 미사용).""" + from src.phase_z2_pipeline import run_phase_z2_mvp1 + run_phase_z2_mvp1(mdx_path, run_id) + + +def main(): + parser = argparse.ArgumentParser(description="MDX 03 pipeline runner.") + parser.add_argument("--phase-z2", action="store_true", + help="Phase Z-2 MVP-1 (sync, AI 미사용, matched_zone only).") + parser.add_argument("--mdx", type=Path, default=DEFAULT_MDX, + help=f"MDX 파일 경로 (default: {DEFAULT_MDX}).") + parser.add_argument("--run-id", type=str, default=None, + help="run_id 오버라이드 (default: timestamp).") + args = parser.parse_args() + + if args.phase_z2: + run_phase_z2(args.mdx, args.run_id) + else: + asyncio.run(run_phase_q(args.mdx)) + + +if __name__ == "__main__": + main() diff --git a/samples/mdx/04. DX 지연 요인.mdx b/samples/mdx/04. DX 지연 요인.mdx new file mode 100644 index 0000000..2104520 --- /dev/null +++ b/samples/mdx/04. DX 지연 요인.mdx @@ -0,0 +1,262 @@ +--- +title: DX 지연 요인 +sidebar: + order: 03 +--- + +## 1. DX에 대한 인식 +
+
+ +
{ + e.currentTarget.style.transform = 'translateY(-3px)'; + e.currentTarget.style.boxShadow = '0 6px 12px rgba(0,0,0,0.1)'; + }} + onMouseOut={(e) => { + e.currentTarget.style.transform = 'translateY(0)'; + e.currentTarget.style.boxShadow = '0 4px 6px rgba(0,0,0,0.05)'; + }} + > +
+

기술 및 소프트웨어 이해도

+
+

"무슨 말인지 잘 모르겠다, 어디까지 어떻게 해야 하는지 모르겠다"

+
    +
  • 기본지침, 시행지침 등 새롭게 알아야 할 게 너무 많다.
  • +
  • 3D 모델과 별 차이점을 모르겠다. S/W마다 사용법이 다르다.
  • +
  • 필요한 것은 쉽고 간단한 건데, 왜 이렇게 복잡하게 만들까?
  • +
+
+ +
{ + e.currentTarget.style.transform = 'translateY(-3px)'; + e.currentTarget.style.boxShadow = '0 6px 12px rgba(0,0,0,0.1)'; + }} + onMouseOut={(e) => { + e.currentTarget.style.transform = 'translateY(0)'; + e.currentTarget.style.boxShadow = '0 4px 6px rgba(0,0,0,0.05)'; + }} + > +
+

효과와 효율성

+
+

"2D 설계 후 전환설계를 수행하는데 효과는 모르겠고, 효율은 낮다"

+
    +
  • 성과품 작성은 기존과 같게 하고, 추가 업무만 발생해 효율이 낮다.
  • +
  • BIM으로 인해 가중되는 업무 대비 효과가 거의 없어 보인다.
  • +
  • 결과적으로 큰 차이를 못 느끼겠고, 이런 노력이 정말 가치 있는 일이야?
  • +
+
+ +
{ + e.currentTarget.style.transform = 'translateY(-3px)'; + e.currentTarget.style.boxShadow = '0 6px 12px rgba(0,0,0,0.1)'; + }} + onMouseOut={(e) => { + e.currentTarget.style.transform = 'translateY(0)'; + e.currentTarget.style.boxShadow = '0 4px 6px rgba(0,0,0,0.05)'; + }} + > +
+

인력 및 교육

+
+

"수행 인력이 부족하고, 기존 직원들은 어떻게 교육해야 하나"

+
    +
  • 교육시간 손실로 일손이 더 필요해지고, 적응하는데도 시간이 걸린다.
  • +
  • 신입사원은 없고 BIM 수행을 할 수 있는 기술자가 부족하다.
  • +
  • 여러 회사의 S/W별 사용법이 달라 새로운 S/W에 적용에 시간이 필요하다.
  • +
+
+ +
{ + e.currentTarget.style.transform = 'translateY(-3px)'; + e.currentTarget.style.boxShadow = '0 6px 12px rgba(0,0,0,0.1)'; + }} + onMouseOut={(e) => { + e.currentTarget.style.transform = 'translateY(0)'; + e.currentTarget.style.boxShadow = '0 4px 6px rgba(0,0,0,0.05)'; + }} + > +
+

경제적 부담

+
+

"S/W 구독료만 크게 발생되고, 비용 보전은 안 된다"

+
    +
  • 사용해야 할 S/W의 종류가 너무 많고 복잡한데 모두 필요한가?
  • +
  • 모든 Project에 적용되는 것도 아닌데, 다수의 S/W 구독료를 내야 한다.
  • +
  • 디지털전환 하기 위해 이 비용을 쓰는게 정말 경제적인 이득이 있는 거야?
  • +
+
+ +
{ + e.currentTarget.style.transform = 'translateY(-3px)'; + e.currentTarget.style.boxShadow = '0 6px 12px rgba(0,0,0,0.1)'; + }} + onMouseOut={(e) => { + e.currentTarget.style.transform = 'translateY(0)'; + e.currentTarget.style.boxShadow = '0 4px 6px rgba(0,0,0,0.05)'; + }} + > +
+

실무 및 적용성

+
+

"실무적 사용에 의한 효율성 증진보다는 홍보, PQ용으로 사용한다"

+
    +
  • 구체적 적용에 의한 비용, 시간, 품질 등의 효과 사례가 없다.
  • +
  • 지형, 선형, 도로, 교량 Model을 만드는 S/W가 모두 달라 적용이 어렵다.
  • +
  • 속성정보를 반영하지 않은 형상 위주의 3D 모델 제작에만 초점이 맞춰져 있다.
  • +
+
+
+
+--- +
+## 2. DX 추진의 실태 + +
+ +### 2.1 정책 및 발주 체계 +- **실질적 기술 경쟁을 저해하는 정책 집행** + - 모든 설계사가 수행 능력을 갖추었다는 전제하에 정책 시행 + - 수행 능력이 없는 업체 선정 후 성과품의 수준을 낮추어 시행 +
+- **적용 효과가 있는 사례도 없이 방침부터 도입** + - DX/BIM 적용에 따른 실무적 이득이 있다고 판단된 사례 부족 + - BIM 지침/방침 등을 시행 경험과 효과 검증도 없이 남발 +
+- **엔지니어링 S/W에 대한 개념 부재** + - 다양한 엔지니어링 S/W의 특성에 대한 깊은 이해 없이 범용 S/W 선택 + - 대형 Global S/W 회사에 과도한 의존과 이에 예속되는 방침 남발로 전용 S/W 소멸 +
+- **기술투자(R&D) 없는 성과 창출 기대** + - 단순 BIM S/W만 구입하면 될 것이라는 안일한 생각 + - 실질적 기술 개발 투자 노력 없이 남들이 하는 대로 하면 된다는 착각 +
+
+ 발주처 반응 + +
+ +### 2.2 조직 및 수행 역량 +- **공학적 개념 정립 부재** + - DX와 BIM의 차이점을 명확히 구분하지 못하고 접근 방식과 기술적 도구 사이의 혼란만 가중 + - 단순히 기술적 도구의 사용에 초점을 맞추느라 3D모델 제작 S/W에 과도하게 의존 +
+- **‘본업 기술력 확보’ 우선의 개념 부재** + - 고도의 전문지식과 현장 경험이 축적된 Manual의 중요성과 필요성에 대한 이해 부족 + - 국가·발주처의 지침·방침에만 의존한 업체의 기술력 +
+- **DX/BIM의 근본 취지와 목표의 이해 부족** + - DX에 의한 과정의 혁신과 결과물의 변화에 대한 고민 부재 + - 기술자가 직접 3D모델을 만들고 수정하며 설계를 수행하지 않고, 별도로 외주 처리하여 본질 회피 +
+- **과거의 타성에 머무르고 있는 기술자 집단** + - 설계/감리/시공 임직원들의 Digital 무지와 전략적 무지 + - 교육과 학습을 통한 인재 양성보다 당장 실무 활용이 가능한 타사 인력 빼오기에 집중 +
+
+ 설계·시공업계 반응 + +
+
+--- + +:::note[핵심 요약] +* 검증 없는 정책의 일방적 추진과 조직의 회피, 이해 부족은 DX 지연을 반복시키고 있다. +::: \ No newline at end of file diff --git a/samples/mdx_batch/04.mdx b/samples/mdx_batch/04.mdx new file mode 100644 index 0000000..2104520 --- /dev/null +++ b/samples/mdx_batch/04.mdx @@ -0,0 +1,262 @@ +--- +title: DX 지연 요인 +sidebar: + order: 03 +--- + +## 1. DX에 대한 인식 +
+
+ +
{ + e.currentTarget.style.transform = 'translateY(-3px)'; + e.currentTarget.style.boxShadow = '0 6px 12px rgba(0,0,0,0.1)'; + }} + onMouseOut={(e) => { + e.currentTarget.style.transform = 'translateY(0)'; + e.currentTarget.style.boxShadow = '0 4px 6px rgba(0,0,0,0.05)'; + }} + > +
+

기술 및 소프트웨어 이해도

+
+

"무슨 말인지 잘 모르겠다, 어디까지 어떻게 해야 하는지 모르겠다"

+
    +
  • 기본지침, 시행지침 등 새롭게 알아야 할 게 너무 많다.
  • +
  • 3D 모델과 별 차이점을 모르겠다. S/W마다 사용법이 다르다.
  • +
  • 필요한 것은 쉽고 간단한 건데, 왜 이렇게 복잡하게 만들까?
  • +
+
+ +
{ + e.currentTarget.style.transform = 'translateY(-3px)'; + e.currentTarget.style.boxShadow = '0 6px 12px rgba(0,0,0,0.1)'; + }} + onMouseOut={(e) => { + e.currentTarget.style.transform = 'translateY(0)'; + e.currentTarget.style.boxShadow = '0 4px 6px rgba(0,0,0,0.05)'; + }} + > +
+

효과와 효율성

+
+

"2D 설계 후 전환설계를 수행하는데 효과는 모르겠고, 효율은 낮다"

+
    +
  • 성과품 작성은 기존과 같게 하고, 추가 업무만 발생해 효율이 낮다.
  • +
  • BIM으로 인해 가중되는 업무 대비 효과가 거의 없어 보인다.
  • +
  • 결과적으로 큰 차이를 못 느끼겠고, 이런 노력이 정말 가치 있는 일이야?
  • +
+
+ +
{ + e.currentTarget.style.transform = 'translateY(-3px)'; + e.currentTarget.style.boxShadow = '0 6px 12px rgba(0,0,0,0.1)'; + }} + onMouseOut={(e) => { + e.currentTarget.style.transform = 'translateY(0)'; + e.currentTarget.style.boxShadow = '0 4px 6px rgba(0,0,0,0.05)'; + }} + > +
+

인력 및 교육

+
+

"수행 인력이 부족하고, 기존 직원들은 어떻게 교육해야 하나"

+
    +
  • 교육시간 손실로 일손이 더 필요해지고, 적응하는데도 시간이 걸린다.
  • +
  • 신입사원은 없고 BIM 수행을 할 수 있는 기술자가 부족하다.
  • +
  • 여러 회사의 S/W별 사용법이 달라 새로운 S/W에 적용에 시간이 필요하다.
  • +
+
+ +
{ + e.currentTarget.style.transform = 'translateY(-3px)'; + e.currentTarget.style.boxShadow = '0 6px 12px rgba(0,0,0,0.1)'; + }} + onMouseOut={(e) => { + e.currentTarget.style.transform = 'translateY(0)'; + e.currentTarget.style.boxShadow = '0 4px 6px rgba(0,0,0,0.05)'; + }} + > +
+

경제적 부담

+
+

"S/W 구독료만 크게 발생되고, 비용 보전은 안 된다"

+
    +
  • 사용해야 할 S/W의 종류가 너무 많고 복잡한데 모두 필요한가?
  • +
  • 모든 Project에 적용되는 것도 아닌데, 다수의 S/W 구독료를 내야 한다.
  • +
  • 디지털전환 하기 위해 이 비용을 쓰는게 정말 경제적인 이득이 있는 거야?
  • +
+
+ +
{ + e.currentTarget.style.transform = 'translateY(-3px)'; + e.currentTarget.style.boxShadow = '0 6px 12px rgba(0,0,0,0.1)'; + }} + onMouseOut={(e) => { + e.currentTarget.style.transform = 'translateY(0)'; + e.currentTarget.style.boxShadow = '0 4px 6px rgba(0,0,0,0.05)'; + }} + > +
+

실무 및 적용성

+
+

"실무적 사용에 의한 효율성 증진보다는 홍보, PQ용으로 사용한다"

+
    +
  • 구체적 적용에 의한 비용, 시간, 품질 등의 효과 사례가 없다.
  • +
  • 지형, 선형, 도로, 교량 Model을 만드는 S/W가 모두 달라 적용이 어렵다.
  • +
  • 속성정보를 반영하지 않은 형상 위주의 3D 모델 제작에만 초점이 맞춰져 있다.
  • +
+
+
+
+--- +
+## 2. DX 추진의 실태 + +
+ +### 2.1 정책 및 발주 체계 +- **실질적 기술 경쟁을 저해하는 정책 집행** + - 모든 설계사가 수행 능력을 갖추었다는 전제하에 정책 시행 + - 수행 능력이 없는 업체 선정 후 성과품의 수준을 낮추어 시행 +
+- **적용 효과가 있는 사례도 없이 방침부터 도입** + - DX/BIM 적용에 따른 실무적 이득이 있다고 판단된 사례 부족 + - BIM 지침/방침 등을 시행 경험과 효과 검증도 없이 남발 +
+- **엔지니어링 S/W에 대한 개념 부재** + - 다양한 엔지니어링 S/W의 특성에 대한 깊은 이해 없이 범용 S/W 선택 + - 대형 Global S/W 회사에 과도한 의존과 이에 예속되는 방침 남발로 전용 S/W 소멸 +
+- **기술투자(R&D) 없는 성과 창출 기대** + - 단순 BIM S/W만 구입하면 될 것이라는 안일한 생각 + - 실질적 기술 개발 투자 노력 없이 남들이 하는 대로 하면 된다는 착각 +
+
+ 발주처 반응 + +
+ +### 2.2 조직 및 수행 역량 +- **공학적 개념 정립 부재** + - DX와 BIM의 차이점을 명확히 구분하지 못하고 접근 방식과 기술적 도구 사이의 혼란만 가중 + - 단순히 기술적 도구의 사용에 초점을 맞추느라 3D모델 제작 S/W에 과도하게 의존 +
+- **‘본업 기술력 확보’ 우선의 개념 부재** + - 고도의 전문지식과 현장 경험이 축적된 Manual의 중요성과 필요성에 대한 이해 부족 + - 국가·발주처의 지침·방침에만 의존한 업체의 기술력 +
+- **DX/BIM의 근본 취지와 목표의 이해 부족** + - DX에 의한 과정의 혁신과 결과물의 변화에 대한 고민 부재 + - 기술자가 직접 3D모델을 만들고 수정하며 설계를 수행하지 않고, 별도로 외주 처리하여 본질 회피 +
+- **과거의 타성에 머무르고 있는 기술자 집단** + - 설계/감리/시공 임직원들의 Digital 무지와 전략적 무지 + - 교육과 학습을 통한 인재 양성보다 당장 실무 활용이 가능한 타사 인력 빼오기에 집중 +
+
+ 설계·시공업계 반응 + +
+
+--- + +:::note[핵심 요약] +* 검증 없는 정책의 일방적 추진과 조직의 회피, 이해 부족은 DX 지연을 반복시키고 있다. +::: \ No newline at end of file diff --git a/scripts/eval_block_matcher.py b/scripts/eval_block_matcher.py new file mode 100644 index 0000000..a42713b --- /dev/null +++ b/scripts/eval_block_matcher.py @@ -0,0 +1,82 @@ +"""블록 매칭 비교 스크립트. + +기존 tag/item_count 매칭과 새 TF-IDF 매칭을 나란히 비교. +"진짜 좋아졌나?"를 판단하기 위한 도구. + +사용법: + python scripts/eval_block_matcher.py + +출력: + 각 MDX의 중목차별로: + - legacy 매칭 결과 (기존) + - tfidf 매칭 결과 (새) + - 일치 여부 +""" +import sys +from pathlib import Path + +sys.path.insert(0, str(Path(__file__).parent.parent)) + +from src.mdx_normalizer import normalize_mdx_content as normalize_mdx +from src.pipeline_v2 import match_blocks_for_sections + + +def evaluate_mdx(mdx_path: Path): + """단일 MDX에 대해 TF-IDF 매칭 결과를 출력.""" + content = mdx_path.read_text(encoding="utf-8") + result = normalize_mdx(content) + sections = result.get("sections", []) + + print(f"\n{'='*60}") + print(f"MDX: {mdx_path.name}") + print(f"{'='*60}") + + v2_results = match_blocks_for_sections(sections) + + for zone_name, info in v2_results.items(): + path = info["path"] + match = info.get("match") + sub_titles = info.get("sub_titles", []) + candidates = info.get("candidates", []) + + print(f"\n zone: {zone_name}") + print(f" sub_titles: {sub_titles}") + print(f" path: {path}") + + if match: + print(f" ✅ direct-fit: {match['block_id']} (score={match['score']})") + else: + print(f" → recipe 경로") + if candidates: + for i, c in enumerate(candidates): + print(f" 후보 {i+1}: {c['block_id']} (score={c['score']})") + else: + print(f" 후보 없음") + + +def main(): + mdx_dir = Path("samples/mdx") + if not mdx_dir.exists(): + print(f"MDX 폴더 없음: {mdx_dir}") + return + + mdx_files = sorted(mdx_dir.glob("*.mdx")) + if not mdx_files: + print("MDX 파일 없음") + return + + print(f"블록 매칭 평가 ({len(mdx_files)}개 MDX)") + print(f"catalog: templates/catalog/blocks.yaml") + + for mdx_path in mdx_files: + try: + evaluate_mdx(mdx_path) + except Exception as e: + print(f"\n ❌ {mdx_path.name}: {e}") + + print(f"\n{'='*60}") + print("평가 완료") + + +if __name__ == "__main__": + main() diff --git a/scripts/fetch_all_frame_screenshots.py b/scripts/fetch_all_frame_screenshots.py new file mode 100644 index 0000000..0e7f080 --- /dev/null +++ b/scripts/fetch_all_frame_screenshots.py @@ -0,0 +1,180 @@ +"""모든 Figma 프레임의 스크린샷을 번호 붙은 단일 폴더로 정리. + +결과: + data/figma_previews/01.png, 02.png, ..., 32.png + data/figma_previews/index.json ({number: {frame_id, node_id, title}}) +""" +from __future__ import annotations + +import base64 +import json +import sys +from pathlib import Path +from urllib import error, request + +sys.path.insert(0, str(Path(__file__).parent.parent)) + +from src.frame_extractor import extract_all_frames + +MCP_URL = "http://127.0.0.1:3845/mcp" +OUT_DIR = Path("data/figma_previews") + +# frame_id → node_id (32개, metadata에서 추출) +FRAME_NODE_MAP = { + "1171281172": "145:8352", + "1171281173": "182:2870", + "1171281174": "182:2810", + "1171281175": "182:2829", + "1171281176": "182:3046", + "1171281177": "182:3053", + "1171281178": "145:8394", + "1171281179": "182:3024", + "1171281180": "112:87", + "1171281181": "182:2572", + "1171281182": "182:2523", + "1171281189": "100:65", + "1171281190": "51:99", + "1171281191": "100:132", + "1171281192": "182:2602", + "1171281193": "106:205", + "1171281194": "112:7", + "1171281195": "106:252", + "1171281197": "182:2727", + "1171281198": "182:2766", + "1171281201": "145:8310", + "1171281202": "112:49", + "1171281203": "145:8266", + "1171281204": "145:8223", + "1171281205": "182:2668", + "1171281206": "182:2643", + "1171281208": "145:8504", + "1171281209": "145:8523", + "1171281210": "181:2519", + "1171281211": "181:2520", + "1171281212": "181:2521", + "1171281213": "181:2522", +} + + +def parse_sse(body: str) -> dict: + for line in body.splitlines(): + if line.startswith("data: "): + return json.loads(line[6:]) + raise RuntimeError(f"No data line in response: {body[:200]}") + + +def post(payload: dict, session_id: str | None = None) -> tuple[dict, str | None]: + data = json.dumps(payload).encode() + req = request.Request( + MCP_URL, + data=data, + method="POST", + headers={ + "Content-Type": "application/json", + "Accept": "application/json, text/event-stream", + }, + ) + if session_id: + req.add_header("mcp-session-id", session_id) + with request.urlopen(req, timeout=60) as resp: + body = resp.read().decode() + sid = resp.headers.get("mcp-session-id") + return (parse_sse(body) if body.strip() else {}, sid) + + +def initialize() -> str: + payload = { + "jsonrpc": "2.0", + "id": 1, + "method": "initialize", + "params": { + "protocolVersion": "2024-11-05", + "capabilities": {}, + "clientInfo": {"name": "frame-dumper", "version": "1.0"}, + }, + } + _, sid = post(payload) + notify = {"jsonrpc": "2.0", "method": "notifications/initialized", "params": {}} + data = json.dumps(notify).encode() + req = request.Request( + MCP_URL, + data=data, + method="POST", + headers={ + "Content-Type": "application/json", + "Accept": "application/json, text/event-stream", + "mcp-session-id": sid or "", + }, + ) + try: + request.urlopen(req, timeout=10).read() + except error.HTTPError: + pass + return sid or "" + + +def get_screenshot(session_id: str, node_id: str, call_id: int) -> bytes: + payload = { + "jsonrpc": "2.0", + "id": call_id, + "method": "tools/call", + "params": {"name": "get_screenshot", "arguments": {"nodeId": node_id}}, + } + resp, _ = post(payload, session_id=session_id) + if "error" in resp: + raise RuntimeError(f"MCP error for {node_id}: {resp['error']}") + for item in resp.get("result", {}).get("content", []): + if item.get("type") == "image": + return base64.b64decode(item["data"]) + raise RuntimeError(f"No image in response for {node_id}") + + +def main() -> int: + # frame_id → title_text 맵 + frames = extract_all_frames("figma_to_html_agent/blocks") + title_map = {f["frame_id"]: (f.get("title_text") or "").replace("\n", " ")[:80] for f in frames} + + # 정렬된 frame_id 목록에 1부터 번호 매김 + frame_ids = sorted(FRAME_NODE_MAP.keys()) + + OUT_DIR.mkdir(parents=True, exist_ok=True) + + print("[init] MCP session...") + sid = initialize() + print(f"[init] session-id={sid}") + + index: dict[str, dict] = {} + for i, fid in enumerate(frame_ids, start=1): + node_id = FRAME_NODE_MAP[fid] + num = f"{i:02d}" + out_path = OUT_DIR / f"{num}.png" + title = title_map.get(fid, "") + + if out_path.exists(): + print(f"[{num}] {fid} (node {node_id}) — 이미 있음, skip") + else: + print(f"[{num}] {fid} (node {node_id}) fetching...") + try: + png = get_screenshot(sid, node_id, 100 + i) + out_path.write_bytes(png) + print(f" saved {len(png)} bytes → {out_path}") + except Exception as e: + print(f" FAILED: {e}") + continue + + index[num] = { + "frame_id": fid, + "node_id": node_id, + "title_text": title, + "png": f"{num}.png", + } + + (OUT_DIR / "index.json").write_text( + json.dumps(index, ensure_ascii=False, indent=2), encoding="utf-8" + ) + print(f"\n[done] {len(index)}개 저장, index: {OUT_DIR/'index.json'}") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/scripts/match_17_units_my_matcher.py b/scripts/match_17_units_my_matcher.py new file mode 100644 index 0000000..85970f6 --- /dev/null +++ b/scripts/match_17_units_my_matcher.py @@ -0,0 +1,334 @@ +"""17개 콘텐츠 단위를 각각 추출하여 내 매처(src/block_matcher_tfidf.py, 32프레임 IDF 고정)로 매칭. + +단위: + 1. MDX01-intro — 중목차 앞 본문 + 2. MDX01-intro-details — 팝업: 혼용 대표 사례 + 3. MDX01-1 — 중목차: 용어 정의 + 4. MDX01-2 — 중목차: 용어간 상호관계 (본문, 표 제외) + 5. MDX01-2-image — 이미지 캡션: DX와 핵심기술간 상호관계 + 6. MDX01-2-details — 팝업+표: DX와 BIM 구분 12행 + 7. MDX02-1 — 중목차: DX의 궁극적 목표 + 8. MDX02-1-image — 이미지 캡션 + 9. MDX02-2 — 중목차(컨테이너): 타이틀 + 도입부만 + 10. MDX02-2.1 — 소목차: 업무 수행 과정의 변화 + 11. MDX02-2.2 — 소목차: 주체별 기대효과 (본문, 표 제외) + 12. MDX02-2.2-table — 표: 발주자/시공자/설계자 + 13. MDX03-1 — 중목차: 필수 요건 + 14. MDX03-2 — 중목차(컨테이너) + 15. MDX03-2.1 — 소목차: 과정의 혁신 (본문, 표 제외) + 16. MDX03-2.1-table — 표: As-is/To-be + 17. MDX03-2.2 — 소목차: 결과의 변화 +""" +from __future__ import annotations + +import json +import re +import sys +from datetime import datetime +from pathlib import Path + +sys.path.insert(0, str(Path(__file__).parent.parent)) + +from src.block_matcher_tfidf import TfidfBlockMatcher + + +TOP_K = 3 + +PREVIEW_DIR = Path("data/figma_previews") +_INDEX: dict[str, dict] = json.loads((PREVIEW_DIR / "index.json").read_text(encoding="utf-8")) +FRAME_TO_NUM: dict[str, str] = {v["frame_id"]: k for k, v in _INDEX.items()} + + +def num_of(fid: str) -> str: + return FRAME_TO_NUM.get(fid, f"?({fid})") + + +def ftitle(matcher: TfidfBlockMatcher, fid: str) -> str: + for f in matcher.frames: + if f["frame_id"] == fid: + return (f.get("title_text") or "").replace("\n", " ")[:50] + return "" + + +def strip_tags(t: str) -> str: + t = re.sub(r"<[^>]+>", " ", t) + t = re.sub(r"\{/\*.*?\*/\}", " ", t, flags=re.DOTALL) + t = re.sub(r"\{[^{}]*\}", " ", t) + t = re.sub(r"\s+", " ", t).strip() + return t + + +def extract_details(raw: str) -> list[dict]: + out = [] + for m in re.finditer(r"]*>([\s\S]*?)
", raw, re.IGNORECASE): + body = m.group(1) + sm = re.search(r"]*>([\s\S]*?)", body, re.IGNORECASE) + summary = strip_tags(sm.group(1)) if sm else "" + rest = body[sm.end():] if sm else body + out.append({ + "summary": summary, + "body": strip_tags(rest), + "start": m.start(), + "end": m.end(), + }) + return out + + +def split_h2(raw: str) -> list[dict]: + """raw → [{'title', 'body', 'start', 'end'}] for each ## section.""" + iters = list(re.finditer(r"^##\s+(.+?)$", raw, re.MULTILINE)) + out = [] + for i, m in enumerate(iters): + end = iters[i+1].start() if i+1 < len(iters) else len(raw) + out.append({ + "title": m.group(1).strip(), + "start": m.start(), + "end": end, + "body_raw": raw[m.end():end], + }) + return out + + +def split_h3(raw: str) -> list[dict]: + iters = list(re.finditer(r"^###\s+(.+?)$", raw, re.MULTILINE)) + out = [] + for i, m in enumerate(iters): + end = iters[i+1].start() if i+1 < len(iters) else len(raw) + out.append({ + "title": m.group(1).strip(), + "start": m.start(), + "end": end, + "body_raw": raw[m.end():end], + }) + return out + + +def extract_tables(raw: str) -> list[str]: + """Markdown 표 ( | ... | ... | ) 블록을 각각 문자열로 반환.""" + lines = raw.splitlines() + tables = [] + cur = [] + for ln in lines: + if re.match(r"^\s*\|.*\|\s*$", ln): + cur.append(ln.strip()) + else: + if len(cur) >= 2: + tables.append("\n".join(cur)) + cur = [] + if len(cur) >= 2: + tables.append("\n".join(cur)) + return tables + + +def extract_image_captions(raw: str) -> list[str]: + """![alt](path) + 그 근처의 이탤릭 [그림 N] 캡션 모음.""" + out = [] + for m in re.finditer(r"!\[([^\]]*)\]\(([^)]+)\)", raw): + alt = m.group(1).strip() + path = m.group(2).strip() + # 뒤 300자 안에 *[그림 ...]* 캡션 찾기 + after = raw[m.end():m.end()+400] + cap = re.search(r"\*\[그림[^\]]*\][^*]*\*", after) + caption = cap.group(0).strip("*").strip() if cap else "" + out.append(f"{alt} {path} {caption}".strip()) + return out + + +def remove_details_and_tables(raw: str) -> str: + t = re.sub(r"", " ", raw, flags=re.IGNORECASE) + # 표 라인 제거 + t = "\n".join(ln for ln in t.splitlines() if not re.match(r"^\s*\|.*\|\s*$", ln)) + return t + + +def build_17_units() -> list[dict]: + mdx_dir = Path("samples/mdx") + raw01 = (mdx_dir / "01. 건설산업 DX의 올바른 이해(0127).mdx").read_text(encoding="utf-8") + raw02 = (mdx_dir / "02. DX의 시행 목표 및 기대효과.mdx").read_text(encoding="utf-8") + raw03 = (mdx_dir / "03. DX 시행을 위한 필수 요건 및 혁신 방안.mdx").read_text(encoding="utf-8") + + units: list[dict] = [] + + # ─── MDX 01 ─── + h2_01 = split_h2(raw01) + intro_01 = raw01[: h2_01[0]["start"]] if h2_01 else raw01 + details_01 = extract_details(raw01) + images_01 = extract_image_captions(raw01) + + # 1. MDX01-intro: 첫 ## 전 본문(details 제외) + intro_text = remove_details_and_tables(intro_01) + units.append({"id": "MDX01-intro", "kind": "중목차 앞 본문", + "label": "용어 혼용 문제 제기", + "text": strip_tags(intro_text)}) + # 2. MDX01-intro-details + d0 = details_01[0] if details_01 else {"summary": "", "body": ""} + units.append({"id": "MDX01-intro-details", "kind": "팝업", + "label": "혼용 대표 사례", + "text": f"{d0['summary']} {d0['body']}"}) + # 3. MDX01-1 + body_01_1 = h2_01[0]["body_raw"] + units.append({"id": "MDX01-1", "kind": "중목차", + "label": "용어 정의", + "text": f"{h2_01[0]['title']} {strip_tags(remove_details_and_tables(body_01_1))}"}) + # 4. MDX01-2 (본문만, details/표 제거) + body_01_2 = h2_01[1]["body_raw"] + units.append({"id": "MDX01-2", "kind": "중목차", + "label": "용어간 상호관계", + "text": f"{h2_01[1]['title']} {strip_tags(remove_details_and_tables(body_01_2))}"}) + # 5. MDX01-2-image + units.append({"id": "MDX01-2-image", "kind": "이미지", + "label": "DX1.png", + "text": images_01[0] if images_01 else ""}) + # 6. MDX01-2-details + d1 = details_01[1] if len(details_01) >= 2 else {"summary": "", "body": ""} + units.append({"id": "MDX01-2-details", "kind": "팝업+표", + "label": "DX와 BIM의 구분 12행 비교표", + "text": f"{d1['summary']} {d1['body']}"}) + + # ─── MDX 02 ─── + h2_02 = split_h2(raw02) + images_02 = extract_image_captions(raw02) + + # 7. MDX02-1 + body_02_1 = h2_02[0]["body_raw"] + units.append({"id": "MDX02-1", "kind": "중목차", + "label": "DX의 궁극적 목표", + "text": f"{h2_02[0]['title']} {strip_tags(remove_details_and_tables(body_02_1))}"}) + # 8. MDX02-1-image + units.append({"id": "MDX02-1-image", "kind": "이미지", + "label": "궁극적목표.png", + "text": images_02[0] if images_02 else ""}) + # 9. MDX02-2 컨테이너 (title + ### 이전 본문) + body_02_2 = h2_02[1]["body_raw"] + h3_02_2 = split_h3(body_02_2) + pre_h3 = body_02_2[: h3_02_2[0]["start"]] if h3_02_2 else body_02_2 + units.append({"id": "MDX02-2", "kind": "중목차(컨테이너)", + "label": "DX 기반 Process 혁신 기대효과", + "text": f"{h2_02[1]['title']} {strip_tags(pre_h3)}"}) + # 10. MDX02-2.1 (표 제거) + body_021 = h3_02_2[0]["body_raw"] + units.append({"id": "MDX02-2.1", "kind": "소목차", + "label": "업무 수행 과정의 변화", + "text": f"{h3_02_2[0]['title']} {strip_tags(remove_details_and_tables(body_021))}"}) + # 11. MDX02-2.2 (표 제거) + body_022 = h3_02_2[1]["body_raw"] + units.append({"id": "MDX02-2.2", "kind": "소목차", + "label": "주체별 기대효과", + "text": f"{h3_02_2[1]['title']} {strip_tags(remove_details_and_tables(body_022))}"}) + # 12. MDX02-2.2-table + tables_022 = extract_tables(body_022) + units.append({"id": "MDX02-2.2-table", "kind": "표", + "label": "발주자/시공자/설계자 4×3 표", + "text": strip_tags(tables_022[0]) if tables_022 else ""}) + + # ─── MDX 03 ─── + h2_03 = split_h2(raw03) + + # 13. MDX03-1 + body_03_1 = h2_03[0]["body_raw"] + units.append({"id": "MDX03-1", "kind": "중목차", + "label": "필수 요건 (기술/사람/자연)", + "text": f"{h2_03[0]['title']} {strip_tags(remove_details_and_tables(body_03_1))}"}) + # 14. MDX03-2 컨테이너 + body_03_2 = h2_03[1]["body_raw"] + h3_03_2 = split_h3(body_03_2) + pre_h3_2 = body_03_2[: h3_03_2[0]["start"]] if h3_03_2 else body_03_2 + units.append({"id": "MDX03-2", "kind": "중목차(컨테이너)", + "label": "Process/Product 혁신", + "text": f"{h2_03[1]['title']} {strip_tags(pre_h3_2)}"}) + # 15. MDX03-2.1 (표 제거) + body_031 = h3_03_2[0]["body_raw"] + units.append({"id": "MDX03-2.1", "kind": "소목차", + "label": "과정(Process)의 혁신", + "text": f"{h3_03_2[0]['title']} {strip_tags(remove_details_and_tables(body_031))}"}) + # 16. MDX03-2.1-table + tables_031 = extract_tables(body_031) + units.append({"id": "MDX03-2.1-table", "kind": "표", + "label": "As-is/To-be 3행 비교표", + "text": strip_tags(tables_031[0]) if tables_031 else ""}) + # 17. MDX03-2.2 + body_032 = h3_03_2[1]["body_raw"] + units.append({"id": "MDX03-2.2", "kind": "소목차", + "label": "결과(Product)의 변화", + "text": f"{h3_03_2[1]['title']} {strip_tags(remove_details_and_tables(body_032))}"}) + + return units + + +def main() -> int: + print("[init] TF-IDF 인덱스 로딩 (src/block_matcher_tfidf.py, 32프레임 IDF 고정)...") + matcher = TfidfBlockMatcher() + print(f"[init] 프레임 {len(matcher.frames)}개 인덱싱 완료") + + units = build_17_units() + + md_lines: list[str] = [ + "# 17개 콘텐츠 단위별 매칭 (내 매처: 32프레임 IDF 고정)", + "", + "엔진: `src/block_matcher_tfidf.py` (프레임 32개만으로 IDF 사전 계산, 확장어 주입).", + "각 단위의 텍스트를 쿼리로 주입하고 top-3 프레임을 출력.", + "", + "| # | 단위 ID | 종류 | 라벨 | 텍스트 길이 | 1위 | 2위 | 3위 |", + "|---|---|---|---|---|---|---|---|", + ] + + rel = Path("..") / ".." / ".." / PREVIEW_DIR + details_sections: list[str] = [] + + for i, u in enumerate(units, start=1): + q = u["text"] + print(f"\n[{i:02d}] {u['id']} ({u['kind']}) — {u['label']} 텍스트 {len(q)}자") + if not q.strip(): + print(" (텍스트 비어 있음)") + row = f"| {i} | {u['id']} | {u['kind']} | {u['label']} | 0 | — | — | — |" + md_lines.append(row) + continue + top = matcher.match(q, sub_titles=None, d1_items=None, top_k=len(matcher.frames)) + top3 = [r for r in top[:TOP_K] if r["score"] > 0] + for rank, r in enumerate(top3, start=1): + num = num_of(r["frame_id"]) + print(f" {rank}. #{num} {r['score']*100:5.1f}% | frame {r['frame_id']} | {ftitle(matcher, r['frame_id'])}") + + cells = [] + for slot in range(3): + if slot < len(top3): + r = top3[slot] + n = num_of(r["frame_id"]) + cells.append(f"**#{n}** {r['score']*100:.1f}%") + else: + cells.append("—") + row = (f"| {i} | {u['id']} | {u['kind']} | {u['label']} | {len(q)} | " + + " | ".join(cells) + " |") + md_lines.append(row) + + # 상세 섹션 + details_sections.append(f"\n### {i}. {u['id']} — {u['label']}") + details_sections.append(f"- 종류: {u['kind']} · 텍스트 길이: {len(q)}자") + preview = q[:120] + ("…" if len(q) > 120 else "") + details_sections.append(f"- 쿼리 미리보기: _{preview}_\n") + details_sections.append("| rank | # | preview | score | frame_id | title_text |") + details_sections.append("|---|---|---|---|---|---|") + for rank, r in enumerate(top3, start=1): + n = num_of(r["frame_id"]) + prev = f"![]({(rel / (n+'.png')).as_posix()})" + details_sections.append( + f"| {rank} | **#{n}** | {prev} | **{r['score']*100:.1f}%** | " + f"`{r['frame_id']}` | {ftitle(matcher, r['frame_id'])} |" + ) + if not top3: + details_sections.append("| — | — | — | 0% | — | (매칭 없음) |") + + md_lines.append("\n## 상세\n") + md_lines.extend(details_sections) + + ts = datetime.now().strftime("%Y%m%d_%H%M%S") + out_dir = Path("data/runs") / f"{ts}_17units_my_matcher" + out_dir.mkdir(parents=True, exist_ok=True) + out = out_dir / "match_report.md" + out.write_text("\n".join(md_lines), encoding="utf-8") + print(f"\n[saved] {out}") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/scripts/match_mdx_strict.py b/scripts/match_mdx_strict.py new file mode 100644 index 0000000..b2fee2b --- /dev/null +++ b/scripts/match_mdx_strict.py @@ -0,0 +1,391 @@ +"""MDX ↔ Figma Frame 매칭 (엄밀한 헤딩 구조 + 팝업 포함 버전). + +mdx_normalizer 대신 raw MDX를 직접 파싱하여: + - 중목차 = ## 헤딩 (오직 ## 만) + - 소목차 = ### 헤딩 (오직 ### 만) + - 팝업 =
......
(summary + body 분리 보존) + +출력 레벨: + L1 대목차 : MDX 전체 raw text (팝업 body 포함) + L2 중목차 : 각 ## 섹션 본문 + 그 섹션 안 팝업 body 포함 + L3 소목차 : 각 ### 섹션 본문 (해당 섹션에 속한 팝업 body 포함) + L4 팝업 : 각
의 summary + body 단독 쿼리 + +매칭 엔진은 src/block_matcher_tfidf.py 그대로 재사용. +""" +from __future__ import annotations + +import json +import re +import sys +from datetime import datetime +from pathlib import Path + +sys.path.insert(0, str(Path(__file__).parent.parent)) + +from src.block_matcher_tfidf import TfidfBlockMatcher + + +TOP_K = 3 + +PREVIEW_DIR = Path("data/figma_previews") +INDEX_PATH = PREVIEW_DIR / "index.json" +_INDEX: dict[str, dict] = json.loads(INDEX_PATH.read_text(encoding="utf-8")) +FRAME_TO_NUM: dict[str, str] = {v["frame_id"]: k for k, v in _INDEX.items()} + +MDX_FILES = [ + ("01", Path("samples/mdx/01. 건설산업 DX의 올바른 이해(0127).mdx")), + ("02", Path("samples/mdx/02. DX의 시행 목표 및 기대효과.mdx")), + ("03", Path("samples/mdx/03. DX 시행을 위한 필수 요건 및 혁신 방안.mdx")), +] + + +# ═══════════════════════════════════════════════════════════ +# MDX 파서 +# ═══════════════════════════════════════════════════════════ + +def strip_tags(text: str) -> str: + """HTML/JSX 태그 제거 + 마크다운 포맷 기호 살짝 정리.""" + text = re.sub(r"<[^>]+>", " ", text) + text = re.sub(r"\{/\*.*?\*/\}", " ", text, flags=re.DOTALL) + text = re.sub(r"\{[^{}]*\}", " ", text) # JSX prop {…} + text = text.replace("\\", " ") + text = re.sub(r"\s+", " ", text).strip() + return text + + +def parse_details(text: str) -> list[dict]: + """MDX 내 모든
블록을 추출. summary + body 반환. + + 각 엔트리: {"summary": str, "body": str, "raw_start": int, "raw_end": int} + """ + popups: list[dict] = [] + for m in re.finditer(r"]*>([\s\S]*?)
", text, re.IGNORECASE): + block = m.group(1) + sm = re.search(r"]*>([\s\S]*?)", block, re.IGNORECASE) + summary = strip_tags(sm.group(1)) if sm else "" + body = block[sm.end():] if sm else block + popups.append({ + "summary": summary, + "body": strip_tags(body), + "raw_start": m.start(), + "raw_end": m.end(), + }) + return popups + + +def parse_mdx_structure(raw: str) -> dict: + """Raw MDX → 헤딩 트리 + 팝업 목록. + + 반환: + { + "doc_title": str, # frontmatter title 또는 None + "intro": str, # 첫 ## 이전 텍스트 + "h2": [ + { + "title": str, + "body": str, # ## 섹션 본문 (### 이전까지) + "h3": [ {"title": str, "body": str, "popups": [...]}, ... ], + "popups": [ {"summary", "body"} ], # 이 ## 섹션에 직접 속한 팝업 + }, + ... + ], + "all_popups": [ ... ], # 전체 팝업 + } + """ + # frontmatter에서 title + fm = re.match(r"---\s*\n([\s\S]*?)\n---\s*\n", raw) + doc_title = None + if fm: + tm = re.search(r"^title:\s*(.+)$", fm.group(1), re.MULTILINE) + if tm: + doc_title = tm.group(1).strip().strip('"').strip("'") + raw_body = raw[fm.end():] + else: + raw_body = raw + + # ## 헤딩 찾기 + h2_iter = list(re.finditer(r"^##\s+(.+?)$", raw_body, re.MULTILINE)) + + intro = raw_body[: h2_iter[0].start()].strip() if h2_iter else raw_body.strip() + + h2_nodes: list[dict] = [] + for i, m in enumerate(h2_iter): + title = m.group(1).strip() + start = m.end() + end = h2_iter[i + 1].start() if i + 1 < len(h2_iter) else len(raw_body) + section_raw = raw_body[start:end] + + # ### 헤딩 + h3_iter = list(re.finditer(r"^###\s+(.+?)$", section_raw, re.MULTILINE)) + + body_before_h3 = section_raw[: h3_iter[0].start()] if h3_iter else section_raw + + h3_nodes: list[dict] = [] + for j, m3 in enumerate(h3_iter): + t3 = m3.group(1).strip() + s3 = m3.end() + e3 = h3_iter[j + 1].start() if j + 1 < len(h3_iter) else len(section_raw) + sub_raw = section_raw[s3:e3] + sub_popups = parse_details(sub_raw) + # body = sub_raw + 팝업 본문 합친 문자열(쿼리용) — 팝업은 inline이므로 raw에 이미 포함되지만 태그 제거 후 텍스트로 강조 + h3_nodes.append({ + "title": t3, + "body": strip_tags(sub_raw), + "popups": sub_popups, + }) + + # 이 ## 섹션 직속 팝업 (### 이전 부분에 있는 것) + section_popups = parse_details(body_before_h3) + + h2_nodes.append({ + "title": title, + "body": strip_tags(body_before_h3), + "h3": h3_nodes, + "popups": section_popups, + }) + + all_popups: list[dict] = [] + all_popups.extend(parse_details(intro)) + for h2 in h2_nodes: + all_popups.extend(h2["popups"]) + for h3 in h2["h3"]: + all_popups.extend(h3["popups"]) + + return { + "doc_title": doc_title, + "intro": strip_tags(intro), + "intro_popups": parse_details(intro), + "h2": h2_nodes, + "all_popups": all_popups, + } + + +# ═══════════════════════════════════════════════════════════ +# 매칭 + 출력 +# ═══════════════════════════════════════════════════════════ + +def num_of(frame_id: str) -> str: + return FRAME_TO_NUM.get(frame_id, f"?({frame_id})") + + +def frame_title(matcher: TfidfBlockMatcher, fid: str) -> str: + for f in matcher.frames: + if f["frame_id"] == fid: + return (f.get("title_text") or "").replace("\n", " ")[:60] + return "" + + +def run_query(matcher: TfidfBlockMatcher, query_text: str) -> list[dict]: + """쿼리 텍스트 하나를 받아서 matcher로 돌린다. + + block_matcher_tfidf.match는 (zone_title, sub_titles, d1_items) 인자를 받지만 + 내부에서는 단순히 문자열로 합쳐 전처리 → TF-IDF 유사도. 여기서는 전체 쿼리를 + 첫 인자(zone_title)로 넣어 동일한 전처리 경로를 탄다. + """ + return matcher.match(query_text, sub_titles=None, d1_items=None, top_k=len(matcher.frames)) + + +def print_ranking(top: list[dict], matcher: TfidfBlockMatcher, indent: str = " "): + if not top or top[0]["score"] <= 0: + print(f"{indent}(매칭 없음, score=0)") + return + for rank, r in enumerate(top[:TOP_K], start=1): + if r["score"] <= 0: + break + num = num_of(r["frame_id"]) + print( + f"{indent} {rank}. #{num} score={r['score']*100:5.1f}% " + f"| frame {r['frame_id']} | {frame_title(matcher, r['frame_id'])}" + ) + + +def md_ranking_table(top: list[dict], matcher: TfidfBlockMatcher, run_dir_depth: int = 3) -> list[str]: + rel = Path(*[".." for _ in range(run_dir_depth)]) / PREVIEW_DIR + lines = [ + "| rank | # | preview | score | frame_id | title_text |", + "|---|---|---|---|---|---|", + ] + for rank, r in enumerate(top[:TOP_K], start=1): + if r["score"] <= 0: + break + num = num_of(r["frame_id"]) + preview = f"![]({(rel / (num + '.png')).as_posix()})" + lines.append( + f"| {rank} | **#{num}** | {preview} | **{r['score']*100:.1f}%** | " + f"`{r['frame_id']}` | {frame_title(matcher, r['frame_id'])} |" + ) + if len(lines) == 2: + lines.append("| — | — | — | 0% | — | (매칭 없음) |") + return lines + + +def evaluate_mdx(matcher: TfidfBlockMatcher, mdx_id: str, mdx_path: Path, md_lines: list[str]): + raw = mdx_path.read_text(encoding="utf-8") + parsed = parse_mdx_structure(raw) + doc_title = parsed["doc_title"] or mdx_path.stem + + print("\n" + "=" * 100) + print(f"MDX {mdx_id}: {doc_title} ({mdx_path.name})") + print(f" 중목차(##) {len(parsed['h2'])}개 · " + f"소목차(###) {sum(len(h2['h3']) for h2 in parsed['h2'])}개 · " + f"팝업(
) {len(parsed['all_popups'])}개") + print("=" * 100) + + md_lines.append(f"\n## MDX {mdx_id} — {doc_title}\n") + md_lines.append(f"파일: `{mdx_path.as_posix()}`") + md_lines.append( + f"- 중목차(##) **{len(parsed['h2'])}개** · " + f"소목차(###) **{sum(len(h2['h3']) for h2 in parsed['h2'])}개** · " + f"팝업(
) **{len(parsed['all_popups'])}개**\n" + ) + + # ─── L1 대목차: 전체 MDX ─── + full_text = strip_tags(raw) + l1_top = run_query(matcher, full_text) + print(f"\n┌─ L1 대목차 [전체 MDX, 팝업 포함]") + print_ranking(l1_top, matcher, indent="│ ") + md_lines.append("### 🟦 L1 — 대목차 (전체 MDX, 팝업 포함)\n") + md_lines.extend(md_ranking_table(l1_top, matcher)) + md_lines.append("") + + # ─── L2 중목차: 각 ## ─── + print(f"\n┌─ L2 중목차 [## 섹션별]") + md_lines.append("\n### 🟩 L2 — 중목차 (각 ## 섹션, 팝업 body 포함)\n") + + for zi, h2 in enumerate(parsed["h2"], start=1): + # 쿼리: ## title + body(### 이전) + 직속 popup + 각 ### body/popup + parts = [h2["title"], h2["body"]] + for p in h2["popups"]: + parts.append(p["summary"]) + parts.append(p["body"]) + for h3 in h2["h3"]: + parts.append(h3["title"]) + parts.append(h3["body"]) + for p in h3["popups"]: + parts.append(p["summary"]) + parts.append(p["body"]) + query = " ".join(parts) + top = run_query(matcher, query) + + pop_titles = [p["summary"] for p in h2["popups"]] + [ + p["summary"] for h3 in h2["h3"] for p in h3["popups"] + ] + print(f"│\n│ [중 {zi}] ## {h2['title']}") + print(f"│ 소목차: {[h3['title'] for h3 in h2['h3']] or '(없음)'}") + print(f"│ 팝업: {pop_titles or '(없음)'}") + print_ranking(top, matcher, indent="│ ") + + md_lines.append(f"\n#### 중 {zi}: `## {h2['title']}`\n") + md_lines.append(f"- 소목차(###): {[h3['title'] for h3 in h2['h3']] or '(없음)'}") + md_lines.append(f"- 팝업: {pop_titles or '(없음)'}\n") + md_lines.extend(md_ranking_table(top, matcher)) + md_lines.append("") + + # ─── L3 소목차: 각 ### ─── + total_h3 = sum(len(h2["h3"]) for h2 in parsed["h2"]) + print(f"\n┌─ L3 소목차 [### 섹션별, 총 {total_h3}개]") + if total_h3 == 0: + print("│ (이 MDX에는 ### 소목차 없음)") + md_lines.append(f"\n### 🟨 L3 — 소목차 (각 ### 섹션)\n") + if total_h3 == 0: + md_lines.append("_(이 MDX에는 ### 소목차 없음)_\n") + + for h2 in parsed["h2"]: + for h3 in h2["h3"]: + parts = [h3["title"], h3["body"]] + for p in h3["popups"]: + parts.append(p["summary"]) + parts.append(p["body"]) + query = " ".join(parts) + top = run_query(matcher, query) + + pop_titles = [p["summary"] for p in h3["popups"]] + print(f"│\n│ [소] ### {h3['title']} (상위 중목차: {h2['title']})") + print(f"│ 팝업: {pop_titles or '(없음)'}") + print_ranking(top, matcher, indent="│ ") + + md_lines.append( + f"\n#### 소: `### {h3['title']}` _(상위 중목차: `{h2['title']}`)_\n" + ) + md_lines.append(f"- 팝업: {pop_titles or '(없음)'}\n") + md_lines.extend(md_ranking_table(top, matcher)) + md_lines.append("") + + # ─── L4 팝업: 각
─── + print(f"\n┌─ L4 팝업 [
단독 매칭, 총 {len(parsed['all_popups'])}개]") + if not parsed["all_popups"]: + print("│ (팝업 없음)") + md_lines.append(f"\n### 🟥 L4 — 팝업 (
단독 매칭)\n") + if not parsed["all_popups"]: + md_lines.append("_(팝업 없음)_\n") + + for pi, pop in enumerate(parsed["all_popups"], start=1): + query = f"{pop['summary']} {pop['body']}" + top = run_query(matcher, query) + preview = pop["body"][:80] + ("…" if len(pop["body"]) > 80 else "") + print(f"│\n│ [팝 {pi}] summary={pop['summary']!r} ({len(pop['body'])}자)") + print(f"│ preview: {preview}") + print_ranking(top, matcher, indent="│ ") + + md_lines.append(f"\n#### 팝 {pi}: `
` — **{pop['summary']}**\n") + md_lines.append(f"- 본문 길이: {len(pop['body'])}자 · preview: _{preview}_\n") + md_lines.extend(md_ranking_table(top, matcher)) + md_lines.append("") + + +def build_frame_legend(matcher: TfidfBlockMatcher, md_lines: list[str]) -> None: + md_lines.append("\n## 프레임 번호 전체 색인 (01 ~ 32)\n") + md_lines.append("| # | preview | frame_id | title_text |") + md_lines.append("|---|---|---|---|") + rel = Path("..") / ".." / ".." / PREVIEW_DIR + for num in sorted(_INDEX.keys()): + entry = _INDEX[num] + preview = f"![]({(rel / (num + '.png')).as_posix()})" + md_lines.append( + f"| **#{num}** | {preview} | `{entry['frame_id']}` | " + f"{frame_title(matcher, entry['frame_id'])} |" + ) + md_lines.append("") + + +def main() -> int: + print("[init] TF-IDF 인덱스 로딩...") + matcher = TfidfBlockMatcher() + print(f"[init] 프레임 {len(matcher.frames)}개 인덱싱 완료") + + md_lines: list[str] = [ + "# MDX ↔ Figma Frame 매칭 (엄밀한 구조 + 팝업 포함)", + "", + "raw MDX를 직접 파싱하여 **## 만 중목차**, **### 만 소목차**, `
` 을 팝업으로 분리.", + "bullet 항목(`* **제목**`)은 헤딩이 아니므로 섹션 body에 포함되며 별도 레벨로 취급하지 않음.", + "", + "| 단계 | 입도 | 쿼리 구성 |", + "|---|---|---|", + "| 🟦 L1 대목차 | MDX 1개 | 전체 MDX raw text (팝업 body 포함) |", + "| 🟩 L2 중목차 | 각 `##` | `## title + body + 하위 ### body + 팝업 body` |", + "| 🟨 L3 소목차 | 각 `###` | `### title + body + 자기 팝업 body` |", + "| 🟥 L4 팝업 | 각 `
` | `summary + body` |", + "", + "점수는 순수 TF-IDF cosine similarity × 100 (%). 판정 라벨 없음.", + ] + + for mdx_id, p in MDX_FILES: + if p.exists(): + evaluate_mdx(matcher, mdx_id, p, md_lines) + else: + print(f"[skip] 없음: {p}") + + build_frame_legend(matcher, md_lines) + + ts = datetime.now().strftime("%Y%m%d_%H%M%S") + out_dir = Path("data/runs") / f"{ts}_mdx_match_strict" + out_dir.mkdir(parents=True, exist_ok=True) + out = out_dir / "match_report.md" + out.write_text("\n".join(md_lines), encoding="utf-8") + print(f"\n[saved] {out}") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/scripts/match_mdx_to_frames_tfidf.py b/scripts/match_mdx_to_frames_tfidf.py new file mode 100644 index 0000000..6591c62 --- /dev/null +++ b/scripts/match_mdx_to_frames_tfidf.py @@ -0,0 +1,238 @@ +"""MDX → Figma Frame 매칭 (TF-IDF) — 대목차 / 중목차 / 소목차 3단계 모두 출력. + +프레임은 data/figma_previews/index.json 의 번호(01~32)로 표기한다. +""" +from __future__ import annotations + +import json +import re +import sys +from datetime import datetime +from pathlib import Path + +sys.path.insert(0, str(Path(__file__).parent.parent)) + +from src.block_matcher_tfidf import TfidfBlockMatcher +from src.mdx_normalizer import normalize_mdx_content +from src.section_parser import extract_major_sections + + +TOP_K = 3 +THRESHOLD = 0.15 # pipeline_v2 direct-fit 커트오프 (표기에 사용 안 함) + +PREVIEW_DIR = Path("data/figma_previews") +INDEX_PATH = PREVIEW_DIR / "index.json" + +# index.json 로드: {"01": {"frame_id": "1171281172", ...}, ...} +_INDEX: dict[str, dict] = json.loads(INDEX_PATH.read_text(encoding="utf-8")) +FRAME_TO_NUM: dict[str, str] = {v["frame_id"]: k for k, v in _INDEX.items()} +NUM_TO_FRAME: dict[str, str] = {k: v["frame_id"] for k, v in _INDEX.items()} + +MDX_FILES = [ + ("01", Path("samples/mdx/01. 건설산업 DX의 올바른 이해(0127).mdx")), + ("02", Path("samples/mdx/02. DX의 시행 목표 및 기대효과.mdx")), + ("03", Path("samples/mdx/03. DX 시행을 위한 필수 요건 및 혁신 방안.mdx")), +] + + +def num_of(frame_id: str) -> str: + return FRAME_TO_NUM.get(frame_id, f"?({frame_id})") + + +def extract_d1_items(content: str) -> list[str]: + return [ + re.sub(r"\*+", "", d).strip() + for d in re.findall(r"^D1:\s*(.*)", content, re.MULTILINE) + ] + + +def frame_title(matcher: TfidfBlockMatcher, fid: str) -> str: + for f in matcher.frames: + if f["frame_id"] == fid: + return (f.get("title_text") or "").replace("\n", " ")[:60] + return "" + + +def print_ranking(label: str, top: list[dict], matcher: TfidfBlockMatcher, indent: str = " "): + if not top or top[0]["score"] <= 0: + print(f"{indent}(매칭 없음, score=0)") + return + for rank, r in enumerate(top[:TOP_K], start=1): + if r["score"] <= 0: + break + num = num_of(r["frame_id"]) + print( + f"{indent} {rank}. #{num} score={r['score']*100:5.1f}% " + f"| frame {r['frame_id']} | {frame_title(matcher, r['frame_id'])}" + ) + + +def md_ranking_table(top: list[dict], matcher: TfidfBlockMatcher) -> list[str]: + rel = Path("..") / ".." / ".." / PREVIEW_DIR # run dir 기준 + lines = [ + "| rank | # | preview | score | frame_id | title_text |", + "|---|---|---|---|---|---|", + ] + for rank, r in enumerate(top[:TOP_K], start=1): + if r["score"] <= 0: + break + num = num_of(r["frame_id"]) + preview = f"![]({(rel / (num + '.png')).as_posix()})" + lines.append( + f"| {rank} | **#{num}** | {preview} | **{r['score']*100:.1f}%** | " + f"`{r['frame_id']}` | {frame_title(matcher, r['frame_id'])} |" + ) + if len(lines) == 2: + lines.append("| — | — | — | 0% | — | (매칭 없음) |") + return lines + + +def evaluate_mdx( + matcher: TfidfBlockMatcher, + mdx_id: str, + mdx_path: Path, + md_lines: list[str], +) -> None: + content = mdx_path.read_text(encoding="utf-8") + norm = normalize_mdx_content(content) + flat_sections = norm.get("sections", []) + zones = extract_major_sections(flat_sections) + doc_title = norm.get("title") or mdx_path.stem + + print("\n" + "=" * 100) + print(f"MDX {mdx_id}: {doc_title} ({mdx_path.name})") + print(f"flat sections: {len(flat_sections)} | zones(중목차): {len(zones)}") + print("=" * 100) + + md_lines.append(f"\n## MDX {mdx_id} — {doc_title}\n") + md_lines.append( + f"파일: `{mdx_path.as_posix()}` · " + f"평면 section {len(flat_sections)}개 · zone(중목차) {len(zones)}개\n" + ) + + # ═══════════ L1: 대목차 (MDX 전체) ═══════════ + l1_subs = [z["title"] for z in zones] + [ + st for z in zones for st in z.get("sub_titles", []) + ] + l1_top = matcher.match(doc_title, l1_subs, d1_items=None, top_k=len(matcher.frames)) + + print(f"\n┌─ L1 대목차 [전체 MDX] '{doc_title}'") + print(f"│ zones: {[z['title'] for z in zones]}") + print_ranking("L1", l1_top, matcher, indent="│ ") + + md_lines.append("### 🟦 L1 — 대목차 (전체 MDX)\n") + md_lines.append(f"- 쿼리: `{doc_title}` + 모든 zone/sub title") + md_lines.append(f"- zone 목록: {[z['title'] for z in zones]}") + md_lines.append("") + md_lines.extend(md_ranking_table(l1_top, matcher)) + md_lines.append("") + + # ═══════════ L2: 중목차 (zone 단위) ═══════════ + print(f"\n┌─ L2 중목차 [zone 단위]") + md_lines.append("### 🟩 L2 — 중목차 (zone 단위)\n") + + for zi, zone in enumerate(zones, start=1): + z_title = zone["title"] + sub_titles = zone.get("sub_titles", []) + z_content = zone.get("content", "") + d1 = extract_d1_items(z_content) + top = matcher.match(z_title, sub_titles, d1, top_k=len(matcher.frames)) + + print(f"│\n│ [zone {zi}] {z_title}") + print(f"│ sub_titles: {sub_titles}") + print(f"│ d1_items: {len(d1)}개") + print_ranking("L2", top, matcher, indent="│ ") + + md_lines.append(f"\n#### zone {zi}: **{z_title}**") + md_lines.append(f"- sub_titles: {sub_titles}") + md_lines.append(f"- d1_items: {len(d1)}개") + md_lines.append("") + md_lines.extend(md_ranking_table(top, matcher)) + md_lines.append("") + + # ═══════════ L3: 소목차 (평면 section 각각) ═══════════ + # normalize의 sections 중 content가 있는 것만 = 실제 소목차 + sub_sections = [s for s in flat_sections if s.get("content", "").strip()] + print(f"\n┌─ L3 소목차 [개별 sub-section, {len(sub_sections)}개]") + md_lines.append("### 🟨 L3 — 소목차 (개별 sub-section)\n") + + for si, sec in enumerate(sub_sections, start=1): + s_title = sec.get("title", "") + s_content = sec.get("content", "") + d1 = extract_d1_items(s_content) + top = matcher.match(s_title, sub_titles=None, d1_items=d1, top_k=len(matcher.frames)) + + # 이 섹션이 어느 zone에 속하는지 찾기 + parent_zone = "—" + for z in zones: + if s_title in z.get("sub_titles", []): + parent_zone = z["title"] + break + + print(f"│\n│ [sub {si}] {s_title} (zone: {parent_zone})") + print(f"│ d1_items: {len(d1)}개") + print_ranking("L3", top, matcher, indent="│ ") + + md_lines.append(f"\n#### sub {si}: **{s_title}** _(zone: {parent_zone})_") + md_lines.append(f"- d1_items: {len(d1)}개") + md_lines.append("") + md_lines.extend(md_ranking_table(top, matcher)) + md_lines.append("") + + +def build_frame_legend(matcher: TfidfBlockMatcher, md_lines: list[str]) -> None: + md_lines.append("\n## 프레임 번호 전체 색인 (01 ~ 32)\n") + md_lines.append("| # | preview | frame_id | title_text |") + md_lines.append("|---|---|---|---|") + rel = Path("..") / ".." / ".." / PREVIEW_DIR + for num in sorted(_INDEX.keys()): + entry = _INDEX[num] + preview = f"![]({(rel / (num + '.png')).as_posix()})" + md_lines.append( + f"| **#{num}** | {preview} | `{entry['frame_id']}` | " + f"{frame_title(matcher, entry['frame_id'])} |" + ) + md_lines.append("") + + +def main() -> int: + print("[init] TF-IDF 인덱스 로딩...") + matcher = TfidfBlockMatcher() + print(f"[init] 프레임 {len(matcher.frames)}개 인덱싱 완료") + print(f"[init] direct-fit 임계값 = {THRESHOLD*100:.0f}%") + + md_lines: list[str] = [ + "# MDX ↔ Figma Frame 매칭 (TF-IDF 순수 점수) — L1/L2/L3 3단계", + "", + "프레임은 `data/figma_previews/{번호}.png` 의 번호로 표기. 하단에 번호-프레임 색인.", + "", + "| 단계 | 입도 | 쿼리 구성 |", + "|---|---|---|", + "| 🟦 L1 대목차 | MDX 전체 1개 | doc title + 모든 zone/sub title |", + "| 🟩 L2 중목차 | zone 단위 | zone title + sub_titles + d1_items |", + "| 🟨 L3 소목차 | 개별 sub-section 각각 | sub title + 자기 content의 d1_items |", + "", + f"- 인덱싱된 프레임: {len(matcher.frames)}개", + "- **각 표는 순수 TF-IDF cosine similarity × 100 을 %로 표시한 점수 랭킹.**", + "- 판정/분기(recipe/direct-fit) 라벨은 출력하지 않음. 점수만 그대로 본다.", + ] + + for mdx_id, p in MDX_FILES: + if p.exists(): + evaluate_mdx(matcher, mdx_id, p, md_lines) + else: + print(f"[skip] 없음: {p}") + + build_frame_legend(matcher, md_lines) + + timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") + out_dir = Path("data/runs") / f"{timestamp}_mdx_match" + out_dir.mkdir(parents=True, exist_ok=True) + out = out_dir / "match_report.md" + out.write_text("\n".join(md_lines), encoding="utf-8") + print(f"\n[saved] {out}") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/scripts/ocr_augment_texts.py b/scripts/ocr_augment_texts.py new file mode 100644 index 0000000..6491c8e --- /dev/null +++ b/scripts/ocr_augment_texts.py @@ -0,0 +1,398 @@ +"""32개 프레임 preview PNG에 EasyOCR + 이미지 전처리를 돌려, +기존 texts.md에 없는 '이미지 베이크 텍스트' 델타를 추출/보강. + +흐름: + 1. 원본 PNG 로드 + 2. 두 가지 변형을 OCR: + (a) 원본 그대로 + (b) 2배 업스케일 + 대비 강화 (녹색/저대비 장식 텍스트 잡기용) + 3. 두 결과 합치고 confidence 컷 (low=0.15, high=0.5) + 4. 오인식 교정 사전 적용 (SIW→S/W, 움합의→융합의 등) + 5. 기존 texts.md 토큰과 비교하여 델타 추출 + 6. 프레임별 통계(감지 수, 델타 수, 누락 여부) 리포트 + 7. --apply 시 texts.md 파일들에 델타 추가 + +사용: + python scripts/ocr_augment_texts.py # 드라이런 (리포트만) + python scripts/ocr_augment_texts.py --apply # texts.md 수정 + python scripts/ocr_augment_texts.py --only 1171281172 +""" +from __future__ import annotations + +import argparse +import json +import re +import sys +from datetime import datetime +from pathlib import Path + +sys.path.insert(0, str(Path(__file__).parent.parent)) + +PREVIEW_DIR = Path("data/figma_previews") +INDEX_PATH = PREVIEW_DIR / "index.json" +BLOCKS_DIR = Path("figma_to_html_agent/blocks") + +APPEND_SECTION_HEADER = "## OCR 보강 (이미지 베이크 텍스트, 자동 추출)" +APPEND_SECTION_MARKER = "" + +# conf 기준 +CONF_HIGH = 0.5 # 이 이상은 그대로 채택 +CONF_LOW = 0.15 # 이 이하는 버림. 사이 구간은 교정 사전 거쳐야 채택 + +# 자주 틀리는 오인식 → 올바른 표현 (정확히 일치 시만 치환) +OCR_CORRECTIONS: dict[str, str] = { + "siw": "S/W", + "sw": "S/W", + "hiw": "H/W", + "hw": "H/W", + "움합의": "융합의", + "(직관지 역할": "직관지 역할", + "패텔입": "패러다임", + "|말": "개발", + "대발": "개발", + "Civil": "Civil", + "I/W": "S/W", + "l/w": "S/W", +} + +# 버리고 싶은 노이즈 패턴 (OCR이 기호/잔여물 잡은 것) +NOISE_PATTERNS = [ + re.compile(r"^[\W_]+$"), # 기호만 + re.compile(r"^\d{1,2}$"), # 숫자 1-2자리 + re.compile(r"^.$"), # 한 글자 +] + + +def is_noise(text: str) -> bool: + for p in NOISE_PATTERNS: + if p.match(text): + return True + return False + + +def apply_corrections(text: str) -> str: + """교정 사전 적용. 대소문자 무시 완전 일치만.""" + key = text.strip().lower() + if key in OCR_CORRECTIONS: + return OCR_CORRECTIONS[key] + # 부분 치환 (문구 안에 숨은 경우) + result = text + for bad, good in OCR_CORRECTIONS.items(): + pattern = re.compile(re.escape(bad), re.IGNORECASE) + result = pattern.sub(good, result) + return result + + +def normalize_for_compare(text: str) -> str: + t = text.lower() + t = re.sub(r"[^\w가-힣]+", "", t) + return t + + +def load_existing_tokens(texts_md: Path) -> set[str]: + if not texts_md.exists(): + return set() + text = texts_md.read_text(encoding="utf-8") + # 기존 OCR 섹션 제외 + if APPEND_SECTION_MARKER in text: + idx = text.find(APPEND_SECTION_MARKER) + header_idx = text.rfind(APPEND_SECTION_HEADER, 0, idx) + if header_idx >= 0: + text = text[:header_idx] + lines = [] + for ln in text.splitlines(): + s = ln.strip() + if s.startswith("#") or s.startswith(">"): + continue + lines.append(ln) + body = " ".join(lines) + tokens: set[str] = set() + for tok in re.split(r"[\s\|\-·•/,.()\[\]:;!?#`'\"*~_+=<>&]+", body): + if not tok: + continue + norm = normalize_for_compare(tok) + if norm and len(norm) >= 2: + tokens.add(norm) + return tokens + + +def preprocess_upscale(png_path: Path, scale: float = 2.0, contrast: float = 1.4): + """이미지를 업스케일 + 대비 강화해서 bytes 반환.""" + from PIL import Image, ImageEnhance + img = Image.open(png_path).convert("RGB") + w, h = img.size + img = img.resize((int(w * scale), int(h * scale)), Image.LANCZOS) + img = ImageEnhance.Contrast(img).enhance(contrast) + import io + buf = io.BytesIO() + img.save(buf, format="PNG") + return buf.getvalue() + + +def run_ocr_variants(reader, png_path: Path) -> list[tuple[str, float, tuple]]: + """원본 + 업스케일 두 번 OCR. (text, conf, bbox_center) 리스트.""" + import numpy as np + from PIL import Image + + collected: list[tuple[str, float, tuple]] = [] + + # 1) 원본 + res1 = reader.readtext(str(png_path), detail=1, paragraph=False) + for bbox, text, conf in res1: + xs = [p[0] for p in bbox] + ys = [p[1] for p in bbox] + center = ((min(xs) + max(xs)) / 2, (min(ys) + max(ys)) / 2) + collected.append((text, float(conf), center)) + + # 2) 업스케일 + 대비 강화 + enhanced_bytes = preprocess_upscale(png_path) + img = np.array(Image.open(__import__("io").BytesIO(enhanced_bytes)).convert("RGB")) + res2 = reader.readtext(img, detail=1, paragraph=False) + for bbox, text, conf in res2: + xs = [p[0] for p in bbox] + ys = [p[1] for p in bbox] + # 원본 좌표계로 환산 (÷2) + center = ((min(xs) + max(xs)) / 4, (min(ys) + max(ys)) / 4) + collected.append((text, float(conf), center)) + + return collected + + +def dedupe_by_position(items: list[tuple[str, float, tuple]]) -> list[tuple[str, float, tuple]]: + """같은 위치(±30px)에서 중복 감지된 것들을 confidence 높은 쪽으로 축약.""" + result: list[tuple[str, float, tuple]] = [] + for text, conf, center in sorted(items, key=lambda r: -r[1]): + dupe = False + for rt, rc, rcenter in result: + if abs(rcenter[0] - center[0]) < 30 and abs(rcenter[1] - center[1]) < 30: + # 텍스트 정규화 같으면 중복 + if normalize_for_compare(rt) == normalize_for_compare(text): + dupe = True + break + # 같은 위치에서 더 긴 버전이 이미 있으면 중복으로 간주 + if normalize_for_compare(text) in normalize_for_compare(rt): + dupe = True + break + if not dupe: + result.append((text, conf, center)) + return result + + +def extract_accepted(items: list[tuple[str, float, tuple]]) -> list[tuple[str, float]]: + """confidence + 교정 적용 후 최종 채택된 (text, conf) 리스트. + + 규칙: + - 교정 사전에 명시된 오인식(예: '패텔입'→'패러다임')은 confidence 무관 채택 + - 그 외 conf < CONF_LOW는 노이즈로 버림 + - CONF_LOW ~ CONF_HIGH 사이: 한글 2자 이상 또는 교정 발생한 것만 + - CONF_HIGH 이상: 그대로 채택 + """ + accepted: list[tuple[str, float]] = [] + for text, conf, _ in items: + if is_noise(text): + continue + corrected = apply_corrections(text) + was_corrected = corrected != text + if is_noise(corrected): + continue + + if was_corrected: + # 교정 사전 매칭 → conf 무관 채택 (신뢰도는 0.99로 덮어씀 — 사전 매칭 확신) + accepted.append((corrected, max(conf, 0.99))) + continue + + if conf < CONF_LOW: + continue + + if conf < CONF_HIGH: + if not re.search(r"[가-힣]{2,}", corrected): + continue + + accepted.append((corrected, conf)) + return accepted + + +def find_delta(accepted: list[tuple[str, float]], existing: set[str]) -> list[tuple[str, float]]: + delta: list[tuple[str, float]] = [] + seen: set[str] = set() + for phrase, conf in accepted: + n = normalize_for_compare(phrase) + if not n or len(n) < 2: + continue + if n in seen: + continue + if n in existing: + continue + words = [w for w in re.split(r"[\s\|\-·•/,.()\[\]:;!?#`'\"*~_+=<>&]+", phrase) if w] + word_norms = [normalize_for_compare(w) for w in words] + has_new = any(wn and len(wn) >= 2 and wn not in existing for wn in word_norms) + if not has_new and n not in existing: + continue + seen.add(n) + delta.append((phrase, conf)) + return delta + + +def strip_prev_ocr_section(text: str) -> str: + marker = APPEND_SECTION_MARKER + idx = text.find(marker) + if idx < 0: + return text + header_idx = text.rfind(APPEND_SECTION_HEADER, 0, idx) + cut = header_idx if header_idx >= 0 else idx + return text[:cut].rstrip() + "\n" + + +def append_delta(texts_md: Path, delta: list[tuple[str, float]]) -> str: + original = texts_md.read_text(encoding="utf-8") if texts_md.exists() else "" + cleaned = strip_prev_ocr_section(original) + if not delta: + return cleaned + ts = datetime.now().strftime("%Y-%m-%d") + lines = [ + "", + APPEND_SECTION_HEADER, + "", + f"> EasyOCR(2x 업스케일 + 대비강화) 자동 추출 ({ts}). 기존 텍스트 레이어에 없던 단어/문구만.", + APPEND_SECTION_MARKER, + "", + ] + for phrase, conf in delta: + lines.append(f"- {phrase} _(conf={conf:.2f})_") + lines.append("") + return cleaned.rstrip() + "\n" + "\n".join(lines) + + +def main() -> int: + ap = argparse.ArgumentParser() + ap.add_argument("--apply", action="store_true", help="texts.md에 실제 반영") + ap.add_argument("--only", type=str, default="") + args = ap.parse_args() + + idx: dict[str, dict] = json.loads(INDEX_PATH.read_text(encoding="utf-8")) + + print("[init] EasyOCR 로딩 (한/영, CPU)...") + import easyocr + reader = easyocr.Reader(["ko", "en"], gpu=False, verbose=False) + print("[init] OK") + + ts = datetime.now().strftime("%Y%m%d_%H%M%S") + out_dir = Path("data/runs") / f"{ts}_ocr_augment" + out_dir.mkdir(parents=True, exist_ok=True) + + numbers = sorted(idx.keys()) + summary_rows: list[dict] = [] + detail_lines: list[str] = [] + + for num in numbers: + entry = idx[num] + fid = entry["frame_id"] + if args.only and fid != args.only: + continue + png = PREVIEW_DIR / f"{num}.png" + texts_md = BLOCKS_DIR / fid / "texts.md" + if not png.exists(): + continue + + print(f"[{num}] {fid} OCR...", end="", flush=True) + raw_items = run_ocr_variants(reader, png) + deduped = dedupe_by_position(raw_items) + accepted = extract_accepted(deduped) + existing = load_existing_tokens(texts_md) + delta = find_delta(accepted, existing) + + # 저신뢰 detection (잠재 누락 신호): conf < LOW 인데 위치 정보가 있는 것 개수 + low_conf_count = sum(1 for _, c, _ in raw_items if c < CONF_LOW) + + print(f" 감지(중복제거) {len(deduped)}개 채택 {len(accepted)}개 델타 {len(delta)}개 " + f"저신뢰잔여 {low_conf_count}개") + + summary_rows.append({ + "num": num, + "fid": fid, + "raw": len(raw_items), + "dedup": len(deduped), + "accepted": len(accepted), + "delta": len(delta), + "low_conf": low_conf_count, + "delta_items": delta, + "low_conf_items": [(t, c) for t, c, _ in raw_items if c < CONF_LOW], + }) + + detail_lines.append(f"\n### {num}. frame `{fid}`") + detail_lines.append(f"- OCR 감지(중복제거 후): {len(deduped)}개") + detail_lines.append(f"- 기존 texts.md 토큰: {len(existing)}개") + detail_lines.append(f"- 채택(교정 후): {len(accepted)}개") + detail_lines.append(f"- **델타(신규 보강): {len(delta)}개**") + if delta: + detail_lines.append("") + detail_lines.append("| 신규 문구 | conf |") + detail_lines.append("|---|---|") + for p, c in delta: + detail_lines.append(f"| {p} | {c:.2f} |") + low = summary_rows[-1]["low_conf_items"] + if low: + detail_lines.append("") + detail_lines.append(f"
저신뢰 잔여 {len(low)}개 (잠재 누락 단서)") + detail_lines.append("") + for t, c in sorted(low, key=lambda x: -x[1])[:20]: + detail_lines.append(f"- `{t}` (conf={c:.3f})") + if len(low) > 20: + detail_lines.append(f"- ... 외 {len(low)-20}개") + detail_lines.append("
") + + if args.apply: + new_text = append_delta(texts_md, delta) + texts_md.parent.mkdir(parents=True, exist_ok=True) + texts_md.write_text(new_text, encoding="utf-8") + + # ─── summary ─── + frames_with_delta = [r for r in summary_rows if r["delta"] > 0] + frames_no_delta = [r for r in summary_rows if r["delta"] == 0] + + report = [ + "# OCR 보강 리포트 (EasyOCR + 전처리 + 교정)", + "", + f"- 드라이런: {'적용됨 (--apply)' if args.apply else '드라이런 (texts.md 미수정)'}", + f"- 대상 프레임: {len(summary_rows)}개", + f"- **텍스트 누락(델타 > 0) 프레임: {len(frames_with_delta)}개**", + f"- 델타 없음(보강 불필요) 프레임: {len(frames_no_delta)}개", + "", + "## 프레임별 요약", + "", + "| # | frame_id | 감지 | 채택 | **델타** | 저신뢰 | 델타 미리보기 |", + "|---|---|---|---|---|---|---|", + ] + for r in summary_rows: + preview = "; ".join(p for p, _ in r["delta_items"][:4]) + if len(r["delta_items"]) > 4: + preview += "…" + mark = "🔴" if r["delta"] > 0 else "·" + report.append( + f"| {r['num']} | `{r['fid']}` | {r['dedup']} | {r['accepted']} | " + f"{mark} **{r['delta']}** | {r['low_conf']} | {preview} |" + ) + + report.append("\n## 텍스트 누락 프레임 리스트 (델타 > 0)\n") + if frames_with_delta: + for r in frames_with_delta: + items = ", ".join(p for p, _ in r["delta_items"]) + report.append(f"- **#{r['num']}** `{r['fid']}` — 델타 {r['delta']}개: {items}") + else: + report.append("_(누락 없음)_") + + report.append("\n## 상세") + report.extend(detail_lines) + + out = out_dir / "report.md" + out.write_text("\n".join(report), encoding="utf-8") + print(f"\n[saved] {out}") + if args.apply: + print("[applied] texts.md 파일들 업데이트 완료") + else: + print("[dryrun] --apply 를 붙이면 texts.md 에 반영됩니다") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/scripts/previews/mdx04_f16_override.py b/scripts/previews/mdx04_f16_override.py new file mode 100644 index 0000000..9aa6bfe --- /dev/null +++ b/scripts/previews/mdx04_f16_override.py @@ -0,0 +1,416 @@ +"""MDX04 F16 override slide-fit preview — slide_fit_preview, NOT a Phase Z final. + +배경: + - V4 top1 = F26. 사용자 semantic review 로 F16 채택 + - 사유: MDX04 04-2.* 는 4-issue diagnostic 구조 → F16 quadrant pattern 적합 + - F26 figma 1:1 변환 부재 (별도 작업 보류) + - anchor 보정 / detect_mdx 수정 / v4_full32_result.yaml 변경 모두 없음 + +매핑 (사용자 결정 — B 수정판, 그대로 유지): + 04-2.1 (4) + 04-2.2 (4) = 8 항목을 4 원인군으로 그룹핑. 04-2.2 보존. + +레이아웃 전환 (composition_preview → slide_fit_preview): + 이전: 1280×1230 비표준 (composition preview) + 현재: 1280×720 표준 슬라이드 (slide_fit_preview) + ├ title bar (1280×56) + ├ body (1200×590) + │ ├ zone-left (340×590) = 04-1 compact 5-card stack + │ └ zone-right (840×590) = F16 quadrant zone-fit (4분면 + center quote) + └ footer pill (1280×48) + F16 native dim (1280×1015) 폐기. zone (840×590) 에 맞게 좌표 재계산. 폰트 축소. +""" +import json +import re +import sys +from datetime import datetime +from html import escape +from pathlib import Path + +import yaml +from jinja2 import Environment, FileSystemLoader, select_autoescape + +ROOT = Path(__file__).resolve().parents[2] +MDX_PATH = ROOT / "samples" / "mdx_batch" / "04.mdx" +V4_RESULT = ROOT / "tests" / "matching" / "v4_full32_result.yaml" +RUN_DIR = ROOT / "data" / "runs" / "mdx04_f16_override" +TEMPLATES_DIR = RUN_DIR / "templates" + + +# ─── 그룹핑 정의 (사용자 예시 그대로) ────────────────────────── + +GROUPING_RULE = { + 'description': '04-2.1 (4 정책 항목) + 04-2.2 (4 조직 항목) = 8 항목을 4 원인군으로 그룹핑. F16 4 분면 ribbon = 그룹명.', + 'reason': 'user wants 04-2.2 보존 + F16 4 분면 디자인 활용. 1:1 짝짓기 강제 회피.', + 'groups': [ + { + 'quadrant': 'q1', + 'name': '정책 집행 / 제도 운용 문제', + 'items': [ + {'source': '04-2.1', 'index': 0}, # 실질적 기술 경쟁을 저해하는 정책 집행 + {'source': '04-2.1', 'index': 1}, # 적용 효과가 있는 사례도 없이 방침부터 도입 + ], + }, + { + 'quadrant': 'q2', + 'name': '개념 이해 부족', + 'items': [ + {'source': '04-2.1', 'index': 2}, # 엔지니어링 S/W에 대한 개념 부재 + {'source': '04-2.2', 'index': 0}, # 공학적 개념 정립 부재 + {'source': '04-2.2', 'index': 2}, # DX/BIM의 근본 취지와 목표의 이해 부족 + ], + }, + { + 'quadrant': 'q3', + 'name': '기술 투자 / 본업 기술력 부족', + 'items': [ + {'source': '04-2.1', 'index': 3}, # 기술투자(R&D) 없는 성과 창출 기대 + {'source': '04-2.2', 'index': 1}, # '본업 기술력 확보' 우선의 개념 부재 + ], + }, + { + 'quadrant': 'q4', + 'name': '조직 / 수행 역량 문제', + 'items': [ + {'source': '04-2.2', 'index': 3}, # 과거의 타성에 머무르고 있는 기술자 집단 + ], + }, + ], +} + + +# ─── MDX 04 파싱 ──────────────────────────────────────────────── + +RE_SUBSECTION_HEAD = re.compile(r'^###\s+(\d+\.\d+)\s+(.+)$', re.MULTILINE) +RE_TOP_BULLET = re.compile(r'^-\s+\*\*([^*]+)\*\*\s*$') + + +def extract_subsection_items(text, num_label): + lines = text.split('\n') + start = None + for i, ln in enumerate(lines): + m = RE_SUBSECTION_HEAD.match(ln.strip()) + if m and m.group(1) == num_label: + start = i + break + if start is None: + return None, [] + end = len(lines) + for j in range(start + 1, len(lines)): + s = lines[j].strip() + if RE_SUBSECTION_HEAD.match(s) or s == '---': + end = j + break + section_title = lines[start].lstrip('# ').strip() + body_lines = lines[start + 1:end] + items = [] + cur = None + for ln in body_lines: + stripped = ln.strip() + m = RE_TOP_BULLET.match(stripped) + if m: + if cur is not None: + items.append(cur) + cur = {'headline': m.group(1).strip(), 'subs': []} + continue + m2 = re.match(r'^-\s+(.+)$', stripped) + if m2 and cur is not None and not stripped.startswith('- **'): + cur['subs'].append(m2.group(1).strip()) + if cur is not None: + items.append(cur) + return section_title, items + + +def extract_section_04_1_cards(text): + m = re.search(r'## 1\. DX에 대한 인식(.*?)(?=^## 2\.)', text, re.DOTALL | re.MULTILINE) + if not m: + return None, [] + body = m.group(1) + cards = [] + h3_iter = list(re.finditer(r']*>([^<]+)', body)) + for idx, h3m in enumerate(h3_iter): + label = h3m.group(1).strip() + section_end = h3_iter[idx + 1].start() if idx + 1 < len(h3_iter) else len(body) + section_text = body[h3m.end():section_end] + # 인용 (첫

의 따옴표 텍스트) + quote_m = re.search(r']*>(?:["“])(.+?)(?:["”])

', section_text, re.DOTALL) + if not quote_m: + quote_m = re.search(r']*>([^<]+)

', section_text, re.DOTALL) + quote = quote_m.group(1).strip() if quote_m else '' + bullets = [b.strip() for b in re.findall(r']*>([^<]+)', section_text)] + cards.append({'label': label, 'quote': quote, 'bullets': bullets}) + return '1. DX에 대한 인식', cards + + +# ─── F16 grouped mapper ──────────────────────────────────────── + +def map_to_f16_grouped(items_2_1, items_2_2, slide_title): + """8 items (2.1 4 + 2.2 4) → 4 quadrant groups (사용자 그룹핑 룰 적용).""" + source_map = {'04-2.1': items_2_1, '04-2.2': items_2_2} + payload = {'center_quote': slide_title} + for group in GROUPING_RULE['groups']: + q = group['quadrant'] + items_for_q = [] + for ref in group['items']: + src_items = source_map[ref['source']] + idx = ref['index'] + if idx < len(src_items): + src_item = src_items[idx] + items_for_q.append({ + 'source': '[' + ref['source'].replace('04-', '') + ']', + 'headline': src_item['headline'], + 'subs': src_item['subs'], + }) + payload[f'{q}_label'] = group['name'] + payload[f'{q}_items'] = items_for_q + return payload + + +def map_to_5card_compact_slots(cards, section_title): + return {'section_title': section_title, 'cards': cards} + + +# ─── V4 metadata lookup ─────────────────────────────────────── + +def get_top1(v4, sid): + sec = v4.get('mdx_sections', {}).get(sid) + if not sec: + return None + j = sec.get('judgments_full32', []) + return j[0] if j else None + + +def get_frame_judgment(v4, sid, frame_number): + sec = v4.get('mdx_sections', {}).get(sid) + if not sec: + return None + for e in sec.get('judgments_full32', []): + if e['frame_number'] == frame_number: + return e + return None + + +# ─── 메인 ────────────────────────────────────────────────────── + +def main(): + if not MDX_PATH.exists(): + print(f"ERROR: MDX 04 not found at {MDX_PATH}", file=sys.stderr) + sys.exit(1) + if not V4_RESULT.exists(): + print(f"ERROR: V4 result not found at {V4_RESULT}", file=sys.stderr) + sys.exit(1) + + mdx_text = MDX_PATH.read_text(encoding='utf-8') + v4 = yaml.safe_load(V4_RESULT.read_text(encoding='utf-8')) + + title_2_1, items_2_1 = extract_subsection_items(mdx_text, '2.1') + title_2_2, items_2_2 = extract_subsection_items(mdx_text, '2.2') + title_1, cards_1 = extract_section_04_1_cards(mdx_text) + + env = Environment( + loader=FileSystemLoader(str(TEMPLATES_DIR)), + autoescape=select_autoescape(['html', 'xml']), + ) + f16_zonefit_tpl = env.get_template("bim_issues_quadrant_four_zonefit.html.j2") + cards5_left_tpl = env.get_template("cards_5_left_zone.html.j2") + slide_fit_tpl = env.get_template("slide_fit_base.html.j2") + + # 04-2 통합 (그룹핑) → F16 zone-fit + payload_f16 = map_to_f16_grouped(items_2_1, items_2_2, slide_title='DX 지연
요인') + html_f16_zonefit = f16_zonefit_tpl.render(slot_payload=payload_f16) + + # 04-1 → 5-card left zone + payload_cards = map_to_5card_compact_slots(cards_1, section_title=title_1) + html_cards_left = cards5_left_tpl.render(slot_payload=payload_cards) + + # slide_fit base 조립 (1280×720) + slide_fit_html = slide_fit_tpl.render( + slide_title='4. DX 지연 요인', + slide_meta='F16 user_semantic_override · slide_fit_preview', + zone_left=html_cards_left, + zone_right=html_f16_zonefit, + slide_footer='검증 없는 정책의 일방적 추진과 조직의 회피, 이해 부족이 DX 지연을 반복시킨다', + ) + + # 통합 1 슬라이드 페이지 (banner + slide_fit + metadata) + timestamp = datetime.now().isoformat(timespec='seconds') + page_html = f''' + + + +MDX04 1280×720 slide_fit · F16 user_semantic_override + + + + +
+ MDX04 slide_fit_preview · 1280×720 표준 (NOT a Phase Z final)
+ composition_preview (1280×1230) → slide_fit_preview (1280×720) 전환. + 같은 grouping rule 유지 (04-2.* 8 항목 → 4 원인군). 04-2.2 보존. F16 native dim 폐기, zone-fit 적용. +
    +
  • layout = title (56) + body (1200×590, left 340 + right 840) + footer pill (48)
  • +
  • zone-left = 04-1 compact 5-card stack
  • +
  • zone-right = F16 quadrant zone-fit (4분면 + center quote)
  • +
  • q1 = 정책 집행 / 제도 운용 (2.1×2) · q2 = 개념 이해 부족 (2.1×1 + 2.2×2)
  • +
  • q3 = 기술 투자 / 본업 기술력 부족 (2.1×1 + 2.2×1) · q4 = 조직 / 수행 역량 (2.2×1)
  • +
+
+ +
+ {slide_fit_html} +
+ + + +''' + (RUN_DIR / "index.html").write_text(page_html, encoding='utf-8') + + # 단독 slide_fit (banner 없이 슬라이드 자체만) + standalone_slide = f''' +MDX04 1280×720 slide_fit (standalone) + +{slide_fit_html} + +''' + (RUN_DIR / "slide_1280x720.html").write_text(standalone_slide, encoding='utf-8') + + # debug.json + top1_2_1 = get_top1(v4, '04-2.1') + top1_2_2 = get_top1(v4, '04-2.2') + top1_1 = get_top1(v4, '04-1') + f16_2_1 = get_frame_judgment(v4, '04-2.1', 16) + f16_2_2 = get_frame_judgment(v4, '04-2.2', 16) + + # grouping coverage 검증 (모든 8 항목 사용됐는지) + used = set() + for g in GROUPING_RULE['groups']: + for ref in g['items']: + used.add((ref['source'], ref['index'])) + expected = set([('04-2.1', i) for i in range(len(items_2_1))] + + [('04-2.2', i) for i in range(len(items_2_2))]) + missing = sorted(expected - used) + extra = sorted(used - expected) + + debug = { + 'kind': 'mdx04_f16_override_slide_fit', + 'preview_stage': 'slide_fit_preview', + 'transition_from': 'composition_preview (1280×1230 비표준)', + 'transition_to': 'slide_fit_preview (1280×720 표준)', + 'transition_note': '같은 grouping rule 유지. F16 native height (1015px) 폐기. zone-fit 좌표 재계산. 폰트 축소.', + 'is_phase_z_final': False, + 'is_diagnostic': True, + 'is_preview_or_result_candidate': True, + 'generated_at': timestamp, + 'v4_source': str(V4_RESULT.relative_to(ROOT)), + 'mdx_source': str(MDX_PATH.relative_to(ROOT)), + 'integrated_slide': True, + 'layout': { + 'slide_dimensions': '1280×720', + 'title_bar_height': 56, + 'body': {'width': 1200, 'height': 590, 'left_zone': 340, 'right_zone': 840, 'gap': 20}, + 'footer_pill_height': 48, + 'zone_left': '04-1 compact 5-card stack (frame library gap)', + 'zone_right': '04-2 통합 F16 quadrant zone-fit (grouped)', + 'mdx_one_slide_principle': True, + 'standard_16_9': True, + }, + 'override_decision': { + 'selected_frame_source': 'user_semantic_override', + 'selected_frame': 'F16', + 'selected_template_id': 'bim_issues_quadrant_four', + 'reason': 'F16 quadrant pattern semantically/visually appropriate for MDX04 04-2.* ' + '(four-issue diagnostic structure). V4 top1 F26 figma 변환 부재 + semantic ' + 'review 에서 F16 가 더 적합 판단.', + }, + 'grouping_rule': GROUPING_RULE, + 'grouping_coverage': { + 'total_items': len(items_2_1) + len(items_2_2), + 'mapped_items': len(used), + 'missing': [{'source': s, 'index': i} for s, i in missing], + 'extra': [{'source': s, 'index': i} for s, i in extra], + 'all_items_preserved': not missing, + }, + 'sections': { + '04-2.1': { + 'mdx_title': title_2_1, + 'item_count': len(items_2_1), + 'v4_top1': { + 'frame_number': top1_2_1['frame_number'], + 'template_id': top1_2_1['template_id'], + 'label': top1_2_1['label'], + 'confidence': top1_2_1['confidence'], + }, + 'selected_frame': 16, + 'original_label': f16_2_1['label'] if f16_2_1 else None, + 'original_confidence': f16_2_1['confidence'] if f16_2_1 else None, + }, + '04-2.2': { + 'mdx_title': title_2_2, + 'item_count': len(items_2_2), + 'v4_top1': { + 'frame_number': top1_2_2['frame_number'], + 'template_id': top1_2_2['template_id'], + 'label': top1_2_2['label'], + 'confidence': top1_2_2['confidence'], + }, + 'selected_frame': 16, + 'original_label': f16_2_2['label'] if f16_2_2 else None, + 'original_confidence': f16_2_2['confidence'] if f16_2_2 else None, + 'preserved_in_grouping': True, + }, + '04-1': { + 'mdx_title': title_1, + 'card_count': len(cards_1), + 'v4_top1': { + 'frame_number': top1_1['frame_number'], + 'template_id': top1_1['template_id'], + 'label': top1_1['label'], + 'confidence': top1_1['confidence'], + } if top1_1 else None, + 'selected_frame': None, + 'override_note': '5-card library gap (32 frame DB 에 cardinality.ideal=5 frame 부재). ' + 'compact 5-column grid 로 통합 슬라이드 상단에 배치.', + }, + }, + 'caveats': [ + '정식 Phase Z final 아님 — V4 lookup 우회', + 'preview_stage = slide_fit_preview (1280×720 표준). 이전 composition_preview (1280×1230) 에서 전환', + 'F16 partial template = preview 전용 (data/runs/mdx04_f16_override/templates/) — design_agent/templates/phase_z2 미수정', + 'anchor 보정 / detect_mdx 수정 / v4_full32_result.yaml 변경 없음', + '04-2.1 의 F16 original_label = reject (anchor=0). override 로 진행', + '04-2.2 의 F16 original_label = restructure (사용 가능 라벨)', + '04-2.2 보존 — 그룹핑으로 8 항목 모두 분면에 매핑', + '04-1 = 5-card library gap. zone-left 에 compact stack 으로 배치', + '그룹핑 룰은 사용자 semantic 결정 (yaml/dict 로 명시). 자동 생성 아님', + 'F16 native dim (1280×1015) 폐기 — zone (840×590) 에 맞춰 좌표 재계산. 폰트 14px(ribbon)/11.5px(headline)/9.5px(sub)', + 'slide-fit 으로 폰트 작아짐 → 가독성 trade-off. composition_preview 와 비교 필요', + ], + } + (RUN_DIR / "debug.json").write_text( + json.dumps(debug, ensure_ascii=False, indent=2), encoding='utf-8', + ) + + # 이전 composition_preview 산출물 정리 — slide_fit_preview 로 대체 + for old in ["slide_04-2.1.html", "slide_04-2.2.html", "slide_04-1.html", + "slide_04-2_grouped.html", "slide_04-1_compact.html"]: + p = RUN_DIR / old + if p.exists(): + p.unlink() + + print(f"[mdx04_f16_override_slide_fit] generated:") + print(f" index : {RUN_DIR / 'index.html'}") + print(f" slide 1280×720 : {RUN_DIR / 'slide_1280x720.html'}") + print(f" debug : {RUN_DIR / 'debug.json'}") + print() + print(f"Coverage: {len(used)}/{len(expected)} items mapped, missing={list(missing)}") + print(f"Stage: composition_preview → slide_fit_preview") + + +if __name__ == "__main__": + main() diff --git a/scripts/previews/mdx04_partial_preview.py b/scripts/previews/mdx04_partial_preview.py new file mode 100644 index 0000000..b8cad1a --- /dev/null +++ b/scripts/previews/mdx04_partial_preview.py @@ -0,0 +1,404 @@ +"""MDX04 partial preview — diagnostic only, NOT a Phase Z final. + +목적: F16 (`bim_issues_quadrant_four`) 가 04-2.1 / 04-2.2 의 4 항목 구조와 +시각적으로 정합하는지 사용자가 눈으로 확인. + +방식: + - V4 runtime 우회 (정식 Phase Z 아님) + - F16 figma 원본 HTML 을 iframe 으로 임베드 (디자인 형태 그대로) + - 04-2.1 / 04-2.2 의 MDX 4 항목을 옆에 시각화 (구조 비교) + - 04-1 = frame library gap (5-card 구조, 매칭 frame 부재) placeholder + - diagnostic banner + V4 metadata + debug.json + +출력: + data/runs/mdx04_partial_preview/index.html + data/runs/mdx04_partial_preview/debug.json + data/runs/mdx04_partial_preview/f16_original/ (figma 원본 + assets) +""" +import json +import re +import sys +from datetime import datetime +from html import escape +from pathlib import Path + +import yaml + +ROOT = Path(__file__).resolve().parents[2] +MDX_PATH = ROOT / "samples" / "mdx_batch" / "04.mdx" +V4_RESULT = ROOT / "tests" / "matching" / "v4_full32_result.yaml" +RUN_DIR = ROOT / "data" / "runs" / "mdx04_partial_preview" + + +# ─── MDX 04 의 04-2.1 / 04-2.2 섹션 추출 (### bullet) ────────────── + +RE_SUBSECTION_HEAD = re.compile(r'^###\s+(\d+\.\d+)\s+(.+)$', re.MULTILINE) +RE_TOP_BULLET = re.compile(r'^-\s+\*\*([^*]+)\*\*\s*$') + + +def extract_subsection(text, num_label): + """### {num_label} ... 부터 다음 ### 또는 --- 직전까지 추출.""" + lines = text.split('\n') + start = None + for i, ln in enumerate(lines): + m = RE_SUBSECTION_HEAD.match(ln.strip()) + if m and m.group(1) == num_label: + start = i + break + if start is None: + return None, [] + end = len(lines) + for j in range(start + 1, len(lines)): + s = lines[j].strip() + if RE_SUBSECTION_HEAD.match(s) or s == '---': + end = j + break + section_title = lines[start].lstrip('# ').strip() + body_lines = lines[start + 1:end] + + # 4 항목 추출 (top bullet + nested bullets) + items = [] + cur = None + for ln in body_lines: + stripped = ln.strip() + m = RE_TOP_BULLET.match(stripped) + if m: + if cur is not None: + items.append(cur) + cur = {'headline': m.group(1).strip(), 'subs': []} + continue + m2 = re.match(r'^-\s+(.+)$', stripped) + if m2 and cur is not None and not stripped.startswith('- **'): + cur['subs'].append(m2.group(1).strip()) + if cur is not None: + items.append(cur) + return section_title, items + + +def extract_section_04_1(text): + """04-1 = ## 1. DX에 대한 인식.

카드 5 개 + 각 카드 안 인용 + bullet 3 개.""" + lines = text.split('\n') + start = None + for i, ln in enumerate(lines): + if ln.strip() == '## 1. DX에 대한 인식': + start = i + break + if start is None: + return None, [] + end = len(lines) + for j in range(start + 1, len(lines)): + s = lines[j].strip() + if s.startswith('## ') and s != '## 1. DX에 대한 인식': + end = j + break + + body = '\n'.join(lines[start:end]) + + #

라벨 + 다음

인용 +

  • bullet 3 개 + cards = [] + for m in re.finditer(r']*>([^<]+)

', body): + cards.append({'label': m.group(1).strip()}) + + return lines[start].lstrip('# ').strip(), cards + + +# ─── V4 metadata lookup ────────────────────────────────────────── + +def get_f16_judgment(v4, section_id): + sec = v4['mdx_sections'].get(section_id) + if not sec: + return None + for e in sec['judgments_full32']: + if e['frame_number'] == 16: + return e + return None + + +def get_top1(v4, section_id): + sec = v4['mdx_sections'].get(section_id) + if not sec: + return None + j = sec.get('judgments_full32', []) + return j[0] if j else None + + +# ─── HTML 렌더링 ───────────────────────────────────────────────── + +def render_items_html(items): + parts = ['
'] + for i, it in enumerate(items, 1): + parts.append('
') + parts.append(f'
{i}. {escape(it["headline"])}
') + if it['subs']: + parts.append('
    ') + for s in it['subs']: + parts.append(f'
  • {escape(s)}
  • ') + parts.append('
') + parts.append('
') + parts.append('
') + return '\n'.join(parts) + + +def render_cards_html(cards): + parts = ['
'] + for i, c in enumerate(cards, 1): + parts.append(f'
{i}. {escape(c["label"])}
') + parts.append('
') + return '\n'.join(parts) + + +def render_v4_metadata_html(j, label_note=''): + if j is None: + return '
V4 entry not found
' + axes = j.get('axes', {}) + return f'''
+
+ V4 rank:{j["v4_full_rank"]} + conf:{j["confidence"]:.4f} + label:{j["label"]} +
+
+ axes: + anchor={axes.get("anchor", 0):.2f} · + cardinality={axes.get("cardinality", 0):.2f} · + relation={axes.get("relation", 0):.2f} · + slot={axes.get("slot", 0):.2f} · + content={axes.get("content", 0):.4f} +
+ {f'
{label_note}
' if label_note else ''} +
''' + + +def render_section(section_id, mdx_title, items_html, j_f16, top1, note): + """좌: F16 figma 원본 iframe / 우: MDX 텍스트 4 항목 / 하: V4 metadata.""" + label_note = note + return f'''
+
+

{escape(section_id)} · {escape(mdx_title)}

+
+ F16 (bim_issues_quadrant_four) candidate · top1 = F{top1["frame_number"]} ({top1["label"]}, conf {top1["confidence"]:.4f}) +
+
+
+
+
F16 figma 원본 (디자인 형태)
+
+ +
+
+
+
MDX 04 {escape(section_id)} 본문 (4 항목)
+ {items_html} +
+
+ {render_v4_metadata_html(j_f16, label_note)} +
''' + + +def render_04_1_placeholder(top1): + return f'''
+
+

04-1 · DX에 대한 인식

+
+ Frame library gap — 5-card 구조, 32 frame DB 에 cardinality.ideal=5 frame 부재 (이번 preview 제외) +
+
+
+ 왜 제외: 04-1 은 5 개 카드 (기술/효과/인력/경제/실무) — h3_cards=5 인식까지는 정상. 다만 32 frame + 중 5-card 대응 frame 이 없어 V4 multi-constraint 통과 가능 frame 자체가 없음 (사용 가능 0/32, 모두 reject). + 이건 detect bug 가 아니라 frame library readiness 문제. +

+ V4 top1 = F{top1["frame_number"]} (conf {top1["confidence"]:.4f}, {top1["label"]}) — F16 도 rank 15, conf 0.361, reject. +
+
''' + + +# ─── 메인 ──────────────────────────────────────────────────────── + +def main(): + if not V4_RESULT.exists(): + print(f"ERROR: V4 result not found at {V4_RESULT}", file=sys.stderr) + sys.exit(1) + if not MDX_PATH.exists(): + print(f"ERROR: MDX 04 not found at {MDX_PATH}", file=sys.stderr) + sys.exit(1) + + mdx_text = MDX_PATH.read_text(encoding='utf-8') + v4 = yaml.safe_load(V4_RESULT.read_text(encoding='utf-8')) + + # 04-2.1 + title_2_1, items_2_1 = extract_subsection(mdx_text, '2.1') + j16_2_1 = get_f16_judgment(v4, '04-2.1') + top1_2_1 = get_top1(v4, '04-2.1') + + # 04-2.2 + title_2_2, items_2_2 = extract_subsection(mdx_text, '2.2') + j16_2_2 = get_f16_judgment(v4, '04-2.2') + top1_2_2 = get_top1(v4, '04-2.2') + + # 04-1 + title_1, cards_1 = extract_section_04_1(mdx_text) + top1_1 = get_top1(v4, '04-1') + + # HTML 조립 + section_2_1_html = render_section( + '04-2.1', title_2_1, + render_items_html(items_2_1), + j16_2_1, top1_2_1, + note='F16 candidate — V4 label=reject (anchor=0). 의미 매칭 회복했으나 anchor terms 부재로 multi-constraint 탈락. preview 목적 = F16 디자인 / 04-2.1 본문 정합성 시각 확인.' + ) + section_2_2_html = render_section( + '04-2.2', title_2_2, + render_items_html(items_2_2), + j16_2_2, top1_2_2, + note='F16 restructure — V4 label=restructure 통과 (사용 가능 라벨). preview 목적 = F16 디자인이 04-2.2 본문에 시각적으로 fit 한지 확인.' + ) + section_1_html = render_04_1_placeholder(top1_1) + + timestamp = datetime.now().isoformat(timespec='seconds') + page_html = f''' + + + +MDX04 Partial Preview · diagnostic only + + + + + + +{section_2_1_html} + +{section_2_2_html} + +{section_1_html} + + +''' + + out_html = RUN_DIR / "index.html" + out_html.write_text(page_html, encoding='utf-8') + + debug = { + 'kind': 'mdx04_partial_preview', + 'is_phase_z_final': False, + 'is_diagnostic': True, + 'purpose': 'F16 디자인 / 04-2.* 4 항목 구조 시각 정합성 확인', + 'generated_at': timestamp, + 'v4_source': str(V4_RESULT.relative_to(ROOT)), + 'mdx_source': str(MDX_PATH.relative_to(ROOT)), + 'sections': { + '04-2.1': { + 'mdx_title': title_2_1, + 'item_count': len(items_2_1), + 'top1': top1_2_1, + 'f16_judgment': j16_2_1, + 'preview_label': 'F16 candidate (V4 label = reject, conf 0.648, anchor=0)', + }, + '04-2.2': { + 'mdx_title': title_2_2, + 'item_count': len(items_2_2), + 'top1': top1_2_2, + 'f16_judgment': j16_2_2, + 'preview_label': 'F16 restructure (V4 label = restructure, 사용 가능 통과)', + }, + '04-1': { + 'mdx_title': title_1, + 'card_count': len(cards_1), + 'top1': top1_1, + 'preview_label': 'EXCLUDED — frame library gap (5-card structure, no matching frame in 32 DB)', + }, + }, + 'caveats': [ + '정식 Phase Z final 아님 — V4 runtime / mapper / partial 모두 우회', + 'F16 figma 원본 HTML 을 그대로 임베드 — 디자인 형태만 시각화 (텍스트 슬롯 매핑 X)', + '04-2.1 의 F16 V4 label = reject (anchor=0) — 의미 매칭 회복했으나 anchor terms 부재', + '04-2.2 의 F16 V4 label = restructure — 사용 가능 라벨, 단 정식 partial 미작성', + '04-1 = frame library readiness 문제 (detect bug 아님)', + ], + } + out_debug = RUN_DIR / "debug.json" + out_debug.write_text(json.dumps(debug, ensure_ascii=False, indent=2), encoding='utf-8') + + print(f"[mdx04_partial_preview] generated:") + print(f" html : {out_html}") + print(f" debug : {out_debug}") + print(f" figma : {RUN_DIR / 'f16_original' / 'index.html'}") + + +if __name__ == "__main__": + main() diff --git a/scripts/run_pipeline_v2.py b/scripts/run_pipeline_v2.py new file mode 100644 index 0000000..8428de2 --- /dev/null +++ b/scripts/run_pipeline_v2.py @@ -0,0 +1,45 @@ +"""Pipeline v2 실행 스크립트. + +사용법: + python scripts/run_pipeline_v2.py + python scripts/run_pipeline_v2.py samples/mdx/03.*.mdx +""" +import sys +import time +from pathlib import Path + +sys.path.insert(0, str(Path(__file__).parent.parent)) + +from src.pipeline_v2 import generate_slide_v2 + + +def main(): + # 인자로 MDX 경로, 없으면 기본값 + if len(sys.argv) > 1: + mdx_path = Path(sys.argv[1]) + else: + mdx_path = Path("samples/mdx/03. DX 시행을 위한 필수 요건 및 혁신 방안.mdx") + + if not mdx_path.exists(): + print(f"MDX 파일 없음: {mdx_path}") + return + + content = mdx_path.read_text(encoding="utf-8") + print(f"MDX: {mdx_path.name}") + print(f"길이: {len(content)}자") + print() + + start = time.time() + result = generate_slide_v2(content, base_path=str(mdx_path.parent)) + elapsed = time.time() - start + + print(f"\n완료! ({elapsed:.1f}초)") + print(f"run_id: {result['run_id']}") + print(f"결과: {result['run_dir']}/") + print(f" final.html") + print(f" final_context.json") + print(f" steps/") + + +if __name__ == "__main__": + main() diff --git a/src/block_matcher_tfidf.py b/src/block_matcher_tfidf.py new file mode 100644 index 0000000..db1274d --- /dev/null +++ b/src/block_matcher_tfidf.py @@ -0,0 +1,185 @@ +"""TF-IDF 기반 블록 매칭 엔진. + +texts.md의 원본 텍스트를 직접 사용 — keywords 수동 생성 불필요. +frame_extractor가 텍스트를 추출하고, 여기서 TF-IDF 유사도를 계산. + +사용법: + matcher = TfidfBlockMatcher() + result = matcher.match("DX 시행을 위한 필수 요건", ["기술(디지털)", "사람(역량)"]) +""" +from __future__ import annotations + +import logging +import math +import re +from collections import Counter +from pathlib import Path +from typing import Any + +from src.frame_extractor import extract_all_frames + +logger = logging.getLogger(__name__) + + +class TfidfBlockMatcher: + """TF-IDF 기반 블록 매칭기. texts.md 직접 사용.""" + + def __init__( + self, + blocks_dir: str | Path = "figma_to_html_agent/blocks", + catalog_path: str | Path = "templates/catalog/blocks.yaml", + ): + self.frames: list[dict] = extract_all_frames(blocks_dir) + self.catalog = self._load_catalog(catalog_path) + + # 프레임별 전체 텍스트 + catalog 텍스트 합침 + self.doc_texts: list[str] = [] + self.doc_ids: list[str] = [] + for frame in self.frames: + # texts.md 원본 텍스트 사용 + text = frame.get("all_text", "") + # catalog에서 추가 정보 (when, description) + cat_entry = self._find_catalog_entry(frame["frame_id"]) + if cat_entry: + text += " " + cat_entry.get("when", "") + text += " " + cat_entry.get("description", "") + self.doc_ids.append(cat_entry.get("id", frame["frame_id"])) + else: + self.doc_ids.append(frame["frame_id"]) + self.doc_texts.append(text) + + # IDF 사전 계산 + self.idf = self._compute_idf(self.doc_texts) + logger.info(f"[tfidf] {len(self.frames)}개 프레임 인덱싱 완료 (texts.md 직접 사용)") + + def _load_catalog(self, path: Path | str) -> list[dict]: + """catalog 로드 (있으면).""" + path = Path(path) + if not path.exists(): + return [] + import yaml + try: + data = yaml.safe_load(path.read_text(encoding="utf-8")) + return data if isinstance(data, list) else data.get("blocks", []) + except Exception: + return [] + + def _find_catalog_entry(self, frame_id: str) -> dict | None: + """frame_id로 catalog 항목 찾기.""" + for entry in self.catalog: + if entry.get("source_frame") == frame_id: + return entry + return None + + def _compute_idf(self, documents: list[str]) -> dict[str, float]: + """IDF 계산.""" + N = len(documents) + doc_freq = Counter() + for doc in documents: + words = set(doc.split()) + for w in words: + doc_freq[w] += 1 + return {w: math.log(N / (freq + 1)) for w, freq in doc_freq.items()} + + def _tfidf_vec(self, text: str) -> dict[str, float]: + """텍스트 → TF-IDF 벡터.""" + words = text.split() + tf = Counter(words) + total = len(words) if words else 1 + vec = {} + for w in tf: + idf = self.idf.get(w, math.log(len(self.doc_texts) + 1)) + vec[w] = (tf[w] / total) * idf + return vec + + def _cosine(self, a: dict, b: dict) -> float: + """cosine similarity.""" + keys = set(a) | set(b) + dot = sum(a.get(k, 0) * b.get(k, 0) for k in keys) + mag_a = math.sqrt(sum(v ** 2 for v in a.values())) if a else 0 + mag_b = math.sqrt(sum(v ** 2 for v in b.values())) if b else 0 + if mag_a == 0 or mag_b == 0: + return 0.0 + return dot / (mag_a * mag_b) + + def _preprocess_query(self, text: str) -> str: + """MDX 쿼리 텍스트 전처리 (프레임 전처리와 동일 규칙).""" + text = text.replace("S/W", "SW 소프트웨어") + text = text.replace("H/W", "HW 하드웨어") + text = re.sub(r'\bDX\b', 'DX 디지털전환', text) + text = re.sub(r'\bBIM\b', 'BIM 건설정보모델링', text) + text = text.replace("(", " ").replace(")", " ") + text = text.replace("[", " ").replace("]", " ") + text = re.sub(r'[·•→←↔×+/]', ' ', text) + text = re.sub(r'\s+', ' ', text).strip() + return text + + def match( + self, + zone_title: str, + sub_titles: list[str] | None = None, + d1_items: list[str] | None = None, + top_k: int = 3, + ) -> list[dict]: + """중목차/소목차 텍스트로 프레임 매칭. + + Returns: + [{"block_id": str, "frame_id": str, "score": float, + "title_text": str, "rough_structure": str}] + """ + if not self.doc_texts: + return [] + + # 쿼리 구성 + parts = [zone_title] + if sub_titles: + parts.extend(sub_titles) + if d1_items: + parts.extend(d1_items) + query = self._preprocess_query(" ".join(parts)) + + # TF-IDF 유사도 계산 + query_vec = self._tfidf_vec(query) + scores = [] + for i, doc_text in enumerate(self.doc_texts): + doc_vec = self._tfidf_vec(doc_text) + score = self._cosine(query_vec, doc_vec) + scores.append((i, score)) + + # 상위 K개 + scores.sort(key=lambda x: -x[1]) + results = [] + for idx, score in scores[:top_k]: + if score <= 0: + continue + frame = self.frames[idx] + results.append({ + "block_id": self.doc_ids[idx], + "frame_id": frame["frame_id"], + "score": round(score, 4), + "method": "tfidf", + "title_text": frame.get("title_text", ""), + "rough_structure": frame.get("rough_structure", ""), + "item_count": frame.get("item_count", 0), + }) + + if results: + logger.info( + f"[tfidf] '{zone_title}' → top: {results[0]['block_id']} " + f"(score={results[0]['score']}, frame={results[0]['frame_id']})" + ) + + return results + + def match_with_threshold( + self, + zone_title: str, + sub_titles: list[str] | None = None, + d1_items: list[str] | None = None, + threshold: float = 0.10, + ) -> dict | None: + """threshold 이상이면 best match, 아니면 None → recipe 경로.""" + results = self.match(zone_title, sub_titles, d1_items, top_k=1) + if results and results[0]["score"] >= threshold: + return results[0] + return None diff --git a/src/catalog_blocks.py b/src/catalog_blocks.py new file mode 100644 index 0000000..537f4d2 --- /dev/null +++ b/src/catalog_blocks.py @@ -0,0 +1,69 @@ +"""새 catalog 로더. + +기존 templates/catalog.yaml과 별도로, +templates/catalog/blocks.yaml을 로드하는 모듈. + +기존 코드는 건드리지 않음. +""" +from __future__ import annotations + +import logging +from pathlib import Path +from typing import Any + +logger = logging.getLogger(__name__) + + +def load_blocks_catalog( + path: str | Path = "templates/catalog/blocks.yaml", +) -> list[dict]: + """blocks.yaml 로드. + + Returns: + [{"id": "prerequisites-3col", + "structure_type": "3col-parallel", + "keywords": ["필수", "요건", ...], + "slots": ["sub_title", "body", "bullets"], + "recipe_compat": ["direct_fit", "parallel_cluster"], + "not_for": ["long_table"], + "template": "blocks/structures/prerequisites-3col.html", + "when": "3개 병렬 비교"}, ...] + """ + import yaml + path = Path(path) + if not path.exists(): + logger.warning(f"[catalog] blocks.yaml 없음: {path}") + return [] + + try: + data = yaml.safe_load(path.read_text(encoding="utf-8")) + blocks = data if isinstance(data, list) else data.get("blocks", []) + logger.info(f"[catalog] {len(blocks)}개 블록 로드: {path}") + return blocks + except Exception as e: + logger.error(f"[catalog] 로드 실패: {e}") + return [] + + +def find_block_by_id( + blocks: list[dict], + block_id: str, +) -> dict | None: + """ID로 블록 찾기.""" + return next((b for b in blocks if b.get("id") == block_id), None) + + +def filter_blocks_by_structure( + blocks: list[dict], + structure_type: str, +) -> list[dict]: + """structure_type으로 필터링.""" + return [b for b in blocks if b.get("structure_type") == structure_type] + + +def filter_blocks_by_recipe( + blocks: list[dict], + recipe: str, +) -> list[dict]: + """recipe 호환 블록 필터링.""" + return [b for b in blocks if recipe in b.get("recipe_compat", [])] diff --git a/src/frame_extractor.py b/src/frame_extractor.py new file mode 100644 index 0000000..8354973 --- /dev/null +++ b/src/frame_extractor.py @@ -0,0 +1,205 @@ +"""프레임별 텍스트 + 메타 추출기. + +figma_to_html_agent/blocks/{frame_id}/texts.md를 파싱하여 +TF-IDF 매칭용 데이터 구조를 만든다. + +keywords 수동 생성 불필요 — texts.md의 원본 텍스트를 직접 사용. +""" +from __future__ import annotations + +import logging +import re +from pathlib import Path +from typing import Any + +logger = logging.getLogger(__name__) + + +def extract_frame_meta(texts_md_path: Path) -> dict: + """texts.md에서 프레임 메타 추출. + + Returns: + { + "frame_id": "1171281190", + "title_text": "필수조건", + "subtitle_texts": ["기술(디지털)", "사람(역량)", ...], + "body_texts": ["건설단계별 근본적인...", ...], + "all_text": "필수조건 기술 디지털 ...", ← TF-IDF용 전체 텍스트 + "item_count": 3, + "rough_structure": "3col", + "sections": [{"heading": "타이틀", "lines": ["필수조건"]}, ...] + } + """ + if not texts_md_path.exists(): + return {} + + content = texts_md_path.read_text(encoding="utf-8") + frame_id = texts_md_path.parent.name + + # 섹션별 파싱 (## 기준) + sections = [] + current_heading = "" + current_lines = [] + + for line in content.split("\n"): + line = line.strip() + if line.startswith("## "): + if current_heading or current_lines: + sections.append({"heading": current_heading, "lines": current_lines}) + current_heading = line.lstrip("# ").strip() + current_lines = [] + elif line.startswith("### "): + # 서브섹션은 heading에 포함 + current_lines.append(line.lstrip("# ").strip()) + elif line.startswith("# "): + # 최상위 제목 (프레임 ID) — 건너뜀 + continue + elif line.startswith(">"): + continue + elif line and not line.startswith("-"): + current_lines.append(line) + elif line.startswith("- "): + current_lines.append(line.lstrip("- ").strip()) + + if current_heading or current_lines: + sections.append({"heading": current_heading, "lines": current_lines}) + + # 층별 텍스트 분류 + title_text = "" + subtitle_texts = [] + body_texts = [] + + for sec in sections: + heading = sec["heading"].lower() + lines = sec["lines"] + + if "타이틀" in heading or "제목" in heading: + title_text = " ".join(lines) + elif "서브" in heading or "헤더" in heading or "카테고리" in heading: + subtitle_texts.extend(lines) + elif "열" in heading or "col" in heading.lower(): + # 열별 텍스트 → subtitle + body + for line in lines: + if len(line) < 20: + subtitle_texts.append(line) + else: + body_texts.append(line) + elif "행" in heading or "row" in heading.lower(): + # 행별 텍스트 + for line in lines: + if len(line) < 15: + subtitle_texts.append(line) + else: + body_texts.append(line) + elif "결론" in heading or "요약" in heading: + body_texts.extend(lines) + else: + # 기타 — 길이로 구분 + for line in lines: + if len(line) < 20: + subtitle_texts.append(line) + else: + body_texts.append(line) + + # rough_structure 추정 + rough_structure = _guess_structure(sections, subtitle_texts) + + # all_text: TF-IDF용 전체 텍스트 (전처리 적용) + all_parts = [title_text] + subtitle_texts + body_texts + all_text = " ".join(all_parts) + all_text = _preprocess_text(all_text) + + return { + "frame_id": frame_id, + "title_text": title_text.strip(), + "subtitle_texts": subtitle_texts, + "body_texts": body_texts, + "all_text": all_text, + "item_count": len(subtitle_texts), + "rough_structure": rough_structure, + "sections": sections, + } + + +def _guess_structure(sections: list[dict], subtitles: list[str]) -> str: + """섹션 구조에서 대략적인 블록 유형 추정.""" + headings = [s["heading"].lower() for s in sections] + heading_text = " ".join(headings) + + # 열 기반 + col_count = sum(1 for h in headings if "열" in h or "col" in h) + if col_count >= 3: + return "3col" + if col_count >= 2: + return "2col" + + # 행 기반 + row_count = sum(1 for h in headings if "행" in h or "row" in h) + if row_count >= 2: + return "rows" + + # 좌/우 + if any("좌" in h or "left" in h for h in headings): + return "2col-compare" + + # 표 + if any("표" in h or "table" in h for h in headings): + return "table" + + # 기본 + if len(subtitles) >= 3: + return "list" + + return "unknown" + + +def _preprocess_text(text: str) -> str: + """TF-IDF용 텍스트 전처리. + + - 표기 통일 + - 괄호/특수문자 정리 + - 중복 제거 + """ + # 표기 통일 + text = text.replace("S/W", "SW 소프트웨어") + text = text.replace("H/W", "HW 하드웨어") + text = re.sub(r'\bDX\b', 'DX 디지털전환', text) + text = re.sub(r'\bBIM\b', 'BIM 건설정보모델링', text) + + # 괄호 내용 유지하되 괄호 제거 + text = text.replace("(", " ").replace(")", " ") + text = text.replace("[", " ").replace("]", " ") + + # 특수문자 정리 + text = re.sub(r'[·•→←↔×+/]', ' ', text) + text = re.sub(r'\s+', ' ', text).strip() + + return text + + +def extract_all_frames( + blocks_dir: str | Path = "figma_to_html_agent/blocks", +) -> list[dict]: + """모든 프레임의 메타 추출. + + Returns: + [{"frame_id": ..., "title_text": ..., "all_text": ..., ...}] + """ + blocks_dir = Path(blocks_dir) + if not blocks_dir.exists(): + logger.warning(f"[extractor] blocks 폴더 없음: {blocks_dir}") + return [] + + frames = [] + for frame_dir in sorted(blocks_dir.iterdir()): + if not frame_dir.is_dir(): + continue + texts_md = frame_dir / "texts.md" + if texts_md.exists(): + meta = extract_frame_meta(texts_md) + if meta: + frames.append(meta) + logger.debug(f"[extractor] {meta['frame_id']}: {meta['title_text']} ({meta['rough_structure']})") + + logger.info(f"[extractor] {len(frames)}개 프레임 추출 완료") + return frames diff --git a/src/pipeline_v2.py b/src/pipeline_v2.py new file mode 100644 index 0000000..7403108 --- /dev/null +++ b/src/pipeline_v2.py @@ -0,0 +1,356 @@ +"""Pipeline v2: TF-IDF 기반 블록 매칭 + 렌더링 파이프라인. + +기존 pipeline.py를 건드리지 않고, 새 매칭/렌더링 엔진으로 동작하는 별도 파이프라인. + +입출력 계약: + 입력: MDX 텍스트 + base_path + 출력: data/runs/{run_id}/ 에 final.html + 단계별 context + +흐름: + 1. MDX 정규화 + 2. zone 구분 (중목차 기준) + 3. TF-IDF 블록 매칭 (direct-fit / recipe 분기) + 4. 블록 렌더링 (템플릿 로드 + 슬롯 삽입) + 5. slide-base 조립 + 6. 저장 +""" +from __future__ import annotations + +import json +import logging +import re +import time +from datetime import datetime +from pathlib import Path +from typing import Any + +from src.block_matcher_tfidf import TfidfBlockMatcher +from src.catalog_blocks import load_blocks_catalog, find_block_by_id + +logger = logging.getLogger(__name__) + + +def generate_slide_v2( + mdx_content: str, + base_path: str = "", + catalog_path: str = "templates/catalog/blocks.yaml", + threshold: float = 0.15, +) -> dict: + """v2 파이프라인: MDX → 슬라이드 HTML. + + Returns: + {"run_id": str, "run_dir": str, "final_html": str, "steps": dict} + """ + templates_dir = Path("templates") + run_id = datetime.now().strftime("%Y%m%d_%H%M%S") + run_dir = Path("data/runs") / run_id + run_dir.mkdir(parents=True, exist_ok=True) + steps_dir = run_dir / "steps" + steps_dir.mkdir(exist_ok=True) + + steps = {} + + # ══ Step 1: MDX 정규화 ══ + logger.info("[v2] Step 1: MDX 정규화") + from src.mdx_normalizer import normalize_mdx_content + normalized = normalize_mdx_content(mdx_content) + steps["step1_normalize"] = { + "title": normalized.get("title", ""), + "sections_count": len(normalized.get("sections", [])), + "sections": normalized.get("sections", []), + } + + # Step 1 저장 + _save_step_html(steps_dir / "step1_normalize.html", "Step 1: MDX 정규화", [ + f"title: {normalized.get('title', '')}", + f"sections: {len(normalized.get('sections', []))}개", + *[f" level={s['level']} | {s['title']} | {len(s.get('content',''))}자" + for s in normalized.get("sections", [])], + ]) + + # ══ Step 2: zone 구분 ══ + logger.info("[v2] Step 2: zone 구분") + from src.section_parser import extract_major_sections + sections = normalized.get("sections", []) + major_sections = extract_major_sections(sections) + steps["step2_zones"] = [ + {"title": s["title"], "sub_titles": s["sub_titles"], "content_len": len(s.get("content", ""))} + for s in major_sections + ] + + _save_step_html(steps_dir / "step2_zones.html", "Step 2: zone 구분", [ + *[f"zone {i+1}: {s['title']} | sub_titles={s['sub_titles']} | {len(s.get('content',''))}자" + for i, s in enumerate(major_sections)], + ]) + + # ══ Step 3: TF-IDF 블록 매칭 ══ + logger.info("[v2] Step 3: TF-IDF 블록 매칭") + matcher = TfidfBlockMatcher(catalog_path) + catalog = load_blocks_catalog(catalog_path) + + match_results = {} + step3_lines = [] + for sec in major_sections: + title = sec["title"] + sub_titles = sec["sub_titles"] + content = sec.get("content", "") + d1_items = [re.sub(r'\*+', '', d).strip() + for d in re.findall(r'^D1:\s*(.*)', content, re.MULTILINE)] + + top3 = matcher.match(title, sub_titles, d1_items, top_k=3) + best = matcher.match_with_threshold(title, sub_titles, d1_items, threshold=threshold) + + match_results[title] = { + "best": best, + "top3": top3, + "path": "direct-fit" if best else "recipe", + "sub_titles": sub_titles, + "content": content, + "d1_items": d1_items, + } + + step3_lines.append(f"zone: {title}") + step3_lines.append(f" sub_titles: {sub_titles}") + if best: + step3_lines.append(f" → direct-fit: {best['block_id']} (score={best['score']})") + else: + step3_lines.append(f" → recipe 경로") + for j, c in enumerate(top3): + step3_lines.append(f" #{j+1}: {c['block_id']} (score={c['score']})") + step3_lines.append("") + + steps["step3_matching"] = match_results + + _save_step_html(steps_dir / "step3_matching.html", "Step 3: TF-IDF 블록 매칭", step3_lines) + + # ══ Step 4: 블록 렌더링 + slide-base 조립 ══ + logger.info("[v2] Step 4: 블록 렌더링 + 조립") + + slide_title = normalized.get("title", "") + # conclusion 추출 + from src.section_parser import extract_conclusion_text + conclusion = extract_conclusion_text(mdx_content) + conclusion = re.sub(r'^[\*•\-]\s*', '', conclusion).strip() + + # 각 zone의 블록 HTML 렌더링 + zone_htmls = [] + zone_csses = [] + total_zones = len(major_sections) + + for sec in major_sections: + title = sec["title"] + info = match_results[title] + + if info["path"] == "direct-fit" and info["best"]: + # direct-fit: 블록 템플릿 로드 + block_id = info["best"]["block_id"] + # template 경로: catalog에 없으면 structures/ 에서 찾기 + block_meta = find_block_by_id(catalog, block_id) + template_path = "" + if block_meta: + template_path = block_meta.get("template", "") + if not template_path: + template_path = f"blocks/structures/{block_id}.html" + if (templates_dir / template_path).exists(): + html, css = _render_block_template( + templates_dir, template_path, title, info + ) + zone_htmls.append((title, html)) + if css: + zone_csses.append(css) + logger.info(f"[v2] {title} → block '{block_id}' 렌더") + else: + html = _render_fallback(title, info) + zone_htmls.append((title, html)) + logger.warning(f"[v2] {title} → block '{block_id}' catalog에 없음, fallback") + else: + # recipe 경로: direct render + html = _render_fallback(title, info) + zone_htmls.append((title, html)) + logger.info(f"[v2] {title} → recipe fallback render") + + # slide-base 조립 + final_html = _assemble_slide( + templates_dir, slide_title, conclusion, + zone_htmls, zone_csses, total_zones, + ) + + steps["step4_render"] = { + "zones": [{"title": t, "html_len": len(h)} for t, h in zone_htmls], + } + + _save_step_html(steps_dir / "step4_render.html", "Step 4: 렌더링", [ + *[f"{t}: {len(h)}자 HTML" for t, h in zone_htmls], + ]) + + # ══ Step 5: 저장 ══ + logger.info("[v2] Step 5: 저장") + (run_dir / "final.html").write_text(final_html, encoding="utf-8") + + context = { + "run_id": run_id, + "title": slide_title, + "conclusion": conclusion, + "steps": {k: _safe_serialize(v) for k, v in steps.items()}, + "match_results": {k: _safe_serialize(v) for k, v in match_results.items()}, + } + (run_dir / "final_context.json").write_text( + json.dumps(context, ensure_ascii=False, indent=2), encoding="utf-8" + ) + + logger.info(f"[v2] 완료: {run_dir}") + return {"run_id": run_id, "run_dir": str(run_dir), "final_html": final_html} + + +def _render_block_template( + templates_dir: Path, + template_path: str, + zone_title: str, + info: dict, +) -> tuple[str, str]: + """블록 템플릿을 로드하고 그대로 반환. + + 현재는 블록 HTML을 그대로 사용 (슬롯 교체는 추후). + CSS는 분리하여 head로 이동. + """ + full_path = templates_dir / template_path + if not full_path.exists(): + logger.warning(f"[v2] 템플릿 없음: {full_path}") + return _render_fallback(zone_title, info), "" + + raw = full_path.read_text(encoding="utf-8") + + # CSS 분리 + css_parts = re.findall(r'', raw, re.DOTALL) + css = "\n".join(css_parts) + html = re.sub(r'', '', raw, flags=re.DOTALL).strip() + + # HTML 주석 제거 + html = re.sub(r'', '', html).strip() + + return html, css + + +def _render_fallback(zone_title: str, info: dict) -> str: + """매칭 안 됐을 때 기본 렌더링. .bul 구조 사용.""" + sub_titles = info.get("sub_titles", []) + content = info.get("content", "") + d1_items = info.get("d1_items", []) + + parts = [] + parts.append(f'
{zone_title}
') + + if d1_items: + for item in d1_items: + if ": " in item: + h, d = item.split(": ", 1) + parts.append(f'
{h}: {d}
') + else: + parts.append(f'
• {item}
') + elif content: + for line in content.split("\n"): + line = line.strip() + if not line or line.startswith("![") or line.startswith("[이미지:"): + continue + d1 = re.match(r'^D1:\s*(.*)', line) + d2 = re.match(r'^D2:\s*(.*)', line) + if d1: + text = re.sub(r'\*+', '', d1.group(1)).strip() + parts.append(f'
{text}
') + elif d2: + text = re.sub(r'\*+', '', d2.group(1)).strip() + parts.append(f'
• {text}
') + + return "\n".join(parts) + + +def _assemble_slide( + templates_dir: Path, + title: str, + conclusion: str, + zone_htmls: list[tuple[str, str]], + zone_csses: list[str], + total_zones: int, +) -> str: + """slide-base.html에 zone들을 조립.""" + from jinja2 import Environment, FileSystemLoader + + # slide-base 로드 + slide_base_path = templates_dir / "blocks" / "slide-base.html" + raw = slide_base_path.read_text(encoding="utf-8") + raw = re.sub(r'', '', raw) + + # body HTML 구성 + body_parts = [] + weight = 1.0 / max(total_zones, 1) + for i, (zone_title, html) in enumerate(zone_htmls): + height_pct = int(weight * 96) + margin = "margin-bottom:2%;" if i < total_zones - 1 else "" + body_parts.append( + f'
' + f'
' + f'{html}
' + ) + + body_html = "\n".join(body_parts) + + # {% block body %} 치환 + raw = raw.replace("{% block body %}{% endblock %}", body_html) + + # 블록 CSS 합치기 + extra_css = "\n".join(zone_csses) + + # Jinja2 렌더 (include 지원) + env = Environment(loader=FileSystemLoader(str(templates_dir))) + template = env.from_string(raw) + result = template.render( + title=title, + footer_text=conclusion, + footer_pill_bg="", + ) + + # 블록 CSS를 head의 첫 앞에 삽입 + if extra_css and '' in result: + result = result.replace('', f'\n{extra_css}\n', 1) + + # body 안에 ', body_part) + if body_styles: + body_part = re.sub(r'', '', body_part) + head_part = head_part.replace('', f'\n{chr(10).join(body_styles)}\n', 1) + result = head_part + body_part + + # asset 임베딩 (svg/ 경로 → base64) + from src.block_assembler import _embed_slide_assets + result = _embed_slide_assets(result, templates_dir) + + return result + + +def _save_step_html(path: Path, title: str, lines: list[str]): + """단계별 디버그 HTML 저장.""" + content = "\n".join(f"
{line}
" for line in lines) + html = f""" + + +
{title}
+{content} +""" + path.write_text(html, encoding="utf-8") + + +def _safe_serialize(obj): + """JSON 직렬화 가능하도록 변환.""" + if isinstance(obj, dict): + return {k: _safe_serialize(v) for k, v in obj.items()} + if isinstance(obj, list): + return [_safe_serialize(v) for v in obj] + if isinstance(obj, (str, int, float, bool, type(None))): + return obj + return str(obj) diff --git a/tests/matching/v4_full32_result.yaml b/tests/matching/v4_full32_result.yaml new file mode 100644 index 0000000..292d5c9 --- /dev/null +++ b/tests/matching/v4_full32_result.yaml @@ -0,0 +1,4888 @@ +meta: + pipeline_step: 8.v4.full32 + description: V4 template-fit 을 V3 Top-K 가 아니라 32 프레임 전체에 적용 + note: 기존 v4_template_fit_r2_result.yaml 는 V3 Top-5 만 평가. 이 파일은 32 전체. + generated_at: '2026-04-29T11:19:26' + answer_map: + 01-2: 18 + 02-2.2: 14 + 03-1: 13 + 03-2: 29 + holdout_sections: + - 01-1 + - 02-1 + - 02-2.1 + - 04-1 + - 04-2.1 + - 04-2.2 +mdx_sections: + 01-2: + mdx_title: 2. 용어간 상호관계 + answer_frame_number: 18 + is_holdout: false + judgments_full32: + - frame_id: '1171281195' + frame_number: 18 + template_id: bim_dx_comparison_table + confidence: 0.9459 + base: 0.9459 + penalty: 0.0 + label: use_as_is + content_embedding: 0.7295 + axes: + anchor: 1.0 + cardinality: 1.0 + relation: 1.0 + slot: 1.0 + content: 0.7295 + v4_full_rank: 1 + - frame_id: '1171281210' + frame_number: 29 + template_id: process_product_two_way + confidence: 0.8675 + base: 0.8675 + penalty: 0.0 + label: reject + content_embedding: 0.6498 + axes: + anchor: 0.75 + cardinality: 1.0 + relation: 1.0 + slot: 1.0 + content: 0.6498 + v4_full_rank: 2 + - frame_id: '1171281204' + frame_number: 24 + template_id: engn_sw_three_types + confidence: 0.7571 + base: 0.7571 + penalty: 0.0 + label: light_edit + content_embedding: 0.6605 + axes: + anchor: 1.0 + cardinality: 0.5 + relation: 1.0 + slot: 0.5 + content: 0.6605 + v4_full_rank: 3 + - frame_id: '1171281203' + frame_number: 23 + template_id: app_sw_package_vs_solution + confidence: 0.6813 + base: 0.6813 + penalty: 0.0 + label: restructure + content_embedding: 0.6567 + axes: + anchor: 0.0 + cardinality: 1.0 + relation: 1.0 + slot: 1.0 + content: 0.6567 + v4_full_rank: 4 + - frame_id: '1171281208' + frame_number: 27 + template_id: bim_adoption_central_split + confidence: 0.6455 + base: 0.8455 + penalty: 0.2 + label: restructure + content_embedding: 0.6442 + axes: + anchor: 0.6667 + cardinality: 1.0 + relation: 1.0 + slot: 1.0 + content: 0.6442 + v4_full_rank: 5 + - frame_id: '1171281212' + frame_number: 31 + template_id: industry_characteristics_three_col + confidence: 0.5781 + base: 0.5781 + penalty: 0.0 + label: reject + content_embedding: 0.599 + axes: + anchor: 0.3333 + cardinality: 0.5 + relation: 1.0 + slot: 0.5 + content: 0.599 + v4_full_rank: 6 + - frame_id: '1171281211' + frame_number: 30 + template_id: industry_current_status_three_col + confidence: 0.5763 + base: 0.5763 + penalty: 0.0 + label: reject + content_embedding: 0.59 + axes: + anchor: 0.3333 + cardinality: 0.5 + relation: 1.0 + slot: 0.5 + content: 0.59 + v4_full_rank: 7 + - frame_id: '1171281178' + frame_number: 7 + template_id: bigroom_system_components + confidence: 0.5352 + base: 0.7352 + penalty: 0.2 + label: reject + content_embedding: 0.6137 + axes: + anchor: 0.25 + cardinality: 1.0 + relation: 1.0 + slot: 1.0 + content: 0.6137 + v4_full_rank: 8 + - frame_id: '1171281206' + frame_number: 26 + template_id: sw_dependency_four_problems + confidence: 0.5107 + base: 0.6107 + penalty: 0.1 + label: reject + content_embedding: 0.5284 + axes: + anchor: 1.0 + cardinality: 0.5 + relation: 0.4 + slot: 0.5 + content: 0.5284 + v4_full_rank: 9 + - frame_id: '1171281175' + frame_number: 4 + template_id: domestic_bim_actor_relations + confidence: 0.5096 + base: 0.5096 + penalty: 0.0 + label: reject + content_embedding: 0.6231 + axes: + anchor: 1.0 + cardinality: 0.0 + relation: 0.3 + slot: 0.5 + content: 0.6231 + v4_full_rank: 10 + - frame_id: '1171281205' + frame_number: 25 + template_id: commercial_sw_four_categories + confidence: 0.46 + base: 0.46 + penalty: 0.0 + label: reject + content_embedding: 0.5874 + axes: + anchor: 0.75 + cardinality: 0.0 + relation: 0.4 + slot: 0.5 + content: 0.5874 + v4_full_rank: 11 + - frame_id: '1171281197' + frame_number: 19 + template_id: design_method_distortion_three_col + confidence: 0.4422 + base: 0.4422 + penalty: 0.0 + label: reject + content_embedding: 0.5858 + axes: + anchor: 0.2 + cardinality: 0.0 + relation: 1.0 + slot: 0.5 + content: 0.5858 + v4_full_rank: 12 + - frame_id: '1171281202' + frame_number: 22 + template_id: model_specialized_engn_sw + confidence: 0.4065 + base: 0.4065 + penalty: 0.0 + label: reject + content_embedding: 0.6326 + axes: + anchor: 0.5 + cardinality: 0.0 + relation: 0.4 + slot: 0.5 + content: 0.6326 + v4_full_rank: 13 + - frame_id: '1171281189' + frame_number: 12 + template_id: construction_goals_three_circle_intersection + confidence: 0.3943 + base: 0.3943 + penalty: 0.0 + label: reject + content_embedding: 0.5713 + axes: + anchor: 0.5 + cardinality: 0.0 + relation: 0.4 + slot: 0.5 + content: 0.5713 + v4_full_rank: 14 + - frame_id: '1171281180' + frame_number: 9 + template_id: pre_construction_model_info_stacked + confidence: 0.3911 + base: 0.4911 + penalty: 0.1 + label: reject + content_embedding: 0.5556 + axes: + anchor: 0.5 + cardinality: 0.5 + relation: 0.4 + slot: 0.5 + content: 0.5556 + v4_full_rank: 15 + - frame_id: '1171281177' + frame_number: 6 + template_id: compensation_complaint_map + confidence: 0.3429 + base: 0.3429 + penalty: 0.0 + label: reject + content_embedding: 0.5147 + axes: + anchor: 0.5 + cardinality: 0.0 + relation: 0.2 + slot: 0.5 + content: 0.5147 + v4_full_rank: 16 + - frame_id: '1171281192' + frame_number: 15 + template_id: policy_goals_plus_execution_requirements + confidence: 0.2649 + base: 0.2649 + penalty: 0.0 + label: reject + content_embedding: 0.5494 + axes: + anchor: 0.0 + cardinality: 0.0 + relation: 0.4 + slot: 0.5 + content: 0.5494 + v4_full_rank: 17 + - frame_id: '1171281209' + frame_number: 28 + template_id: sw_reality_three_emphasis + confidence: 0.259 + base: 0.559 + penalty: 0.3 + label: reject + content_embedding: 0.5826 + axes: + anchor: 0.75 + cardinality: 0.5 + relation: 0.4 + slot: 0.5 + content: 0.5826 + v4_full_rank: 18 + - frame_id: '1171281198' + frame_number: 20 + template_id: dx_sw_necessity_three_perspectives + confidence: 0.2564 + base: 0.4564 + penalty: 0.2 + label: reject + content_embedding: 0.6736 + axes: + anchor: 0.6667 + cardinality: 0.0 + relation: 0.4 + slot: 0.5 + content: 0.6736 + v4_full_rank: 19 + - frame_id: '1171281201' + frame_number: 21 + template_id: solution_engn_split_diagram + confidence: 0.2097 + base: 0.4097 + penalty: 0.2 + label: reject + content_embedding: 0.6484 + axes: + anchor: 0.5 + cardinality: 0.0 + relation: 0.4 + slot: 0.5 + content: 0.6484 + v4_full_rank: 20 + - frame_id: '1171281173' + frame_number: 2 + template_id: engn_sw_development_domain_knowledge + confidence: 0.1755 + base: 0.3755 + penalty: 0.2 + label: reject + content_embedding: 0.6856 + axes: + anchor: 0.3333 + cardinality: 0.0 + relation: 0.4 + slot: 0.5 + content: 0.6856 + v4_full_rank: 21 + - frame_id: '1171281190' + frame_number: 13 + template_id: three_parallel_requirements + confidence: 0.1643 + base: 0.4643 + penalty: 0.3 + label: reject + content_embedding: 0.6296 + axes: + anchor: 0.3333 + cardinality: 0.5 + relation: 0.4 + slot: 0.5 + content: 0.6296 + v4_full_rank: 22 + - frame_id: '1171281194' + frame_number: 17 + template_id: bim_current_problems_paired + confidence: 0.1475 + base: 0.3475 + penalty: 0.2 + label: reject + content_embedding: 0.6502 + axes: + anchor: 0.25 + cardinality: 0.0 + relation: 0.4 + slot: 0.5 + content: 0.6502 + v4_full_rank: 23 + - frame_id: '1171281191' + frame_number: 14 + template_id: three_persona_benefits + confidence: 0.13 + base: 0.33 + penalty: 0.2 + label: reject + content_embedding: 0.5626 + axes: + anchor: 0.25 + cardinality: 0.0 + relation: 0.4 + slot: 0.5 + content: 0.5626 + v4_full_rank: 24 + - frame_id: '1171281193' + frame_number: 16 + template_id: bim_issues_quadrant_four + confidence: 0.1252 + base: 0.3252 + penalty: 0.2 + label: reject + content_embedding: 0.6009 + axes: + anchor: 0.2 + cardinality: 0.0 + relation: 0.4 + slot: 0.5 + content: 0.6009 + v4_full_rank: 25 + - frame_id: '1171281172' + frame_number: 1 + template_id: sw_development_cycle_six_nodes + confidence: 0.1115 + base: 0.3115 + penalty: 0.2 + label: reject + content_embedding: 0.5741 + axes: + anchor: 0.1667 + cardinality: 0.0 + relation: 0.4 + slot: 0.5 + content: 0.5741 + v4_full_rank: 26 + - frame_id: '1171281174' + frame_number: 3 + template_id: overseas_bim_numbered_list + confidence: 0.079 + base: 0.279 + penalty: 0.2 + label: reject + content_embedding: 0.6199 + axes: + anchor: 0.0 + cardinality: 0.0 + relation: 0.4 + slot: 0.5 + content: 0.6199 + v4_full_rank: 27 + - frame_id: '1171281179' + frame_number: 8 + template_id: info_management_what_how_when + confidence: 0.0726 + base: 0.2726 + penalty: 0.2 + label: reject + content_embedding: 0.5881 + axes: + anchor: 0.0 + cardinality: 0.0 + relation: 0.4 + slot: 0.5 + content: 0.5881 + v4_full_rank: 28 + - frame_id: '1171281176' + frame_number: 5 + template_id: compensation_complaint_side_card + confidence: 0.0707 + base: 0.3707 + penalty: 0.3 + label: reject + content_embedding: 0.5784 + axes: + anchor: 0.0 + cardinality: 0.5 + relation: 0.4 + slot: 0.5 + content: 0.5784 + v4_full_rank: 29 + - frame_id: '1171281181' + frame_number: 10 + template_id: field_effectiveness_five_elements + confidence: 0.0707 + base: 0.2707 + penalty: 0.2 + label: reject + content_embedding: 0.5784 + axes: + anchor: 0.0 + cardinality: 0.0 + relation: 0.4 + slot: 0.5 + content: 0.5784 + v4_full_rank: 30 + - frame_id: '1171281213' + frame_number: 32 + template_id: policy_achievement_five_goals + confidence: 0.0683 + base: 0.2683 + penalty: 0.2 + label: reject + content_embedding: 0.5663 + axes: + anchor: 0.0 + cardinality: 0.0 + relation: 0.4 + slot: 0.5 + content: 0.5663 + v4_full_rank: 31 + - frame_id: '1171281182' + frame_number: 11 + template_id: construction_bim_three_usage + confidence: 0.0 + base: 0.2721 + penalty: 0.3 + label: reject + content_embedding: 0.5853 + axes: + anchor: 0.0 + cardinality: 0.0 + relation: 0.4 + slot: 0.5 + content: 0.5853 + v4_full_rank: 32 + usable_count: 4 + reject_count: 28 + 02-2.2: + mdx_title: 2.2 DX 시행 주체별 기대효과 + answer_frame_number: 14 + is_holdout: false + judgments_full32: + - frame_id: '1171281191' + frame_number: 14 + template_id: three_persona_benefits + confidence: 0.939 + base: 0.939 + penalty: 0.0 + label: use_as_is + content_embedding: 0.695 + axes: + anchor: 1.0 + cardinality: 1.0 + relation: 1.0 + slot: 1.0 + content: 0.695 + v4_full_rank: 1 + - frame_id: '1171281198' + frame_number: 20 + template_id: dx_sw_necessity_three_perspectives + confidence: 0.7781 + base: 0.7781 + penalty: 0.0 + label: restructure + content_embedding: 0.5153 + axes: + anchor: 0.5 + cardinality: 1.0 + relation: 1.0 + slot: 1.0 + content: 0.5153 + v4_full_rank: 2 + - frame_id: '1171281190' + frame_number: 13 + template_id: three_parallel_requirements + confidence: 0.7411 + base: 0.7411 + penalty: 0.0 + label: reject + content_embedding: 0.5391 + axes: + anchor: 0.3333 + cardinality: 1.0 + relation: 1.0 + slot: 1.0 + content: 0.5391 + v4_full_rank: 3 + - frame_id: '1171281201' + frame_number: 21 + template_id: solution_engn_split_diagram + confidence: 0.7394 + base: 0.7394 + penalty: 0.0 + label: restructure + content_embedding: 0.522 + axes: + anchor: 0.5 + cardinality: 0.8 + relation: 1.0 + slot: 1.0 + content: 0.522 + v4_full_rank: 4 + - frame_id: '1171281209' + frame_number: 28 + template_id: sw_reality_three_emphasis + confidence: 0.7208 + base: 0.7208 + penalty: 0.0 + label: reject + content_embedding: 0.4374 + axes: + anchor: 0.3333 + cardinality: 1.0 + relation: 1.0 + slot: 1.0 + content: 0.4374 + v4_full_rank: 5 + - frame_id: '1171281182' + frame_number: 11 + template_id: construction_bim_three_usage + confidence: 0.7122 + base: 0.9122 + penalty: 0.2 + label: restructure + content_embedding: 0.5611 + axes: + anchor: 1.0 + cardinality: 1.0 + relation: 1.0 + slot: 1.0 + content: 0.5611 + v4_full_rank: 6 + - frame_id: '1171281189' + frame_number: 12 + template_id: construction_goals_three_circle_intersection + confidence: 0.6748 + base: 0.8748 + penalty: 0.2 + label: restructure + content_embedding: 0.5615 + axes: + anchor: 0.85 + cardinality: 1.0 + relation: 1.0 + slot: 1.0 + content: 0.5615 + v4_full_rank: 7 + - frame_id: '1171281204' + frame_number: 24 + template_id: engn_sw_three_types + confidence: 0.6152 + base: 0.6152 + penalty: 0.0 + label: reject + content_embedding: 0.5094 + axes: + anchor: 0.3333 + cardinality: 1.0 + relation: 0.4 + slot: 1.0 + content: 0.5094 + v4_full_rank: 8 + - frame_id: '1171281206' + frame_number: 26 + template_id: sw_dependency_four_problems + confidence: 0.5327 + base: 0.6327 + penalty: 0.1 + label: reject + content_embedding: 0.4551 + axes: + anchor: 0.6667 + cardinality: 0.5 + relation: 1.0 + slot: 0.5 + content: 0.4551 + v4_full_rank: 9 + - frame_id: '1171281202' + frame_number: 22 + template_id: model_specialized_engn_sw + confidence: 0.5301 + base: 0.7301 + penalty: 0.2 + label: reject + content_embedding: 0.484 + axes: + anchor: 0.3333 + cardinality: 1.0 + relation: 1.0 + slot: 1.0 + content: 0.484 + v4_full_rank: 10 + - frame_id: '1171281176' + frame_number: 5 + template_id: compensation_complaint_side_card + confidence: 0.5231 + base: 0.7231 + penalty: 0.2 + label: reject + content_embedding: 0.4486 + axes: + anchor: 0.3333 + cardinality: 1.0 + relation: 1.0 + slot: 1.0 + content: 0.4486 + v4_full_rank: 11 + - frame_id: '1171281179' + frame_number: 8 + template_id: info_management_what_how_when + confidence: 0.4467 + base: 0.6467 + penalty: 0.2 + label: reject + content_embedding: 0.4837 + axes: + anchor: 0.0 + cardinality: 1.0 + relation: 1.0 + slot: 1.0 + content: 0.4837 + v4_full_rank: 12 + - frame_id: '1171281205' + frame_number: 25 + template_id: commercial_sw_four_categories + confidence: 0.4376 + base: 0.4376 + penalty: 0.0 + label: reject + content_embedding: 0.4561 + axes: + anchor: 0.2857 + cardinality: 0.0 + relation: 1.0 + slot: 0.5 + content: 0.4561 + v4_full_rank: 13 + - frame_id: '1171281197' + frame_number: 19 + template_id: design_method_distortion_three_col + confidence: 0.4117 + base: 0.6117 + penalty: 0.2 + label: reject + content_embedding: 0.492 + axes: + anchor: 0.3333 + cardinality: 1.0 + relation: 0.4 + slot: 1.0 + content: 0.492 + v4_full_rank: 14 + - frame_id: '1171281180' + frame_number: 9 + template_id: pre_construction_model_info_stacked + confidence: 0.3842 + base: 0.6842 + penalty: 0.3 + label: reject + content_embedding: 0.5043 + axes: + anchor: 0.8333 + cardinality: 0.5 + relation: 1.0 + slot: 0.5 + content: 0.5043 + v4_full_rank: 15 + - frame_id: '1171281211' + frame_number: 30 + template_id: industry_current_status_three_col + confidence: 0.3497 + base: 0.5497 + penalty: 0.2 + label: reject + content_embedding: 0.5986 + axes: + anchor: 0.0 + cardinality: 1.0 + relation: 0.4 + slot: 1.0 + content: 0.5986 + v4_full_rank: 16 + - frame_id: '1171281212' + frame_number: 31 + template_id: industry_characteristics_three_col + confidence: 0.3398 + base: 0.5398 + penalty: 0.2 + label: reject + content_embedding: 0.5488 + axes: + anchor: 0.0 + cardinality: 1.0 + relation: 0.4 + slot: 1.0 + content: 0.5488 + v4_full_rank: 17 + - frame_id: '1171281174' + frame_number: 3 + template_id: overseas_bim_numbered_list + confidence: 0.3053 + base: 0.5053 + penalty: 0.2 + label: reject + content_embedding: 0.5266 + axes: + anchor: 0.5 + cardinality: 0.0 + relation: 1.0 + slot: 0.5 + content: 0.5266 + v4_full_rank: 18 + - frame_id: '1171281175' + frame_number: 4 + template_id: domestic_bim_actor_relations + confidence: 0.2512 + base: 0.2512 + penalty: 0.0 + label: reject + content_embedding: 0.5811 + axes: + anchor: 0.0 + cardinality: 0.0 + relation: 0.3 + slot: 0.5 + content: 0.5811 + v4_full_rank: 19 + - frame_id: '1171281208' + frame_number: 27 + template_id: bim_adoption_central_split + confidence: 0.2467 + base: 0.4467 + penalty: 0.2 + label: reject + content_embedding: 0.625 + axes: + anchor: 0.6667 + cardinality: 0.0 + relation: 0.4 + slot: 0.5 + content: 0.625 + v4_full_rank: 20 + - frame_id: '1171281194' + frame_number: 17 + template_id: bim_current_problems_paired + confidence: 0.2362 + base: 0.4362 + penalty: 0.2 + label: reject + content_embedding: 0.4936 + axes: + anchor: 0.25 + cardinality: 0.0 + relation: 1.0 + slot: 0.5 + content: 0.4936 + v4_full_rank: 21 + - frame_id: '1171281172' + frame_number: 1 + template_id: sw_development_cycle_six_nodes + confidence: 0.2261 + base: 0.4261 + penalty: 0.2 + label: reject + content_embedding: 0.5472 + axes: + anchor: 0.1667 + cardinality: 0.0 + relation: 1.0 + slot: 0.5 + content: 0.5472 + v4_full_rank: 22 + - frame_id: '1171281192' + frame_number: 15 + template_id: policy_goals_plus_execution_requirements + confidence: 0.2056 + base: 0.4056 + penalty: 0.2 + label: reject + content_embedding: 0.6531 + axes: + anchor: 0.0 + cardinality: 0.0 + relation: 1.0 + slot: 0.5 + content: 0.6531 + v4_full_rank: 23 + - frame_id: '1171281213' + frame_number: 32 + template_id: policy_achievement_five_goals + confidence: 0.1896 + base: 0.3896 + penalty: 0.2 + label: reject + content_embedding: 0.5732 + axes: + anchor: 0.0 + cardinality: 0.0 + relation: 1.0 + slot: 0.5 + content: 0.5732 + v4_full_rank: 24 + - frame_id: '1171281173' + frame_number: 2 + template_id: engn_sw_development_domain_knowledge + confidence: 0.1831 + base: 0.3831 + penalty: 0.2 + label: reject + content_embedding: 0.5403 + axes: + anchor: 0.0 + cardinality: 0.0 + relation: 1.0 + slot: 0.5 + content: 0.5403 + v4_full_rank: 25 + - frame_id: '1171281181' + frame_number: 10 + template_id: field_effectiveness_five_elements + confidence: 0.18 + base: 0.38 + penalty: 0.2 + label: reject + content_embedding: 0.5251 + axes: + anchor: 0.0 + cardinality: 0.0 + relation: 1.0 + slot: 0.5 + content: 0.5251 + v4_full_rank: 26 + - frame_id: '1171281193' + frame_number: 16 + template_id: bim_issues_quadrant_four + confidence: 0.1739 + base: 0.3739 + penalty: 0.2 + label: reject + content_embedding: 0.4945 + axes: + anchor: 0.0 + cardinality: 0.0 + relation: 1.0 + slot: 0.5 + content: 0.4945 + v4_full_rank: 27 + - frame_id: '1171281210' + frame_number: 29 + template_id: process_product_two_way + confidence: 0.1483 + base: 0.4483 + penalty: 0.3 + label: reject + content_embedding: 0.529 + axes: + anchor: 0.75 + cardinality: 0.0 + relation: 0.4 + slot: 0.5 + content: 0.529 + v4_full_rank: 28 + - frame_id: '1171281195' + frame_number: 18 + template_id: bim_dx_comparison_table + confidence: 0.1132 + base: 0.4632 + penalty: 0.35 + label: reject + content_embedding: 0.5052 + axes: + anchor: 0.4286 + cardinality: 0.5 + relation: 0.4 + slot: 0.5 + content: 0.5052 + v4_full_rank: 29 + - frame_id: '1171281177' + frame_number: 6 + template_id: compensation_complaint_map + confidence: 0.0749 + base: 0.2749 + penalty: 0.2 + label: reject + content_embedding: 0.4868 + axes: + anchor: 0.25 + cardinality: 0.0 + relation: 0.2 + slot: 0.5 + content: 0.4868 + v4_full_rank: 30 + - frame_id: '1171281178' + frame_number: 7 + template_id: bigroom_system_components + confidence: 0.0705 + base: 0.4205 + penalty: 0.35 + label: reject + content_embedding: 0.5149 + axes: + anchor: 0.25 + cardinality: 0.5 + relation: 0.4 + slot: 0.5 + content: 0.5149 + v4_full_rank: 31 + - frame_id: '1171281203' + frame_number: 23 + template_id: app_sw_package_vs_solution + confidence: 0.0043 + base: 0.3543 + penalty: 0.35 + label: reject + content_embedding: 0.4964 + axes: + anchor: 0.0 + cardinality: 0.5 + relation: 0.4 + slot: 0.5 + content: 0.4964 + v4_full_rank: 32 + usable_count: 5 + reject_count: 27 + 03-1: + mdx_title: 1. DX 시행을 위한 필수 요건 + answer_frame_number: 13 + is_holdout: false + judgments_full32: + - frame_id: '1171281190' + frame_number: 13 + template_id: three_parallel_requirements + confidence: 0.9268 + base: 0.9268 + penalty: 0.0 + label: use_as_is + content_embedding: 0.634 + axes: + anchor: 1.0 + cardinality: 1.0 + relation: 1.0 + slot: 1.0 + content: 0.634 + v4_full_rank: 1 + - frame_id: '1171281198' + frame_number: 20 + template_id: dx_sw_necessity_three_perspectives + confidence: 0.8413 + base: 0.8413 + penalty: 0.0 + label: light_edit + content_embedding: 0.5188 + axes: + anchor: 0.75 + cardinality: 1.0 + relation: 1.0 + slot: 1.0 + content: 0.5188 + v4_full_rank: 2 + - frame_id: '1171281209' + frame_number: 28 + template_id: sw_reality_three_emphasis + confidence: 0.7203 + base: 0.7203 + penalty: 0.0 + label: reject + content_embedding: 0.4351 + axes: + anchor: 0.3333 + cardinality: 1.0 + relation: 1.0 + slot: 1.0 + content: 0.4351 + v4_full_rank: 3 + - frame_id: '1171281201' + frame_number: 21 + template_id: solution_engn_split_diagram + confidence: 0.6631 + base: 0.6631 + penalty: 0.0 + label: reject + content_embedding: 0.4531 + axes: + anchor: 0.25 + cardinality: 0.8 + relation: 1.0 + slot: 1.0 + content: 0.4531 + v4_full_rank: 4 + - frame_id: '1171281191' + frame_number: 14 + template_id: three_persona_benefits + confidence: 0.6548 + base: 0.6548 + penalty: 0.0 + label: reject + content_embedding: 0.5238 + axes: + anchor: 0.0 + cardinality: 1.0 + relation: 1.0 + slot: 1.0 + content: 0.5238 + v4_full_rank: 5 + - frame_id: '1171281211' + frame_number: 30 + template_id: industry_current_status_three_col + confidence: 0.6048 + base: 0.6048 + penalty: 0.0 + label: reject + content_embedding: 0.4571 + axes: + anchor: 0.3333 + cardinality: 1.0 + relation: 0.4 + slot: 1.0 + content: 0.4571 + v4_full_rank: 6 + - frame_id: '1171281204' + frame_number: 24 + template_id: engn_sw_three_types + confidence: 0.6041 + base: 0.6041 + penalty: 0.0 + label: reject + content_embedding: 0.4539 + axes: + anchor: 0.3333 + cardinality: 1.0 + relation: 0.4 + slot: 1.0 + content: 0.4539 + v4_full_rank: 7 + - frame_id: '1171281212' + frame_number: 31 + template_id: industry_characteristics_three_col + confidence: 0.6037 + base: 0.6037 + penalty: 0.0 + label: reject + content_embedding: 0.452 + axes: + anchor: 0.3333 + cardinality: 1.0 + relation: 0.4 + slot: 1.0 + content: 0.452 + v4_full_rank: 8 + - frame_id: '1171281197' + frame_number: 19 + template_id: design_method_distortion_three_col + confidence: 0.5975 + base: 0.5975 + penalty: 0.0 + label: reject + content_embedding: 0.4209 + axes: + anchor: 0.3333 + cardinality: 1.0 + relation: 0.4 + slot: 1.0 + content: 0.4209 + v4_full_rank: 9 + - frame_id: '1171281202' + frame_number: 22 + template_id: model_specialized_engn_sw + confidence: 0.4986 + base: 0.6986 + penalty: 0.2 + label: reject + content_embedding: 0.4306 + axes: + anchor: 0.25 + cardinality: 1.0 + relation: 1.0 + slot: 1.0 + content: 0.4306 + v4_full_rank: 10 + - frame_id: '1171281182' + frame_number: 11 + template_id: construction_bim_three_usage + confidence: 0.4479 + base: 0.6479 + penalty: 0.2 + label: reject + content_embedding: 0.4894 + axes: + anchor: 0.0 + cardinality: 1.0 + relation: 1.0 + slot: 1.0 + content: 0.4894 + v4_full_rank: 11 + - frame_id: '1171281206' + frame_number: 26 + template_id: sw_dependency_four_problems + confidence: 0.4453 + base: 0.5453 + penalty: 0.1 + label: reject + content_embedding: 0.435 + axes: + anchor: 0.3333 + cardinality: 0.5 + relation: 1.0 + slot: 0.5 + content: 0.435 + v4_full_rank: 12 + - frame_id: '1171281189' + frame_number: 12 + template_id: construction_goals_three_circle_intersection + confidence: 0.4419 + base: 0.6419 + penalty: 0.2 + label: reject + content_embedding: 0.4594 + axes: + anchor: 0.0 + cardinality: 1.0 + relation: 1.0 + slot: 1.0 + content: 0.4594 + v4_full_rank: 13 + - frame_id: '1171281179' + frame_number: 8 + template_id: info_management_what_how_when + confidence: 0.4363 + base: 0.6363 + penalty: 0.2 + label: reject + content_embedding: 0.4315 + axes: + anchor: 0.0 + cardinality: 1.0 + relation: 1.0 + slot: 1.0 + content: 0.4315 + v4_full_rank: 14 + - frame_id: '1171281176' + frame_number: 5 + template_id: compensation_complaint_side_card + confidence: 0.4277 + base: 0.6277 + penalty: 0.2 + label: reject + content_embedding: 0.3885 + axes: + anchor: 0.0 + cardinality: 1.0 + relation: 1.0 + slot: 1.0 + content: 0.3885 + v4_full_rank: 15 + - frame_id: '1171281205' + frame_number: 25 + template_id: commercial_sw_four_categories + confidence: 0.4246 + base: 0.4246 + penalty: 0.0 + label: reject + content_embedding: 0.4355 + axes: + anchor: 0.25 + cardinality: 0.0 + relation: 1.0 + slot: 0.5 + content: 0.4355 + v4_full_rank: 16 + - frame_id: '1171281174' + frame_number: 3 + template_id: overseas_bim_numbered_list + confidence: 0.3008 + base: 0.5008 + penalty: 0.2 + label: reject + content_embedding: 0.5038 + axes: + anchor: 0.5 + cardinality: 0.0 + relation: 1.0 + slot: 0.5 + content: 0.5038 + v4_full_rank: 17 + - frame_id: '1171281192' + frame_number: 15 + template_id: policy_goals_plus_execution_requirements + confidence: 0.2359 + base: 0.4359 + penalty: 0.2 + label: reject + content_embedding: 0.5959 + axes: + anchor: 0.1667 + cardinality: 0.0 + relation: 1.0 + slot: 0.5 + content: 0.5959 + v4_full_rank: 18 + - frame_id: '1171281208' + frame_number: 27 + template_id: bim_adoption_central_split + confidence: 0.2132 + base: 0.4132 + penalty: 0.2 + label: reject + content_embedding: 0.4577 + axes: + anchor: 0.6667 + cardinality: 0.0 + relation: 0.4 + slot: 0.5 + content: 0.4577 + v4_full_rank: 19 + - frame_id: '1171281194' + frame_number: 17 + template_id: bim_current_problems_paired + confidence: 0.2097 + base: 0.4097 + penalty: 0.2 + label: reject + content_embedding: 0.4236 + axes: + anchor: 0.2 + cardinality: 0.0 + relation: 1.0 + slot: 0.5 + content: 0.4236 + v4_full_rank: 20 + - frame_id: '1171281173' + frame_number: 2 + template_id: engn_sw_development_domain_knowledge + confidence: 0.1891 + base: 0.3891 + penalty: 0.2 + label: reject + content_embedding: 0.5705 + axes: + anchor: 0.0 + cardinality: 0.0 + relation: 1.0 + slot: 0.5 + content: 0.5705 + v4_full_rank: 21 + - frame_id: '1171281181' + frame_number: 10 + template_id: field_effectiveness_five_elements + confidence: 0.1881 + base: 0.3881 + penalty: 0.2 + label: reject + content_embedding: 0.5653 + axes: + anchor: 0.0 + cardinality: 0.0 + relation: 1.0 + slot: 0.5 + content: 0.5653 + v4_full_rank: 22 + - frame_id: '1171281213' + frame_number: 32 + template_id: policy_achievement_five_goals + confidence: 0.1813 + base: 0.3813 + penalty: 0.2 + label: reject + content_embedding: 0.5313 + axes: + anchor: 0.0 + cardinality: 0.0 + relation: 1.0 + slot: 0.5 + content: 0.5313 + v4_full_rank: 23 + - frame_id: '1171281180' + frame_number: 9 + template_id: pre_construction_model_info_stacked + confidence: 0.1715 + base: 0.4715 + penalty: 0.3 + label: reject + content_embedding: 0.4826 + axes: + anchor: 0.0 + cardinality: 0.5 + relation: 1.0 + slot: 0.5 + content: 0.4826 + v4_full_rank: 24 + - frame_id: '1171281172' + frame_number: 1 + template_id: sw_development_cycle_six_nodes + confidence: 0.1694 + base: 0.3694 + penalty: 0.2 + label: reject + content_embedding: 0.4719 + axes: + anchor: 0.0 + cardinality: 0.0 + relation: 1.0 + slot: 0.5 + content: 0.4719 + v4_full_rank: 25 + - frame_id: '1171281193' + frame_number: 16 + template_id: bim_issues_quadrant_four + confidence: 0.1676 + base: 0.3676 + penalty: 0.2 + label: reject + content_embedding: 0.4632 + axes: + anchor: 0.0 + cardinality: 0.0 + relation: 1.0 + slot: 0.5 + content: 0.4632 + v4_full_rank: 26 + - frame_id: '1171281210' + frame_number: 29 + template_id: process_product_two_way + confidence: 0.0972 + base: 0.2972 + penalty: 0.2 + label: reject + content_embedding: 0.3985 + axes: + anchor: 0.25 + cardinality: 0.0 + relation: 0.4 + slot: 0.5 + content: 0.3985 + v4_full_rank: 27 + - frame_id: '1171281178' + frame_number: 7 + template_id: bigroom_system_components + confidence: 0.0576 + base: 0.4076 + penalty: 0.35 + label: reject + content_embedding: 0.4506 + axes: + anchor: 0.25 + cardinality: 0.5 + relation: 0.4 + slot: 0.5 + content: 0.4506 + v4_full_rank: 28 + - frame_id: '1171281195' + frame_number: 18 + template_id: bim_dx_comparison_table + confidence: 0.0489 + base: 0.3989 + penalty: 0.35 + label: reject + content_embedding: 0.3626 + axes: + anchor: 0.2857 + cardinality: 0.5 + relation: 0.4 + slot: 0.5 + content: 0.3626 + v4_full_rank: 29 + - frame_id: '1171281175' + frame_number: 4 + template_id: domestic_bim_actor_relations + confidence: 0.0253 + base: 0.2253 + penalty: 0.2 + label: reject + content_embedding: 0.4513 + axes: + anchor: 0.0 + cardinality: 0.0 + relation: 0.3 + slot: 0.5 + content: 0.4513 + v4_full_rank: 30 + - frame_id: '1171281177' + frame_number: 6 + template_id: compensation_complaint_map + confidence: 0.0 + base: 0.1937 + penalty: 0.2 + label: reject + content_embedding: 0.3937 + axes: + anchor: 0.0 + cardinality: 0.0 + relation: 0.2 + slot: 0.5 + content: 0.3937 + v4_full_rank: 31 + - frame_id: '1171281203' + frame_number: 23 + template_id: app_sw_package_vs_solution + confidence: 0.0 + base: 0.3297 + penalty: 0.35 + label: reject + content_embedding: 0.3736 + axes: + anchor: 0.0 + cardinality: 0.5 + relation: 0.4 + slot: 0.5 + content: 0.3736 + v4_full_rank: 32 + usable_count: 2 + reject_count: 30 + 03-2: + mdx_title: 2. Process의 혁신과 Product의 변화 + answer_frame_number: 29 + is_holdout: false + judgments_full32: + - frame_id: '1171281210' + frame_number: 29 + template_id: process_product_two_way + confidence: 0.9198 + base: 0.9198 + penalty: 0.0 + label: use_as_is + content_embedding: 0.5991 + axes: + anchor: 1.0 + cardinality: 1.0 + relation: 1.0 + slot: 1.0 + content: 0.5991 + v4_full_rank: 1 + - frame_id: '1171281195' + frame_number: 18 + template_id: bim_dx_comparison_table + confidence: 0.8138 + base: 0.8138 + penalty: 0.0 + label: reject + content_embedding: 0.4439 + axes: + anchor: 0.7 + cardinality: 1.0 + relation: 1.0 + slot: 1.0 + content: 0.4439 + v4_full_rank: 2 + - frame_id: '1171281203' + frame_number: 23 + template_id: app_sw_package_vs_solution + confidence: 0.6467 + base: 0.6467 + penalty: 0.0 + label: reject + content_embedding: 0.4835 + axes: + anchor: 0.0 + cardinality: 1.0 + relation: 1.0 + slot: 1.0 + content: 0.4835 + v4_full_rank: 3 + - frame_id: '1171281204' + frame_number: 24 + template_id: engn_sw_three_types + confidence: 0.6396 + base: 0.6396 + penalty: 0.0 + label: reject + content_embedding: 0.4897 + axes: + anchor: 0.6667 + cardinality: 0.5 + relation: 1.0 + slot: 0.5 + content: 0.4897 + v4_full_rank: 4 + - frame_id: '1171281208' + frame_number: 27 + template_id: bim_adoption_central_split + confidence: 0.6278 + base: 0.8278 + penalty: 0.2 + label: restructure + content_embedding: 0.5555 + axes: + anchor: 0.6667 + cardinality: 1.0 + relation: 1.0 + slot: 1.0 + content: 0.5555 + v4_full_rank: 5 + - frame_id: '1171281211' + frame_number: 30 + template_id: industry_current_status_three_col + confidence: 0.5324 + base: 0.5324 + penalty: 0.0 + label: reject + content_embedding: 0.4745 + axes: + anchor: 0.25 + cardinality: 0.5 + relation: 1.0 + slot: 0.5 + content: 0.4745 + v4_full_rank: 6 + - frame_id: '1171281178' + frame_number: 7 + template_id: bigroom_system_components + confidence: 0.5054 + base: 0.7054 + penalty: 0.2 + label: reject + content_embedding: 0.4643 + axes: + anchor: 0.25 + cardinality: 1.0 + relation: 1.0 + slot: 1.0 + content: 0.4643 + v4_full_rank: 7 + - frame_id: '1171281212' + frame_number: 31 + template_id: industry_characteristics_three_col + confidence: 0.4757 + base: 0.4757 + penalty: 0.0 + label: reject + content_embedding: 0.5033 + axes: + anchor: 0.0 + cardinality: 0.5 + relation: 1.0 + slot: 0.5 + content: 0.5033 + v4_full_rank: 8 + - frame_id: '1171281180' + frame_number: 9 + template_id: pre_construction_model_info_stacked + confidence: 0.4428 + base: 0.5428 + penalty: 0.1 + label: reject + content_embedding: 0.3975 + axes: + anchor: 0.8333 + cardinality: 0.5 + relation: 0.4 + slot: 0.5 + content: 0.3975 + v4_full_rank: 9 + - frame_id: '1171281202' + frame_number: 22 + template_id: model_specialized_engn_sw + confidence: 0.3778 + base: 0.3778 + penalty: 0.0 + label: reject + content_embedding: 0.4888 + axes: + anchor: 0.5 + cardinality: 0.0 + relation: 0.4 + slot: 0.5 + content: 0.4888 + v4_full_rank: 10 + - frame_id: '1171281197' + frame_number: 19 + template_id: design_method_distortion_three_col + confidence: 0.3754 + base: 0.3754 + penalty: 0.0 + label: reject + content_embedding: 0.5022 + axes: + anchor: 0.0 + cardinality: 0.0 + relation: 1.0 + slot: 0.5 + content: 0.5022 + v4_full_rank: 11 + - frame_id: '1171281205' + frame_number: 25 + template_id: commercial_sw_four_categories + confidence: 0.3651 + base: 0.3651 + penalty: 0.0 + label: reject + content_embedding: 0.4253 + axes: + anchor: 0.5 + cardinality: 0.0 + relation: 0.4 + slot: 0.5 + content: 0.4253 + v4_full_rank: 12 + - frame_id: '1171281206' + frame_number: 26 + template_id: sw_dependency_four_problems + confidence: 0.3196 + base: 0.4196 + penalty: 0.1 + label: reject + content_embedding: 0.4064 + axes: + anchor: 0.3333 + cardinality: 0.5 + relation: 0.4 + slot: 0.5 + content: 0.4064 + v4_full_rank: 13 + - frame_id: '1171281194' + frame_number: 17 + template_id: bim_current_problems_paired + confidence: 0.3126 + base: 0.3126 + penalty: 0.0 + label: reject + content_embedding: 0.4754 + axes: + anchor: 0.25 + cardinality: 0.0 + relation: 0.4 + slot: 0.5 + content: 0.4754 + v4_full_rank: 14 + - frame_id: '1171281193' + frame_number: 16 + template_id: bim_issues_quadrant_four + confidence: 0.2926 + base: 0.4926 + penalty: 0.2 + label: reject + content_embedding: 0.438 + axes: + anchor: 1.0 + cardinality: 0.0 + relation: 0.4 + slot: 0.5 + content: 0.438 + v4_full_rank: 15 + - frame_id: '1171281175' + frame_number: 4 + template_id: domestic_bim_actor_relations + confidence: 0.279 + base: 0.279 + penalty: 0.0 + label: reject + content_embedding: 0.4074 + axes: + anchor: 0.25 + cardinality: 0.0 + relation: 0.3 + slot: 0.5 + content: 0.4074 + v4_full_rank: 16 + - frame_id: '1171281192' + frame_number: 15 + template_id: policy_goals_plus_execution_requirements + confidence: 0.2469 + base: 0.2469 + penalty: 0.0 + label: reject + content_embedding: 0.4593 + axes: + anchor: 0.0 + cardinality: 0.0 + relation: 0.4 + slot: 0.5 + content: 0.4593 + v4_full_rank: 17 + - frame_id: '1171281201' + frame_number: 21 + template_id: solution_engn_split_diagram + confidence: 0.2452 + base: 0.4452 + penalty: 0.2 + label: reject + content_embedding: 0.5134 + axes: + anchor: 0.75 + cardinality: 0.0 + relation: 0.4 + slot: 0.5 + content: 0.5134 + v4_full_rank: 18 + - frame_id: '1171281189' + frame_number: 12 + template_id: construction_goals_three_circle_intersection + confidence: 0.2429 + base: 0.2429 + penalty: 0.0 + label: reject + content_embedding: 0.4395 + axes: + anchor: 0.0 + cardinality: 0.0 + relation: 0.4 + slot: 0.5 + content: 0.4395 + v4_full_rank: 19 + - frame_id: '1171281198' + frame_number: 20 + template_id: dx_sw_necessity_three_perspectives + confidence: 0.2143 + base: 0.4143 + penalty: 0.2 + label: reject + content_embedding: 0.4634 + axes: + anchor: 0.6667 + cardinality: 0.0 + relation: 0.4 + slot: 0.5 + content: 0.4634 + v4_full_rank: 20 + - frame_id: '1171281191' + frame_number: 14 + template_id: three_persona_benefits + confidence: 0.1885 + base: 0.3885 + penalty: 0.2 + label: reject + content_embedding: 0.4174 + axes: + anchor: 0.6 + cardinality: 0.0 + relation: 0.4 + slot: 0.5 + content: 0.4174 + v4_full_rank: 21 + - frame_id: '1171281177' + frame_number: 6 + template_id: compensation_complaint_map + confidence: 0.1867 + base: 0.1867 + penalty: 0.0 + label: reject + content_embedding: 0.3587 + axes: + anchor: 0.0 + cardinality: 0.0 + relation: 0.2 + slot: 0.5 + content: 0.3587 + v4_full_rank: 22 + - frame_id: '1171281174' + frame_number: 3 + template_id: overseas_bim_numbered_list + confidence: 0.1701 + base: 0.3701 + penalty: 0.2 + label: reject + content_embedding: 0.4507 + axes: + anchor: 0.5 + cardinality: 0.0 + relation: 0.4 + slot: 0.5 + content: 0.4507 + v4_full_rank: 23 + - frame_id: '1171281209' + frame_number: 28 + template_id: sw_reality_three_emphasis + confidence: 0.1626 + base: 0.4626 + penalty: 0.3 + label: reject + content_embedding: 0.4128 + axes: + anchor: 0.5 + cardinality: 0.5 + relation: 0.4 + slot: 0.5 + content: 0.4128 + v4_full_rank: 24 + - frame_id: '1171281176' + frame_number: 5 + template_id: compensation_complaint_side_card + confidence: 0.121 + base: 0.421 + penalty: 0.3 + label: reject + content_embedding: 0.4132 + axes: + anchor: 0.3333 + cardinality: 0.5 + relation: 0.4 + slot: 0.5 + content: 0.4132 + v4_full_rank: 25 + - frame_id: '1171281172' + frame_number: 1 + template_id: sw_development_cycle_six_nodes + confidence: 0.1127 + base: 0.3127 + penalty: 0.2 + label: reject + content_embedding: 0.58 + axes: + anchor: 0.1667 + cardinality: 0.0 + relation: 0.4 + slot: 0.5 + content: 0.58 + v4_full_rank: 26 + - frame_id: '1171281190' + frame_number: 13 + template_id: three_parallel_requirements + confidence: 0.0943 + base: 0.3943 + penalty: 0.3 + label: reject + content_embedding: 0.3839 + axes: + anchor: 0.25 + cardinality: 0.5 + relation: 0.4 + slot: 0.5 + content: 0.3839 + v4_full_rank: 27 + - frame_id: '1171281173' + frame_number: 2 + template_id: engn_sw_development_domain_knowledge + confidence: 0.0635 + base: 0.2635 + penalty: 0.2 + label: reject + content_embedding: 0.5423 + axes: + anchor: 0.0 + cardinality: 0.0 + relation: 0.4 + slot: 0.5 + content: 0.5423 + v4_full_rank: 28 + - frame_id: '1171281179' + frame_number: 8 + template_id: info_management_what_how_when + confidence: 0.0516 + base: 0.2516 + penalty: 0.2 + label: reject + content_embedding: 0.4832 + axes: + anchor: 0.0 + cardinality: 0.0 + relation: 0.4 + slot: 0.5 + content: 0.4832 + v4_full_rank: 29 + - frame_id: '1171281213' + frame_number: 32 + template_id: policy_achievement_five_goals + confidence: 0.0515 + base: 0.2515 + penalty: 0.2 + label: reject + content_embedding: 0.4823 + axes: + anchor: 0.0 + cardinality: 0.0 + relation: 0.4 + slot: 0.5 + content: 0.4823 + v4_full_rank: 30 + - frame_id: '1171281181' + frame_number: 10 + template_id: field_effectiveness_five_elements + confidence: 0.0454 + base: 0.2454 + penalty: 0.2 + label: reject + content_embedding: 0.4518 + axes: + anchor: 0.0 + cardinality: 0.0 + relation: 0.4 + slot: 0.5 + content: 0.4518 + v4_full_rank: 31 + - frame_id: '1171281182' + frame_number: 11 + template_id: construction_bim_three_usage + confidence: 0.0 + base: 0.248 + penalty: 0.3 + label: reject + content_embedding: 0.465 + axes: + anchor: 0.0 + cardinality: 0.0 + relation: 0.4 + slot: 0.5 + content: 0.465 + v4_full_rank: 32 + usable_count: 2 + reject_count: 30 + 01-1: + mdx_title: 1. 용어 정의 + answer_frame_number: null + is_holdout: true + judgments_full32: + - frame_id: '1171281182' + frame_number: 11 + template_id: construction_bim_three_usage + confidence: 0.9101 + base: 0.9101 + penalty: 0.0 + label: use_as_is + content_embedding: 0.5507 + axes: + anchor: 1.0 + cardinality: 1.0 + relation: 1.0 + slot: 1.0 + content: 0.5507 + v4_full_rank: 1 + - frame_id: '1171281189' + frame_number: 12 + template_id: construction_goals_three_circle_intersection + confidence: 0.8261 + base: 0.8261 + penalty: 0.0 + label: light_edit + content_embedding: 0.547 + axes: + anchor: 0.6667 + cardinality: 1.0 + relation: 1.0 + slot: 1.0 + content: 0.547 + v4_full_rank: 2 + - frame_id: '1171281198' + frame_number: 20 + template_id: dx_sw_necessity_three_perspectives + confidence: 0.8168 + base: 0.8168 + penalty: 0.0 + label: light_edit + content_embedding: 0.5005 + axes: + anchor: 0.6667 + cardinality: 1.0 + relation: 1.0 + slot: 1.0 + content: 0.5005 + v4_full_rank: 3 + - frame_id: '1171281202' + frame_number: 22 + template_id: model_specialized_engn_sw + confidence: 0.749 + base: 0.749 + penalty: 0.0 + label: restructure + content_embedding: 0.5782 + axes: + anchor: 0.3333 + cardinality: 1.0 + relation: 1.0 + slot: 1.0 + content: 0.5782 + v4_full_rank: 4 + - frame_id: '1171281190' + frame_number: 13 + template_id: three_parallel_requirements + confidence: 0.7402 + base: 0.7402 + penalty: 0.0 + label: reject + content_embedding: 0.5342 + axes: + anchor: 0.3333 + cardinality: 1.0 + relation: 1.0 + slot: 1.0 + content: 0.5342 + v4_full_rank: 5 + - frame_id: '1171281179' + frame_number: 8 + template_id: info_management_what_how_when + confidence: 0.7395 + base: 0.7395 + penalty: 0.0 + label: reject + content_embedding: 0.5308 + axes: + anchor: 0.3333 + cardinality: 1.0 + relation: 1.0 + slot: 1.0 + content: 0.5308 + v4_full_rank: 6 + - frame_id: '1171281191' + frame_number: 14 + template_id: three_persona_benefits + confidence: 0.6973 + base: 0.6973 + penalty: 0.0 + label: reject + content_embedding: 0.4864 + axes: + anchor: 0.2 + cardinality: 1.0 + relation: 1.0 + slot: 1.0 + content: 0.4864 + v4_full_rank: 7 + - frame_id: '1171281201' + frame_number: 21 + template_id: solution_engn_split_diagram + confidence: 0.6865 + base: 0.6865 + penalty: 0.0 + label: restructure + content_embedding: 0.57 + axes: + anchor: 0.25 + cardinality: 0.8 + relation: 1.0 + slot: 1.0 + content: 0.57 + v4_full_rank: 8 + - frame_id: '1171281176' + frame_number: 5 + template_id: compensation_complaint_side_card + confidence: 0.6574 + base: 0.6574 + penalty: 0.0 + label: reject + content_embedding: 0.5372 + axes: + anchor: 0.0 + cardinality: 1.0 + relation: 1.0 + slot: 1.0 + content: 0.5372 + v4_full_rank: 9 + - frame_id: '1171281209' + frame_number: 28 + template_id: sw_reality_three_emphasis + confidence: 0.6568 + base: 0.6568 + penalty: 0.0 + label: reject + content_embedding: 0.5338 + axes: + anchor: 0.0 + cardinality: 1.0 + relation: 1.0 + slot: 1.0 + content: 0.5338 + v4_full_rank: 10 + - frame_id: '1171281212' + frame_number: 31 + template_id: industry_characteristics_three_col + confidence: 0.5467 + base: 0.5467 + penalty: 0.0 + label: reject + content_embedding: 0.5833 + axes: + anchor: 0.0 + cardinality: 1.0 + relation: 0.4 + slot: 1.0 + content: 0.5833 + v4_full_rank: 11 + - frame_id: '1171281204' + frame_number: 24 + template_id: engn_sw_three_types + confidence: 0.5424 + base: 0.5424 + penalty: 0.0 + label: reject + content_embedding: 0.5622 + axes: + anchor: 0.0 + cardinality: 1.0 + relation: 0.4 + slot: 1.0 + content: 0.5622 + v4_full_rank: 12 + - frame_id: '1171281211' + frame_number: 30 + template_id: industry_current_status_three_col + confidence: 0.5407 + base: 0.5407 + penalty: 0.0 + label: reject + content_embedding: 0.5536 + axes: + anchor: 0.0 + cardinality: 1.0 + relation: 0.4 + slot: 1.0 + content: 0.5536 + v4_full_rank: 13 + - frame_id: '1171281197' + frame_number: 19 + template_id: design_method_distortion_three_col + confidence: 0.5287 + base: 0.5287 + penalty: 0.0 + label: reject + content_embedding: 0.4936 + axes: + anchor: 0.0 + cardinality: 1.0 + relation: 0.4 + slot: 1.0 + content: 0.4936 + v4_full_rank: 14 + - frame_id: '1171281174' + frame_number: 3 + template_id: overseas_bim_numbered_list + confidence: 0.5125 + base: 0.5125 + penalty: 0.0 + label: reject + content_embedding: 0.5625 + axes: + anchor: 0.5 + cardinality: 0.0 + relation: 1.0 + slot: 0.5 + content: 0.5625 + v4_full_rank: 15 + - frame_id: '1171281180' + frame_number: 9 + template_id: pre_construction_model_info_stacked + confidence: 0.4787 + base: 0.5787 + penalty: 0.1 + label: reject + content_embedding: 0.6017 + axes: + anchor: 0.3333 + cardinality: 0.5 + relation: 1.0 + slot: 0.5 + content: 0.6017 + v4_full_rank: 16 + - frame_id: '1171281172' + frame_number: 1 + template_id: sw_development_cycle_six_nodes + confidence: 0.424 + base: 0.424 + penalty: 0.0 + label: reject + content_embedding: 0.5368 + axes: + anchor: 0.1667 + cardinality: 0.0 + relation: 1.0 + slot: 0.5 + content: 0.5368 + v4_full_rank: 17 + - frame_id: '1171281205' + frame_number: 25 + template_id: commercial_sw_four_categories + confidence: 0.3897 + base: 0.3897 + penalty: 0.0 + label: reject + content_embedding: 0.5737 + axes: + anchor: 0.0 + cardinality: 0.0 + relation: 1.0 + slot: 0.5 + content: 0.5737 + v4_full_rank: 18 + - frame_id: '1171281173' + frame_number: 2 + template_id: engn_sw_development_domain_knowledge + confidence: 0.3885 + base: 0.3885 + penalty: 0.0 + label: reject + content_embedding: 0.5676 + axes: + anchor: 0.0 + cardinality: 0.0 + relation: 1.0 + slot: 0.5 + content: 0.5676 + v4_full_rank: 19 + - frame_id: '1171281194' + frame_number: 17 + template_id: bim_current_problems_paired + confidence: 0.3872 + base: 0.3872 + penalty: 0.0 + label: reject + content_embedding: 0.5612 + axes: + anchor: 0.0 + cardinality: 0.0 + relation: 1.0 + slot: 0.5 + content: 0.5612 + v4_full_rank: 20 + - frame_id: '1171281213' + frame_number: 32 + template_id: policy_achievement_five_goals + confidence: 0.3849 + base: 0.3849 + penalty: 0.0 + label: reject + content_embedding: 0.5495 + axes: + anchor: 0.0 + cardinality: 0.0 + relation: 1.0 + slot: 0.5 + content: 0.5495 + v4_full_rank: 21 + - frame_id: '1171281181' + frame_number: 10 + template_id: field_effectiveness_five_elements + confidence: 0.3844 + base: 0.3844 + penalty: 0.0 + label: reject + content_embedding: 0.5468 + axes: + anchor: 0.0 + cardinality: 0.0 + relation: 1.0 + slot: 0.5 + content: 0.5468 + v4_full_rank: 22 + - frame_id: '1171281193' + frame_number: 16 + template_id: bim_issues_quadrant_four + confidence: 0.3807 + base: 0.3807 + penalty: 0.0 + label: reject + content_embedding: 0.5287 + axes: + anchor: 0.0 + cardinality: 0.0 + relation: 1.0 + slot: 0.5 + content: 0.5287 + v4_full_rank: 23 + - frame_id: '1171281192' + frame_number: 15 + template_id: policy_goals_plus_execution_requirements + confidence: 0.3796 + base: 0.3796 + penalty: 0.0 + label: reject + content_embedding: 0.5228 + axes: + anchor: 0.0 + cardinality: 0.0 + relation: 1.0 + slot: 0.5 + content: 0.5228 + v4_full_rank: 24 + - frame_id: '1171281206' + frame_number: 26 + template_id: sw_dependency_four_problems + confidence: 0.3652 + base: 0.4652 + penalty: 0.1 + label: reject + content_embedding: 0.451 + axes: + anchor: 0.0 + cardinality: 0.5 + relation: 1.0 + slot: 0.5 + content: 0.451 + v4_full_rank: 25 + - frame_id: '1171281175' + frame_number: 4 + template_id: domestic_bim_actor_relations + confidence: 0.312 + base: 0.312 + penalty: 0.0 + label: reject + content_embedding: 0.5726 + axes: + anchor: 0.25 + cardinality: 0.0 + relation: 0.3 + slot: 0.5 + content: 0.5726 + v4_full_rank: 26 + - frame_id: '1171281208' + frame_number: 27 + template_id: bim_adoption_central_split + confidence: 0.2393 + base: 0.4393 + penalty: 0.2 + label: reject + content_embedding: 0.5881 + axes: + anchor: 0.6667 + cardinality: 0.0 + relation: 0.4 + slot: 0.5 + content: 0.5881 + v4_full_rank: 27 + - frame_id: '1171281177' + frame_number: 6 + template_id: compensation_complaint_map + confidence: 0.2216 + base: 0.2216 + penalty: 0.0 + label: reject + content_embedding: 0.5329 + axes: + anchor: 0.0 + cardinality: 0.0 + relation: 0.2 + slot: 0.5 + content: 0.5329 + v4_full_rank: 28 + - frame_id: '1171281178' + frame_number: 7 + template_id: bigroom_system_components + confidence: 0.2187 + base: 0.3687 + penalty: 0.15 + label: reject + content_embedding: 0.5685 + axes: + anchor: 0.0 + cardinality: 0.5 + relation: 0.4 + slot: 0.5 + content: 0.5685 + v4_full_rank: 29 + - frame_id: '1171281195' + frame_number: 18 + template_id: bim_dx_comparison_table + confidence: 0.1881 + base: 0.5381 + penalty: 0.35 + label: reject + content_embedding: 0.5405 + axes: + anchor: 0.7 + cardinality: 0.5 + relation: 0.4 + slot: 0.5 + content: 0.5405 + v4_full_rank: 30 + - frame_id: '1171281210' + frame_number: 29 + template_id: process_product_two_way + confidence: 0.1837 + base: 0.3837 + penalty: 0.2 + label: reject + content_embedding: 0.5185 + axes: + anchor: 0.5 + cardinality: 0.0 + relation: 0.4 + slot: 0.5 + content: 0.5185 + v4_full_rank: 31 + - frame_id: '1171281203' + frame_number: 23 + template_id: app_sw_package_vs_solution + confidence: 0.0059 + base: 0.3559 + penalty: 0.35 + label: reject + content_embedding: 0.5047 + axes: + anchor: 0.0 + cardinality: 0.5 + relation: 0.4 + slot: 0.5 + content: 0.5047 + v4_full_rank: 32 + usable_count: 5 + reject_count: 27 + 02-1: + mdx_title: 1. DX의 궁극적 목표 + answer_frame_number: null + is_holdout: true + judgments_full32: + - frame_id: '1171281189' + frame_number: 12 + template_id: construction_goals_three_circle_intersection + confidence: 0.914 + base: 0.914 + penalty: 0.0 + label: use_as_is + content_embedding: 0.57 + axes: + anchor: 1.0 + cardinality: 1.0 + relation: 1.0 + slot: 1.0 + content: 0.57 + v4_full_rank: 1 + - frame_id: '1171281191' + frame_number: 14 + template_id: three_persona_benefits + confidence: 0.7656 + base: 0.7656 + penalty: 0.0 + label: reject + content_embedding: 0.5779 + axes: + anchor: 0.4 + cardinality: 1.0 + relation: 1.0 + slot: 1.0 + content: 0.5779 + v4_full_rank: 2 + - frame_id: '1171281198' + frame_number: 20 + template_id: dx_sw_necessity_three_perspectives + confidence: 0.7512 + base: 0.7512 + penalty: 0.0 + label: reject + content_embedding: 0.5895 + axes: + anchor: 0.3333 + cardinality: 1.0 + relation: 1.0 + slot: 1.0 + content: 0.5895 + v4_full_rank: 3 + - frame_id: '1171281202' + frame_number: 22 + template_id: model_specialized_engn_sw + confidence: 0.7267 + base: 0.7267 + penalty: 0.0 + label: reject + content_embedding: 0.4667 + axes: + anchor: 0.3333 + cardinality: 1.0 + relation: 1.0 + slot: 1.0 + content: 0.4667 + v4_full_rank: 4 + - frame_id: '1171281201' + frame_number: 21 + template_id: solution_engn_split_diagram + confidence: 0.724 + base: 0.724 + penalty: 0.0 + label: restructure + content_embedding: 0.445 + axes: + anchor: 0.5 + cardinality: 0.8 + relation: 1.0 + slot: 1.0 + content: 0.445 + v4_full_rank: 5 + - frame_id: '1171281190' + frame_number: 13 + template_id: three_parallel_requirements + confidence: 0.6517 + base: 0.6517 + penalty: 0.0 + label: reject + content_embedding: 0.5085 + axes: + anchor: 0.0 + cardinality: 1.0 + relation: 1.0 + slot: 1.0 + content: 0.5085 + v4_full_rank: 6 + - frame_id: '1171281179' + frame_number: 8 + template_id: info_management_what_how_when + confidence: 0.6446 + base: 0.6446 + penalty: 0.0 + label: reject + content_embedding: 0.4728 + axes: + anchor: 0.0 + cardinality: 1.0 + relation: 1.0 + slot: 1.0 + content: 0.4728 + v4_full_rank: 7 + - frame_id: '1171281176' + frame_number: 5 + template_id: compensation_complaint_side_card + confidence: 0.6358 + base: 0.6358 + penalty: 0.0 + label: reject + content_embedding: 0.4289 + axes: + anchor: 0.0 + cardinality: 1.0 + relation: 1.0 + slot: 1.0 + content: 0.4289 + v4_full_rank: 8 + - frame_id: '1171281182' + frame_number: 11 + template_id: construction_bim_three_usage + confidence: 0.6358 + base: 0.6358 + penalty: 0.0 + label: reject + content_embedding: 0.4291 + axes: + anchor: 0.0 + cardinality: 1.0 + relation: 1.0 + slot: 1.0 + content: 0.4291 + v4_full_rank: 9 + - frame_id: '1171281209' + frame_number: 28 + template_id: sw_reality_three_emphasis + confidence: 0.6338 + base: 0.6338 + penalty: 0.0 + label: reject + content_embedding: 0.4191 + axes: + anchor: 0.0 + cardinality: 1.0 + relation: 1.0 + slot: 1.0 + content: 0.4191 + v4_full_rank: 10 + - frame_id: '1171281204' + frame_number: 24 + template_id: engn_sw_three_types + confidence: 0.6073 + base: 0.6073 + penalty: 0.0 + label: reject + content_embedding: 0.4698 + axes: + anchor: 0.3333 + cardinality: 1.0 + relation: 0.4 + slot: 1.0 + content: 0.4698 + v4_full_rank: 11 + - frame_id: '1171281197' + frame_number: 19 + template_id: design_method_distortion_three_col + confidence: 0.5897 + base: 0.5897 + penalty: 0.0 + label: reject + content_embedding: 0.382 + axes: + anchor: 0.3333 + cardinality: 1.0 + relation: 0.4 + slot: 1.0 + content: 0.382 + v4_full_rank: 12 + - frame_id: '1171281211' + frame_number: 30 + template_id: industry_current_status_three_col + confidence: 0.5253 + base: 0.5253 + penalty: 0.0 + label: reject + content_embedding: 0.4767 + axes: + anchor: 0.0 + cardinality: 1.0 + relation: 0.4 + slot: 1.0 + content: 0.4767 + v4_full_rank: 13 + - frame_id: '1171281212' + frame_number: 31 + template_id: industry_characteristics_three_col + confidence: 0.5212 + base: 0.5212 + penalty: 0.0 + label: reject + content_embedding: 0.4562 + axes: + anchor: 0.0 + cardinality: 1.0 + relation: 0.4 + slot: 1.0 + content: 0.4562 + v4_full_rank: 14 + - frame_id: '1171281174' + frame_number: 3 + template_id: overseas_bim_numbered_list + confidence: 0.4941 + base: 0.4941 + penalty: 0.0 + label: reject + content_embedding: 0.4707 + axes: + anchor: 0.5 + cardinality: 0.0 + relation: 1.0 + slot: 0.5 + content: 0.4707 + v4_full_rank: 15 + - frame_id: '1171281180' + frame_number: 9 + template_id: pre_construction_model_info_stacked + confidence: 0.4782 + base: 0.5782 + penalty: 0.1 + label: reject + content_embedding: 0.391 + axes: + anchor: 0.5 + cardinality: 0.5 + relation: 1.0 + slot: 0.5 + content: 0.391 + v4_full_rank: 16 + - frame_id: '1171281194' + frame_number: 17 + template_id: bim_current_problems_paired + confidence: 0.4237 + base: 0.4237 + penalty: 0.0 + label: reject + content_embedding: 0.4309 + axes: + anchor: 0.25 + cardinality: 0.0 + relation: 1.0 + slot: 0.5 + content: 0.4309 + v4_full_rank: 17 + - frame_id: '1171281192' + frame_number: 15 + template_id: policy_goals_plus_execution_requirements + confidence: 0.4007 + base: 0.4007 + penalty: 0.0 + label: reject + content_embedding: 0.6285 + axes: + anchor: 0.0 + cardinality: 0.0 + relation: 1.0 + slot: 0.5 + content: 0.6285 + v4_full_rank: 18 + - frame_id: '1171281213' + frame_number: 32 + template_id: policy_achievement_five_goals + confidence: 0.3947 + base: 0.3947 + penalty: 0.0 + label: reject + content_embedding: 0.5983 + axes: + anchor: 0.0 + cardinality: 0.0 + relation: 1.0 + slot: 0.5 + content: 0.5983 + v4_full_rank: 19 + - frame_id: '1171281172' + frame_number: 1 + template_id: sw_development_cycle_six_nodes + confidence: 0.3829 + base: 0.3829 + penalty: 0.0 + label: reject + content_embedding: 0.5395 + axes: + anchor: 0.0 + cardinality: 0.0 + relation: 1.0 + slot: 0.5 + content: 0.5395 + v4_full_rank: 20 + - frame_id: '1171281173' + frame_number: 2 + template_id: engn_sw_development_domain_knowledge + confidence: 0.3763 + base: 0.3763 + penalty: 0.0 + label: reject + content_embedding: 0.5063 + axes: + anchor: 0.0 + cardinality: 0.0 + relation: 1.0 + slot: 0.5 + content: 0.5063 + v4_full_rank: 21 + - frame_id: '1171281193' + frame_number: 16 + template_id: bim_issues_quadrant_four + confidence: 0.3685 + base: 0.3685 + penalty: 0.0 + label: reject + content_embedding: 0.4677 + axes: + anchor: 0.0 + cardinality: 0.0 + relation: 1.0 + slot: 0.5 + content: 0.4677 + v4_full_rank: 22 + - frame_id: '1171281205' + frame_number: 25 + template_id: commercial_sw_four_categories + confidence: 0.3652 + base: 0.3652 + penalty: 0.0 + label: reject + content_embedding: 0.4508 + axes: + anchor: 0.0 + cardinality: 0.0 + relation: 1.0 + slot: 0.5 + content: 0.4508 + v4_full_rank: 23 + - frame_id: '1171281181' + frame_number: 10 + template_id: field_effectiveness_five_elements + confidence: 0.3643 + base: 0.3643 + penalty: 0.0 + label: reject + content_embedding: 0.4464 + axes: + anchor: 0.0 + cardinality: 0.0 + relation: 1.0 + slot: 0.5 + content: 0.4464 + v4_full_rank: 24 + - frame_id: '1171281206' + frame_number: 26 + template_id: sw_dependency_four_problems + confidence: 0.352 + base: 0.452 + penalty: 0.1 + label: reject + content_embedding: 0.3851 + axes: + anchor: 0.0 + cardinality: 0.5 + relation: 1.0 + slot: 0.5 + content: 0.3851 + v4_full_rank: 25 + - frame_id: '1171281208' + frame_number: 27 + template_id: bim_adoption_central_split + confidence: 0.231 + base: 0.431 + penalty: 0.2 + label: reject + content_embedding: 0.5467 + axes: + anchor: 0.6667 + cardinality: 0.0 + relation: 0.4 + slot: 0.5 + content: 0.5467 + v4_full_rank: 26 + - frame_id: '1171281175' + frame_number: 4 + template_id: domestic_bim_actor_relations + confidence: 0.2266 + base: 0.2266 + penalty: 0.0 + label: reject + content_embedding: 0.4579 + axes: + anchor: 0.0 + cardinality: 0.0 + relation: 0.3 + slot: 0.5 + content: 0.4579 + v4_full_rank: 27 + - frame_id: '1171281178' + frame_number: 7 + template_id: bigroom_system_components + confidence: 0.2013 + base: 0.3513 + penalty: 0.15 + label: reject + content_embedding: 0.4813 + axes: + anchor: 0.0 + cardinality: 0.5 + relation: 0.4 + slot: 0.5 + content: 0.4813 + v4_full_rank: 28 + - frame_id: '1171281177' + frame_number: 6 + template_id: compensation_complaint_map + confidence: 0.1977 + base: 0.1977 + penalty: 0.0 + label: reject + content_embedding: 0.4137 + axes: + anchor: 0.0 + cardinality: 0.0 + relation: 0.2 + slot: 0.5 + content: 0.4137 + v4_full_rank: 29 + - frame_id: '1171281210' + frame_number: 29 + template_id: process_product_two_way + confidence: 0.1761 + base: 0.3761 + penalty: 0.2 + label: reject + content_embedding: 0.4804 + axes: + anchor: 0.5 + cardinality: 0.0 + relation: 0.4 + slot: 0.5 + content: 0.4804 + v4_full_rank: 30 + - frame_id: '1171281195' + frame_number: 18 + template_id: bim_dx_comparison_table + confidence: 0.0758 + base: 0.4258 + penalty: 0.35 + label: reject + content_embedding: 0.4967 + axes: + anchor: 0.2857 + cardinality: 0.5 + relation: 0.4 + slot: 0.5 + content: 0.4967 + v4_full_rank: 31 + - frame_id: '1171281203' + frame_number: 23 + template_id: app_sw_package_vs_solution + confidence: 0.0 + base: 0.3431 + penalty: 0.35 + label: reject + content_embedding: 0.4403 + axes: + anchor: 0.0 + cardinality: 0.5 + relation: 0.4 + slot: 0.5 + content: 0.4403 + v4_full_rank: 32 + usable_count: 2 + reject_count: 30 + 02-2.1: + mdx_title: 2.1 업무 수행 과정(Process)의 변화 + answer_frame_number: null + is_holdout: true + judgments_full32: + - frame_id: '1171281177' + frame_number: 6 + template_id: compensation_complaint_map + confidence: 0.544 + base: 0.544 + penalty: 0.0 + label: reject + content_embedding: 0.3452 + axes: + anchor: 0.0 + cardinality: 1.0 + relation: 1.0 + slot: 0.5 + content: 0.3452 + v4_full_rank: 1 + - frame_id: '1171281174' + frame_number: 3 + template_id: overseas_bim_numbered_list + confidence: 0.2544 + base: 0.2544 + penalty: 0.0 + label: reject + content_embedding: 0.4472 + axes: + anchor: 0.5 + cardinality: 0.0 + relation: 0.2 + slot: 0.0 + content: 0.4472 + v4_full_rank: 2 + - frame_id: '1171281180' + frame_number: 9 + template_id: pre_construction_model_info_stacked + confidence: 0.2439 + base: 0.3439 + penalty: 0.1 + label: reject + content_embedding: 0.3943 + axes: + anchor: 0.5 + cardinality: 0.5 + relation: 0.2 + slot: 0.0 + content: 0.3943 + v4_full_rank: 3 + - frame_id: '1171281178' + frame_number: 7 + template_id: bigroom_system_components + confidence: 0.23 + base: 0.23 + penalty: 0.0 + label: reject + content_embedding: 0.4499 + axes: + anchor: 0.0 + cardinality: 0.5 + relation: 0.2 + slot: 0.0 + content: 0.4499 + v4_full_rank: 4 + - frame_id: '1171281204' + frame_number: 24 + template_id: engn_sw_three_types + confidence: 0.2261 + base: 0.2261 + penalty: 0.0 + label: reject + content_embedding: 0.4307 + axes: + anchor: 0.0 + cardinality: 0.5 + relation: 0.2 + slot: 0.0 + content: 0.4307 + v4_full_rank: 5 + - frame_id: '1171281203' + frame_number: 23 + template_id: app_sw_package_vs_solution + confidence: 0.2254 + base: 0.2254 + penalty: 0.0 + label: reject + content_embedding: 0.4268 + axes: + anchor: 0.0 + cardinality: 0.5 + relation: 0.2 + slot: 0.0 + content: 0.4268 + v4_full_rank: 6 + - frame_id: '1171281202' + frame_number: 22 + template_id: model_specialized_engn_sw + confidence: 0.212 + base: 0.212 + penalty: 0.0 + label: reject + content_embedding: 0.4435 + axes: + anchor: 0.3333 + cardinality: 0.0 + relation: 0.2 + slot: 0.0 + content: 0.4435 + v4_full_rank: 7 + - frame_id: '1171281201' + frame_number: 21 + template_id: solution_engn_split_diagram + confidence: 0.1998 + base: 0.1998 + penalty: 0.0 + label: reject + content_embedding: 0.4865 + axes: + anchor: 0.25 + cardinality: 0.0 + relation: 0.2 + slot: 0.0 + content: 0.4865 + v4_full_rank: 8 + - frame_id: '1171281210' + frame_number: 29 + template_id: process_product_two_way + confidence: 0.1982 + base: 0.3982 + penalty: 0.2 + label: reject + content_embedding: 0.5411 + axes: + anchor: 1.0 + cardinality: 0.0 + relation: 0.2 + slot: 0.0 + content: 0.5411 + v4_full_rank: 9 + - frame_id: '1171281206' + frame_number: 26 + template_id: sw_dependency_four_problems + confidence: 0.192 + base: 0.292 + penalty: 0.1 + label: reject + content_embedding: 0.3435 + axes: + anchor: 0.3333 + cardinality: 0.5 + relation: 0.2 + slot: 0.0 + content: 0.3435 + v4_full_rank: 10 + - frame_id: '1171281182' + frame_number: 11 + template_id: construction_bim_three_usage + confidence: 0.1718 + base: 0.1718 + penalty: 0.0 + label: reject + content_embedding: 0.4092 + axes: + anchor: 0.2 + cardinality: 0.0 + relation: 0.2 + slot: 0.0 + content: 0.4092 + v4_full_rank: 11 + - frame_id: '1171281205' + frame_number: 25 + template_id: commercial_sw_four_categories + confidence: 0.1548 + base: 0.1548 + penalty: 0.0 + label: reject + content_embedding: 0.3954 + axes: + anchor: 0.1429 + cardinality: 0.0 + relation: 0.2 + slot: 0.0 + content: 0.3954 + v4_full_rank: 12 + - frame_id: '1171281172' + frame_number: 1 + template_id: sw_development_cycle_six_nodes + confidence: 0.1427 + base: 0.1427 + penalty: 0.0 + label: reject + content_embedding: 0.5134 + axes: + anchor: 0.0 + cardinality: 0.0 + relation: 0.2 + slot: 0.0 + content: 0.5134 + v4_full_rank: 13 + - frame_id: '1171281208' + frame_number: 27 + template_id: bim_adoption_central_split + confidence: 0.1409 + base: 0.1409 + penalty: 0.0 + label: reject + content_embedding: 0.5043 + axes: + anchor: 0.0 + cardinality: 0.0 + relation: 0.2 + slot: 0.0 + content: 0.5043 + v4_full_rank: 14 + - frame_id: '1171281213' + frame_number: 32 + template_id: policy_achievement_five_goals + confidence: 0.1342 + base: 0.1342 + penalty: 0.0 + label: reject + content_embedding: 0.4712 + axes: + anchor: 0.0 + cardinality: 0.0 + relation: 0.2 + slot: 0.0 + content: 0.4712 + v4_full_rank: 15 + - frame_id: '1171281197' + frame_number: 19 + template_id: design_method_distortion_three_col + confidence: 0.1326 + base: 0.1326 + penalty: 0.0 + label: reject + content_embedding: 0.4632 + axes: + anchor: 0.0 + cardinality: 0.0 + relation: 0.2 + slot: 0.0 + content: 0.4632 + v4_full_rank: 16 + - frame_id: '1171281173' + frame_number: 2 + template_id: engn_sw_development_domain_knowledge + confidence: 0.1306 + base: 0.1306 + penalty: 0.0 + label: reject + content_embedding: 0.4532 + axes: + anchor: 0.0 + cardinality: 0.0 + relation: 0.2 + slot: 0.0 + content: 0.4532 + v4_full_rank: 17 + - frame_id: '1171281181' + frame_number: 10 + template_id: field_effectiveness_five_elements + confidence: 0.1301 + base: 0.1301 + penalty: 0.0 + label: reject + content_embedding: 0.4504 + axes: + anchor: 0.0 + cardinality: 0.0 + relation: 0.2 + slot: 0.0 + content: 0.4504 + v4_full_rank: 18 + - frame_id: '1171281194' + frame_number: 17 + template_id: bim_current_problems_paired + confidence: 0.1265 + base: 0.1265 + penalty: 0.0 + label: reject + content_embedding: 0.4327 + axes: + anchor: 0.0 + cardinality: 0.0 + relation: 0.2 + slot: 0.0 + content: 0.4327 + v4_full_rank: 19 + - frame_id: '1171281192' + frame_number: 15 + template_id: policy_goals_plus_execution_requirements + confidence: 0.1218 + base: 0.1218 + penalty: 0.0 + label: reject + content_embedding: 0.409 + axes: + anchor: 0.0 + cardinality: 0.0 + relation: 0.2 + slot: 0.0 + content: 0.409 + v4_full_rank: 20 + - frame_id: '1171281193' + frame_number: 16 + template_id: bim_issues_quadrant_four + confidence: 0.1203 + base: 0.1203 + penalty: 0.0 + label: reject + content_embedding: 0.4013 + axes: + anchor: 0.0 + cardinality: 0.0 + relation: 0.2 + slot: 0.0 + content: 0.4013 + v4_full_rank: 21 + - frame_id: '1171281189' + frame_number: 12 + template_id: construction_goals_three_circle_intersection + confidence: 0.118 + base: 0.118 + penalty: 0.0 + label: reject + content_embedding: 0.3901 + axes: + anchor: 0.0 + cardinality: 0.0 + relation: 0.2 + slot: 0.0 + content: 0.3901 + v4_full_rank: 22 + - frame_id: '1171281209' + frame_number: 28 + template_id: sw_reality_three_emphasis + confidence: 0.1169 + base: 0.2169 + penalty: 0.1 + label: reject + content_embedding: 0.3847 + axes: + anchor: 0.0 + cardinality: 0.5 + relation: 0.2 + slot: 0.0 + content: 0.3847 + v4_full_rank: 23 + - frame_id: '1171281190' + frame_number: 13 + template_id: three_parallel_requirements + confidence: 0.1092 + base: 0.2092 + penalty: 0.1 + label: reject + content_embedding: 0.3459 + axes: + anchor: 0.0 + cardinality: 0.5 + relation: 0.2 + slot: 0.0 + content: 0.3459 + v4_full_rank: 24 + - frame_id: '1171281195' + frame_number: 18 + template_id: bim_dx_comparison_table + confidence: 0.0589 + base: 0.2589 + penalty: 0.2 + label: reject + content_embedding: 0.4161 + axes: + anchor: 0.1429 + cardinality: 0.5 + relation: 0.2 + slot: 0.0 + content: 0.4161 + v4_full_rank: 25 + - frame_id: '1171281212' + frame_number: 31 + template_id: industry_characteristics_three_col + confidence: 0.0311 + base: 0.2311 + penalty: 0.2 + label: reject + content_embedding: 0.4555 + axes: + anchor: 0.0 + cardinality: 0.5 + relation: 0.2 + slot: 0.0 + content: 0.4555 + v4_full_rank: 26 + - frame_id: '1171281211' + frame_number: 30 + template_id: industry_current_status_three_col + confidence: 0.0211 + base: 0.2211 + penalty: 0.2 + label: reject + content_embedding: 0.4056 + axes: + anchor: 0.0 + cardinality: 0.5 + relation: 0.2 + slot: 0.0 + content: 0.4056 + v4_full_rank: 27 + - frame_id: '1171281175' + frame_number: 4 + template_id: domestic_bim_actor_relations + confidence: 0.0 + base: 0.118 + penalty: 0.2 + label: reject + content_embedding: 0.39 + axes: + anchor: 0.0 + cardinality: 0.0 + relation: 0.2 + slot: 0.0 + content: 0.39 + v4_full_rank: 28 + - frame_id: '1171281176' + frame_number: 5 + template_id: compensation_complaint_side_card + confidence: 0.0 + base: 0.2211 + penalty: 0.3 + label: reject + content_embedding: 0.4055 + axes: + anchor: 0.0 + cardinality: 0.5 + relation: 0.2 + slot: 0.0 + content: 0.4055 + v4_full_rank: 29 + - frame_id: '1171281179' + frame_number: 8 + template_id: info_management_what_how_when + confidence: 0.0 + base: 0.1379 + penalty: 0.2 + label: reject + content_embedding: 0.4897 + axes: + anchor: 0.0 + cardinality: 0.0 + relation: 0.2 + slot: 0.0 + content: 0.4897 + v4_full_rank: 30 + - frame_id: '1171281191' + frame_number: 14 + template_id: three_persona_benefits + confidence: 0.0 + base: 0.1583 + penalty: 0.2 + label: reject + content_embedding: 0.3415 + axes: + anchor: 0.2 + cardinality: 0.0 + relation: 0.2 + slot: 0.0 + content: 0.3415 + v4_full_rank: 31 + - frame_id: '1171281198' + frame_number: 20 + template_id: dx_sw_necessity_three_perspectives + confidence: 0.0 + base: 0.1106 + penalty: 0.2 + label: reject + content_embedding: 0.3531 + axes: + anchor: 0.0 + cardinality: 0.0 + relation: 0.2 + slot: 0.0 + content: 0.3531 + v4_full_rank: 32 + usable_count: 0 + reject_count: 32 + 04-1: + mdx_title: 1. DX에 대한 인식 + answer_frame_number: null + is_holdout: true + judgments_full32: + - frame_id: '1171281180' + frame_number: 9 + template_id: pre_construction_model_info_stacked + confidence: 0.7114 + base: 0.7114 + penalty: 0.0 + label: reject + content_embedding: 0.3906 + axes: + anchor: 0.3333 + cardinality: 1.0 + relation: 1.0 + slot: 1.0 + content: 0.3906 + v4_full_rank: 1 + - frame_id: '1171281181' + frame_number: 10 + template_id: field_effectiveness_five_elements + confidence: 0.6453 + base: 0.6453 + penalty: 0.0 + label: reject + content_embedding: 0.4765 + axes: + anchor: 0.0 + cardinality: 1.0 + relation: 1.0 + slot: 1.0 + content: 0.4765 + v4_full_rank: 2 + - frame_id: '1171281213' + frame_number: 32 + template_id: policy_achievement_five_goals + confidence: 0.6412 + base: 0.6412 + penalty: 0.0 + label: reject + content_embedding: 0.4562 + axes: + anchor: 0.0 + cardinality: 1.0 + relation: 1.0 + slot: 1.0 + content: 0.4562 + v4_full_rank: 3 + - frame_id: '1171281176' + frame_number: 5 + template_id: compensation_complaint_side_card + confidence: 0.5946 + base: 0.5946 + penalty: 0.0 + label: reject + content_embedding: 0.4229 + axes: + anchor: 0.0 + cardinality: 0.8 + relation: 1.0 + slot: 1.0 + content: 0.4229 + v4_full_rank: 4 + - frame_id: '1171281201' + frame_number: 21 + template_id: solution_engn_split_diagram + confidence: 0.4588 + base: 0.6588 + penalty: 0.2 + label: reject + content_embedding: 0.4315 + axes: + anchor: 0.25 + cardinality: 0.8 + relation: 1.0 + slot: 1.0 + content: 0.4315 + v4_full_rank: 5 + - frame_id: '1171281202' + frame_number: 22 + template_id: model_specialized_engn_sw + confidence: 0.4502 + base: 0.4502 + penalty: 0.0 + label: reject + content_embedding: 0.4595 + axes: + anchor: 0.3333 + cardinality: 0.0 + relation: 1.0 + slot: 0.5 + content: 0.4595 + v4_full_rank: 6 + - frame_id: '1171281205' + frame_number: 25 + template_id: commercial_sw_four_categories + confidence: 0.4244 + base: 0.4244 + penalty: 0.0 + label: reject + content_embedding: 0.4345 + axes: + anchor: 0.25 + cardinality: 0.0 + relation: 1.0 + slot: 0.5 + content: 0.4345 + v4_full_rank: 7 + - frame_id: '1171281182' + frame_number: 11 + template_id: construction_bim_three_usage + confidence: 0.4095 + base: 0.6095 + penalty: 0.2 + label: reject + content_embedding: 0.4224 + axes: + anchor: 1.0 + cardinality: 0.0 + relation: 1.0 + slot: 0.5 + content: 0.4224 + v4_full_rank: 8 + - frame_id: '1171281206' + frame_number: 26 + template_id: sw_dependency_four_problems + confidence: 0.3916 + base: 0.5416 + penalty: 0.15 + label: reject + content_embedding: 0.4164 + axes: + anchor: 0.3333 + cardinality: 0.5 + relation: 1.0 + slot: 0.5 + content: 0.4164 + v4_full_rank: 9 + - frame_id: '1171281173' + frame_number: 2 + template_id: engn_sw_development_domain_knowledge + confidence: 0.3811 + base: 0.3811 + penalty: 0.0 + label: reject + content_embedding: 0.5305 + axes: + anchor: 0.0 + cardinality: 0.0 + relation: 1.0 + slot: 0.5 + content: 0.5305 + v4_full_rank: 10 + - frame_id: '1171281172' + frame_number: 1 + template_id: sw_development_cycle_six_nodes + confidence: 0.3795 + base: 0.3795 + penalty: 0.0 + label: reject + content_embedding: 0.5225 + axes: + anchor: 0.0 + cardinality: 0.0 + relation: 1.0 + slot: 0.5 + content: 0.5225 + v4_full_rank: 11 + - frame_id: '1171281192' + frame_number: 15 + template_id: policy_goals_plus_execution_requirements + confidence: 0.3751 + base: 0.3751 + penalty: 0.0 + label: reject + content_embedding: 0.5007 + axes: + anchor: 0.0 + cardinality: 0.0 + relation: 1.0 + slot: 0.5 + content: 0.5007 + v4_full_rank: 12 + - frame_id: '1171281194' + frame_number: 17 + template_id: bim_current_problems_paired + confidence: 0.3645 + base: 0.3645 + penalty: 0.0 + label: reject + content_embedding: 0.4473 + axes: + anchor: 0.0 + cardinality: 0.0 + relation: 1.0 + slot: 0.5 + content: 0.4473 + v4_full_rank: 13 + - frame_id: '1171281174' + frame_number: 3 + template_id: overseas_bim_numbered_list + confidence: 0.3635 + base: 0.3635 + penalty: 0.0 + label: reject + content_embedding: 0.4423 + axes: + anchor: 0.0 + cardinality: 0.0 + relation: 1.0 + slot: 0.5 + content: 0.4423 + v4_full_rank: 14 + - frame_id: '1171281193' + frame_number: 16 + template_id: bim_issues_quadrant_four + confidence: 0.361 + base: 0.361 + penalty: 0.0 + label: reject + content_embedding: 0.4299 + axes: + anchor: 0.0 + cardinality: 0.0 + relation: 1.0 + slot: 0.5 + content: 0.4299 + v4_full_rank: 15 + - frame_id: '1171281190' + frame_number: 13 + template_id: three_parallel_requirements + confidence: 0.3607 + base: 0.5607 + penalty: 0.2 + label: reject + content_embedding: 0.512 + axes: + anchor: 0.3333 + cardinality: 0.5 + relation: 1.0 + slot: 0.5 + content: 0.512 + v4_full_rank: 16 + - frame_id: '1171281198' + frame_number: 20 + template_id: dx_sw_necessity_three_perspectives + confidence: 0.3064 + base: 0.5064 + penalty: 0.2 + label: reject + content_embedding: 0.5321 + axes: + anchor: 0.5 + cardinality: 0.0 + relation: 1.0 + slot: 0.5 + content: 0.5321 + v4_full_rank: 17 + - frame_id: '1171281175' + frame_number: 4 + template_id: domestic_bim_actor_relations + confidence: 0.2794 + base: 0.2794 + penalty: 0.0 + label: reject + content_embedding: 0.4097 + axes: + anchor: 0.25 + cardinality: 0.0 + relation: 0.3 + slot: 0.5 + content: 0.4097 + v4_full_rank: 18 + - frame_id: '1171281178' + frame_number: 7 + template_id: bigroom_system_components + confidence: 0.2627 + base: 0.4127 + penalty: 0.15 + label: reject + content_embedding: 0.4759 + axes: + anchor: 0.25 + cardinality: 0.5 + relation: 0.4 + slot: 0.5 + content: 0.4759 + v4_full_rank: 19 + - frame_id: '1171281179' + frame_number: 8 + template_id: info_management_what_how_when + confidence: 0.2468 + base: 0.4468 + penalty: 0.2 + label: reject + content_embedding: 0.4422 + axes: + anchor: 0.3333 + cardinality: 0.0 + relation: 1.0 + slot: 0.5 + content: 0.4422 + v4_full_rank: 20 + - frame_id: '1171281210' + frame_number: 29 + template_id: process_product_two_way + confidence: 0.2237 + base: 0.4237 + penalty: 0.2 + label: reject + content_embedding: 0.406 + axes: + anchor: 0.75 + cardinality: 0.0 + relation: 0.4 + slot: 0.5 + content: 0.406 + v4_full_rank: 21 + - frame_id: '1171281177' + frame_number: 6 + template_id: compensation_complaint_map + confidence: 0.1929 + base: 0.1929 + penalty: 0.0 + label: reject + content_embedding: 0.3896 + axes: + anchor: 0.0 + cardinality: 0.0 + relation: 0.2 + slot: 0.5 + content: 0.3896 + v4_full_rank: 22 + - frame_id: '1171281209' + frame_number: 28 + template_id: sw_reality_three_emphasis + confidence: 0.1835 + base: 0.5335 + penalty: 0.35 + label: reject + content_embedding: 0.3761 + axes: + anchor: 0.3333 + cardinality: 0.5 + relation: 1.0 + slot: 0.5 + content: 0.3761 + v4_full_rank: 23 + - frame_id: '1171281191' + frame_number: 14 + template_id: three_persona_benefits + confidence: 0.1677 + base: 0.3677 + penalty: 0.2 + label: reject + content_embedding: 0.4635 + axes: + anchor: 0.0 + cardinality: 0.0 + relation: 1.0 + slot: 0.5 + content: 0.4635 + v4_full_rank: 24 + - frame_id: '1171281195' + frame_number: 18 + template_id: bim_dx_comparison_table + confidence: 0.1674 + base: 0.5174 + penalty: 0.35 + label: reject + content_embedding: 0.4372 + axes: + anchor: 0.7 + cardinality: 0.5 + relation: 0.4 + slot: 0.5 + content: 0.4372 + v4_full_rank: 25 + - frame_id: '1171281189' + frame_number: 12 + template_id: construction_goals_three_circle_intersection + confidence: 0.1607 + base: 0.3607 + penalty: 0.2 + label: reject + content_embedding: 0.4285 + axes: + anchor: 0.0 + cardinality: 0.0 + relation: 1.0 + slot: 0.5 + content: 0.4285 + v4_full_rank: 26 + - frame_id: '1171281208' + frame_number: 27 + template_id: bim_adoption_central_split + confidence: 0.129 + base: 0.329 + penalty: 0.2 + label: reject + content_embedding: 0.4533 + axes: + anchor: 0.3333 + cardinality: 0.0 + relation: 0.4 + slot: 0.5 + content: 0.4533 + v4_full_rank: 27 + - frame_id: '1171281204' + frame_number: 24 + template_id: engn_sw_three_types + confidence: 0.0785 + base: 0.4285 + penalty: 0.35 + label: reject + content_embedding: 0.4506 + axes: + anchor: 0.3333 + cardinality: 0.5 + relation: 0.4 + slot: 0.5 + content: 0.4506 + v4_full_rank: 28 + - frame_id: '1171281197' + frame_number: 19 + template_id: design_method_distortion_three_col + confidence: 0.0381 + base: 0.2381 + penalty: 0.2 + label: reject + content_embedding: 0.4156 + axes: + anchor: 0.0 + cardinality: 0.0 + relation: 0.4 + slot: 0.5 + content: 0.4156 + v4_full_rank: 29 + - frame_id: '1171281203' + frame_number: 23 + template_id: app_sw_package_vs_solution + confidence: 0.0 + base: 0.3433 + penalty: 0.35 + label: reject + content_embedding: 0.4414 + axes: + anchor: 0.0 + cardinality: 0.5 + relation: 0.4 + slot: 0.5 + content: 0.4414 + v4_full_rank: 30 + - frame_id: '1171281211' + frame_number: 30 + template_id: industry_current_status_three_col + confidence: 0.0 + base: 0.3453 + penalty: 0.35 + label: reject + content_embedding: 0.4513 + axes: + anchor: 0.0 + cardinality: 0.5 + relation: 0.4 + slot: 0.5 + content: 0.4513 + v4_full_rank: 31 + - frame_id: '1171281212' + frame_number: 31 + template_id: industry_characteristics_three_col + confidence: 0.0 + base: 0.3407 + penalty: 0.35 + label: reject + content_embedding: 0.4283 + axes: + anchor: 0.0 + cardinality: 0.5 + relation: 0.4 + slot: 0.5 + content: 0.4283 + v4_full_rank: 32 + usable_count: 0 + reject_count: 32 + 04-2.1: + mdx_title: 2.1 정책 및 발주 체계 + answer_frame_number: null + is_holdout: true + judgments_full32: + - frame_id: '1171281206' + frame_number: 26 + template_id: sw_dependency_four_problems + confidence: 0.8018 + base: 0.8018 + penalty: 0.0 + label: restructure + content_embedding: 0.4259 + axes: + anchor: 0.6667 + cardinality: 1.0 + relation: 1.0 + slot: 1.0 + content: 0.4259 + v4_full_rank: 1 + - frame_id: '1171281193' + frame_number: 16 + template_id: bim_issues_quadrant_four + confidence: 0.7309 + base: 0.7309 + penalty: 0.0 + label: reject + content_embedding: 0.4877 + axes: + anchor: 0.3333 + cardinality: 1.0 + relation: 1.0 + slot: 1.0 + content: 0.4877 + v4_full_rank: 2 + - frame_id: '1171281194' + frame_number: 17 + template_id: bim_current_problems_paired + confidence: 0.7015 + base: 0.7015 + penalty: 0.0 + label: reject + content_embedding: 0.5077 + axes: + anchor: 0.2 + cardinality: 1.0 + relation: 1.0 + slot: 1.0 + content: 0.5077 + v4_full_rank: 3 + - frame_id: '1171281205' + frame_number: 25 + template_id: commercial_sw_four_categories + confidence: 0.6907 + base: 0.6907 + penalty: 0.0 + label: reject + content_embedding: 0.391 + axes: + anchor: 0.25 + cardinality: 1.0 + relation: 1.0 + slot: 1.0 + content: 0.391 + v4_full_rank: 4 + - frame_id: '1171281192' + frame_number: 15 + template_id: policy_goals_plus_execution_requirements + confidence: 0.6429 + base: 0.6429 + penalty: 0.0 + label: reject + content_embedding: 0.4647 + axes: + anchor: 0.0 + cardinality: 1.0 + relation: 1.0 + slot: 1.0 + content: 0.4647 + v4_full_rank: 5 + - frame_id: '1171281173' + frame_number: 2 + template_id: engn_sw_development_domain_knowledge + confidence: 0.642 + base: 0.642 + penalty: 0.0 + label: reject + content_embedding: 0.4599 + axes: + anchor: 0.0 + cardinality: 1.0 + relation: 1.0 + slot: 1.0 + content: 0.4599 + v4_full_rank: 6 + - frame_id: '1171281202' + frame_number: 22 + template_id: model_specialized_engn_sw + confidence: 0.6393 + base: 0.6393 + penalty: 0.0 + label: reject + content_embedding: 0.3341 + axes: + anchor: 0.25 + cardinality: 0.8 + relation: 1.0 + slot: 1.0 + content: 0.3341 + v4_full_rank: 7 + - frame_id: '1171281174' + frame_number: 3 + template_id: overseas_bim_numbered_list + confidence: 0.6354 + base: 0.6354 + penalty: 0.0 + label: reject + content_embedding: 0.4268 + axes: + anchor: 0.0 + cardinality: 1.0 + relation: 1.0 + slot: 1.0 + content: 0.4268 + v4_full_rank: 8 + - frame_id: '1171281175' + frame_number: 4 + template_id: domestic_bim_actor_relations + confidence: 0.6016 + base: 0.6016 + penalty: 0.0 + label: restructure + content_embedding: 0.4578 + axes: + anchor: 0.4 + cardinality: 1.0 + relation: 0.3 + slot: 1.0 + content: 0.4578 + v4_full_rank: 9 + - frame_id: '1171281180' + frame_number: 9 + template_id: pre_construction_model_info_stacked + confidence: 0.5911 + base: 0.5911 + penalty: 0.0 + label: reject + content_embedding: 0.4053 + axes: + anchor: 0.0 + cardinality: 0.8 + relation: 1.0 + slot: 1.0 + content: 0.4053 + v4_full_rank: 10 + - frame_id: '1171281176' + frame_number: 5 + template_id: compensation_complaint_side_card + confidence: 0.5715 + base: 0.5715 + penalty: 0.0 + label: reject + content_embedding: 0.3076 + axes: + anchor: 0.0 + cardinality: 0.8 + relation: 1.0 + slot: 1.0 + content: 0.3076 + v4_full_rank: 11 + - frame_id: '1171281201' + frame_number: 21 + template_id: solution_engn_split_diagram + confidence: 0.5032 + base: 0.7032 + penalty: 0.2 + label: reject + content_embedding: 0.4537 + axes: + anchor: 0.25 + cardinality: 1.0 + relation: 1.0 + slot: 1.0 + content: 0.4537 + v4_full_rank: 12 + - frame_id: '1171281181' + frame_number: 10 + template_id: field_effectiveness_five_elements + confidence: 0.3693 + base: 0.3693 + penalty: 0.0 + label: reject + content_embedding: 0.4716 + axes: + anchor: 0.0 + cardinality: 0.0 + relation: 1.0 + slot: 0.5 + content: 0.4716 + v4_full_rank: 13 + - frame_id: '1171281213' + frame_number: 32 + template_id: policy_achievement_five_goals + confidence: 0.366 + base: 0.366 + penalty: 0.0 + label: reject + content_embedding: 0.4551 + axes: + anchor: 0.0 + cardinality: 0.0 + relation: 1.0 + slot: 0.5 + content: 0.4551 + v4_full_rank: 14 + - frame_id: '1171281172' + frame_number: 1 + template_id: sw_development_cycle_six_nodes + confidence: 0.3515 + base: 0.3515 + penalty: 0.0 + label: reject + content_embedding: 0.3827 + axes: + anchor: 0.0 + cardinality: 0.0 + relation: 1.0 + slot: 0.5 + content: 0.3827 + v4_full_rank: 15 + - frame_id: '1171281190' + frame_number: 13 + template_id: three_parallel_requirements + confidence: 0.3233 + base: 0.5233 + penalty: 0.2 + label: reject + content_embedding: 0.3247 + axes: + anchor: 0.3333 + cardinality: 0.5 + relation: 1.0 + slot: 0.5 + content: 0.3247 + v4_full_rank: 16 + - frame_id: '1171281198' + frame_number: 20 + template_id: dx_sw_necessity_three_perspectives + confidence: 0.2609 + base: 0.4609 + penalty: 0.2 + label: reject + content_embedding: 0.3046 + axes: + anchor: 0.5 + cardinality: 0.0 + relation: 1.0 + slot: 0.5 + content: 0.3046 + v4_full_rank: 17 + - frame_id: '1171281178' + frame_number: 7 + template_id: bigroom_system_components + confidence: 0.2438 + base: 0.3938 + penalty: 0.15 + label: reject + content_embedding: 0.3813 + axes: + anchor: 0.25 + cardinality: 0.5 + relation: 0.4 + slot: 0.5 + content: 0.3813 + v4_full_rank: 18 + - frame_id: '1171281177' + frame_number: 6 + template_id: compensation_complaint_map + confidence: 0.1984 + base: 0.1984 + penalty: 0.0 + label: reject + content_embedding: 0.4169 + axes: + anchor: 0.0 + cardinality: 0.0 + relation: 0.2 + slot: 0.5 + content: 0.4169 + v4_full_rank: 19 + - frame_id: '1171281209' + frame_number: 28 + template_id: sw_reality_three_emphasis + confidence: 0.1968 + base: 0.5468 + penalty: 0.35 + label: reject + content_embedding: 0.4425 + axes: + anchor: 0.3333 + cardinality: 0.5 + relation: 1.0 + slot: 0.5 + content: 0.4425 + v4_full_rank: 20 + - frame_id: '1171281189' + frame_number: 12 + template_id: construction_goals_three_circle_intersection + confidence: 0.1527 + base: 0.3527 + penalty: 0.2 + label: reject + content_embedding: 0.3884 + axes: + anchor: 0.0 + cardinality: 0.0 + relation: 1.0 + slot: 0.5 + content: 0.3884 + v4_full_rank: 21 + - frame_id: '1171281191' + frame_number: 14 + template_id: three_persona_benefits + confidence: 0.1499 + base: 0.3499 + penalty: 0.2 + label: reject + content_embedding: 0.3744 + axes: + anchor: 0.0 + cardinality: 0.0 + relation: 1.0 + slot: 0.5 + content: 0.3744 + v4_full_rank: 22 + - frame_id: '1171281179' + frame_number: 8 + template_id: info_management_what_how_when + confidence: 0.1494 + base: 0.3494 + penalty: 0.2 + label: reject + content_embedding: 0.3721 + axes: + anchor: 0.0 + cardinality: 0.0 + relation: 1.0 + slot: 0.5 + content: 0.3721 + v4_full_rank: 23 + - frame_id: '1171281182' + frame_number: 11 + template_id: construction_bim_three_usage + confidence: 0.149 + base: 0.349 + penalty: 0.2 + label: reject + content_embedding: 0.3698 + axes: + anchor: 0.0 + cardinality: 0.0 + relation: 1.0 + slot: 0.5 + content: 0.3698 + v4_full_rank: 24 + - frame_id: '1171281195' + frame_number: 18 + template_id: bim_dx_comparison_table + confidence: 0.1476 + base: 0.4976 + penalty: 0.35 + label: reject + content_embedding: 0.3379 + axes: + anchor: 0.7 + cardinality: 0.5 + relation: 0.4 + slot: 0.5 + content: 0.3379 + v4_full_rank: 25 + - frame_id: '1171281204' + frame_number: 24 + template_id: engn_sw_three_types + confidence: 0.1475 + base: 0.4975 + penalty: 0.35 + label: reject + content_embedding: 0.3794 + axes: + anchor: 0.6667 + cardinality: 0.5 + relation: 0.4 + slot: 0.5 + content: 0.3794 + v4_full_rank: 26 + - frame_id: '1171281208' + frame_number: 27 + template_id: bim_adoption_central_split + confidence: 0.133 + base: 0.333 + penalty: 0.2 + label: reject + content_embedding: 0.4733 + axes: + anchor: 0.3333 + cardinality: 0.0 + relation: 0.4 + slot: 0.5 + content: 0.4733 + v4_full_rank: 27 + - frame_id: '1171281212' + frame_number: 31 + template_id: industry_characteristics_three_col + confidence: 0.1217 + base: 0.4717 + penalty: 0.35 + label: reject + content_embedding: 0.4586 + axes: + anchor: 0.5 + cardinality: 0.5 + relation: 0.4 + slot: 0.5 + content: 0.4586 + v4_full_rank: 28 + - frame_id: '1171281197' + frame_number: 19 + template_id: design_method_distortion_three_col + confidence: 0.0959 + base: 0.2959 + penalty: 0.2 + label: reject + content_embedding: 0.4543 + axes: + anchor: 0.2 + cardinality: 0.0 + relation: 0.4 + slot: 0.5 + content: 0.4543 + v4_full_rank: 29 + - frame_id: '1171281210' + frame_number: 29 + template_id: process_product_two_way + confidence: 0.037 + base: 0.237 + penalty: 0.2 + label: reject + content_embedding: 0.41 + axes: + anchor: 0.0 + cardinality: 0.0 + relation: 0.4 + slot: 0.5 + content: 0.41 + v4_full_rank: 30 + - frame_id: '1171281203' + frame_number: 23 + template_id: app_sw_package_vs_solution + confidence: 0.0 + base: 0.326 + penalty: 0.35 + label: reject + content_embedding: 0.3548 + axes: + anchor: 0.0 + cardinality: 0.5 + relation: 0.4 + slot: 0.5 + content: 0.3548 + v4_full_rank: 31 + - frame_id: '1171281211' + frame_number: 30 + template_id: industry_current_status_three_col + confidence: 0.0 + base: 0.3297 + penalty: 0.35 + label: reject + content_embedding: 0.3736 + axes: + anchor: 0.0 + cardinality: 0.5 + relation: 0.4 + slot: 0.5 + content: 0.3736 + v4_full_rank: 32 + usable_count: 2 + reject_count: 30 + 04-2.2: + mdx_title: 2.2 조직 및 수행 역량 + answer_frame_number: null + is_holdout: true + judgments_full32: + - frame_id: '1171281193' + frame_number: 16 + template_id: bim_issues_quadrant_four + confidence: 0.8335 + base: 0.8335 + penalty: 0.0 + label: light_edit + content_embedding: 0.5841 + axes: + anchor: 0.6667 + cardinality: 1.0 + relation: 1.0 + slot: 1.0 + content: 0.5841 + v4_full_rank: 1 + - frame_id: '1171281206' + frame_number: 26 + template_id: sw_dependency_four_problems + confidence: 0.8074 + base: 0.8074 + penalty: 0.0 + label: light_edit + content_embedding: 0.4536 + axes: + anchor: 0.6667 + cardinality: 1.0 + relation: 1.0 + slot: 1.0 + content: 0.4536 + v4_full_rank: 2 + - frame_id: '1171281194' + frame_number: 17 + template_id: bim_current_problems_paired + confidence: 0.7782 + base: 0.7782 + penalty: 0.0 + label: restructure + content_embedding: 0.6408 + axes: + anchor: 0.4 + cardinality: 1.0 + relation: 1.0 + slot: 1.0 + content: 0.6408 + v4_full_rank: 3 + - frame_id: '1171281205' + frame_number: 25 + template_id: commercial_sw_four_categories + confidence: 0.6994 + base: 0.6994 + penalty: 0.0 + label: reject + content_embedding: 0.4343 + axes: + anchor: 0.25 + cardinality: 1.0 + relation: 1.0 + slot: 1.0 + content: 0.4343 + v4_full_rank: 4 + - frame_id: '1171281174' + frame_number: 3 + template_id: overseas_bim_numbered_list + confidence: 0.6939 + base: 0.6939 + penalty: 0.0 + label: reject + content_embedding: 0.5111 + axes: + anchor: 0.1667 + cardinality: 1.0 + relation: 1.0 + slot: 1.0 + content: 0.5111 + v4_full_rank: 5 + - frame_id: '1171281202' + frame_number: 22 + template_id: model_specialized_engn_sw + confidence: 0.6823 + base: 0.6823 + penalty: 0.0 + label: reject + content_embedding: 0.4446 + axes: + anchor: 0.3333 + cardinality: 0.8 + relation: 1.0 + slot: 1.0 + content: 0.4446 + v4_full_rank: 6 + - frame_id: '1171281180' + frame_number: 9 + template_id: pre_construction_model_info_stacked + confidence: 0.6735 + base: 0.6735 + penalty: 0.0 + label: reject + content_embedding: 0.4009 + axes: + anchor: 0.3333 + cardinality: 0.8 + relation: 1.0 + slot: 1.0 + content: 0.4009 + v4_full_rank: 7 + - frame_id: '1171281173' + frame_number: 2 + template_id: engn_sw_development_domain_knowledge + confidence: 0.6537 + base: 0.6537 + penalty: 0.0 + label: reject + content_embedding: 0.5184 + axes: + anchor: 0.0 + cardinality: 1.0 + relation: 1.0 + slot: 1.0 + content: 0.5184 + v4_full_rank: 8 + - frame_id: '1171281192' + frame_number: 15 + template_id: policy_goals_plus_execution_requirements + confidence: 0.6427 + base: 0.6427 + penalty: 0.0 + label: reject + content_embedding: 0.4636 + axes: + anchor: 0.0 + cardinality: 1.0 + relation: 1.0 + slot: 1.0 + content: 0.4636 + v4_full_rank: 9 + - frame_id: '1171281175' + frame_number: 4 + template_id: domestic_bim_actor_relations + confidence: 0.5787 + base: 0.5787 + penalty: 0.0 + label: reject + content_embedding: 0.531 + axes: + anchor: 0.25 + cardinality: 1.0 + relation: 0.3 + slot: 1.0 + content: 0.531 + v4_full_rank: 10 + - frame_id: '1171281176' + frame_number: 5 + template_id: compensation_complaint_side_card + confidence: 0.5781 + base: 0.5781 + penalty: 0.0 + label: reject + content_embedding: 0.3405 + axes: + anchor: 0.0 + cardinality: 0.8 + relation: 1.0 + slot: 1.0 + content: 0.3405 + v4_full_rank: 11 + - frame_id: '1171281201' + frame_number: 21 + template_id: solution_engn_split_diagram + confidence: 0.5148 + base: 0.7148 + penalty: 0.2 + label: reject + content_embedding: 0.5116 + axes: + anchor: 0.25 + cardinality: 1.0 + relation: 1.0 + slot: 1.0 + content: 0.5116 + v4_full_rank: 12 + - frame_id: '1171281181' + frame_number: 10 + template_id: field_effectiveness_five_elements + confidence: 0.3814 + base: 0.3814 + penalty: 0.0 + label: reject + content_embedding: 0.5319 + axes: + anchor: 0.0 + cardinality: 0.0 + relation: 1.0 + slot: 0.5 + content: 0.5319 + v4_full_rank: 13 + - frame_id: '1171281213' + frame_number: 32 + template_id: policy_achievement_five_goals + confidence: 0.3691 + base: 0.3691 + penalty: 0.0 + label: reject + content_embedding: 0.4706 + axes: + anchor: 0.0 + cardinality: 0.0 + relation: 1.0 + slot: 0.5 + content: 0.4706 + v4_full_rank: 14 + - frame_id: '1171281172' + frame_number: 1 + template_id: sw_development_cycle_six_nodes + confidence: 0.3666 + base: 0.3666 + penalty: 0.0 + label: reject + content_embedding: 0.4578 + axes: + anchor: 0.0 + cardinality: 0.0 + relation: 1.0 + slot: 0.5 + content: 0.4578 + v4_full_rank: 15 + - frame_id: '1171281190' + frame_number: 13 + template_id: three_parallel_requirements + confidence: 0.3409 + base: 0.5409 + penalty: 0.2 + label: reject + content_embedding: 0.4128 + axes: + anchor: 0.3333 + cardinality: 0.5 + relation: 1.0 + slot: 0.5 + content: 0.4128 + v4_full_rank: 16 + - frame_id: '1171281198' + frame_number: 20 + template_id: dx_sw_necessity_three_perspectives + confidence: 0.2779 + base: 0.4779 + penalty: 0.2 + label: reject + content_embedding: 0.3896 + axes: + anchor: 0.5 + cardinality: 0.0 + relation: 1.0 + slot: 0.5 + content: 0.3896 + v4_full_rank: 17 + - frame_id: '1171281178' + frame_number: 7 + template_id: bigroom_system_components + confidence: 0.2697 + base: 0.4197 + penalty: 0.15 + label: reject + content_embedding: 0.5109 + axes: + anchor: 0.25 + cardinality: 0.5 + relation: 0.4 + slot: 0.5 + content: 0.5109 + v4_full_rank: 18 + - frame_id: '1171281209' + frame_number: 28 + template_id: sw_reality_three_emphasis + confidence: 0.2102 + base: 0.5602 + penalty: 0.35 + label: reject + content_embedding: 0.5092 + axes: + anchor: 0.3333 + cardinality: 0.5 + relation: 1.0 + slot: 0.5 + content: 0.5092 + v4_full_rank: 19 + - frame_id: '1171281177' + frame_number: 6 + template_id: compensation_complaint_map + confidence: 0.1949 + base: 0.1949 + penalty: 0.0 + label: reject + content_embedding: 0.3993 + axes: + anchor: 0.0 + cardinality: 0.0 + relation: 0.2 + slot: 0.5 + content: 0.3993 + v4_full_rank: 20 + - frame_id: '1171281189' + frame_number: 12 + template_id: construction_goals_three_circle_intersection + confidence: 0.172 + base: 0.372 + penalty: 0.2 + label: reject + content_embedding: 0.485 + axes: + anchor: 0.0 + cardinality: 0.0 + relation: 1.0 + slot: 0.5 + content: 0.485 + v4_full_rank: 21 + - frame_id: '1171281179' + frame_number: 8 + template_id: info_management_what_how_when + confidence: 0.1683 + base: 0.3683 + penalty: 0.2 + label: reject + content_embedding: 0.4666 + axes: + anchor: 0.0 + cardinality: 0.0 + relation: 1.0 + slot: 0.5 + content: 0.4666 + v4_full_rank: 22 + - frame_id: '1171281210' + frame_number: 29 + template_id: process_product_two_way + confidence: 0.1675 + base: 0.3675 + penalty: 0.2 + label: reject + content_embedding: 0.4374 + axes: + anchor: 0.5 + cardinality: 0.0 + relation: 0.4 + slot: 0.5 + content: 0.4374 + v4_full_rank: 23 + - frame_id: '1171281195' + frame_number: 18 + template_id: bim_dx_comparison_table + confidence: 0.1673 + base: 0.5173 + penalty: 0.35 + label: reject + content_embedding: 0.4366 + axes: + anchor: 0.7 + cardinality: 0.5 + relation: 0.4 + slot: 0.5 + content: 0.4366 + v4_full_rank: 24 + - frame_id: '1171281182' + frame_number: 11 + template_id: construction_bim_three_usage + confidence: 0.1626 + base: 0.3626 + penalty: 0.2 + label: reject + content_embedding: 0.4378 + axes: + anchor: 0.0 + cardinality: 0.0 + relation: 1.0 + slot: 0.5 + content: 0.4378 + v4_full_rank: 25 + - frame_id: '1171281191' + frame_number: 14 + template_id: three_persona_benefits + confidence: 0.1582 + base: 0.3582 + penalty: 0.2 + label: reject + content_embedding: 0.4161 + axes: + anchor: 0.0 + cardinality: 0.0 + relation: 1.0 + slot: 0.5 + content: 0.4161 + v4_full_rank: 26 + - frame_id: '1171281208' + frame_number: 27 + template_id: bim_adoption_central_split + confidence: 0.1319 + base: 0.3319 + penalty: 0.2 + label: reject + content_embedding: 0.4676 + axes: + anchor: 0.3333 + cardinality: 0.0 + relation: 0.4 + slot: 0.5 + content: 0.4676 + v4_full_rank: 27 + - frame_id: '1171281204' + frame_number: 24 + template_id: engn_sw_three_types + confidence: 0.0804 + base: 0.4304 + penalty: 0.35 + label: reject + content_embedding: 0.4605 + axes: + anchor: 0.3333 + cardinality: 0.5 + relation: 0.4 + slot: 0.5 + content: 0.4605 + v4_full_rank: 28 + - frame_id: '1171281197' + frame_number: 19 + template_id: design_method_distortion_three_col + confidence: 0.0699 + base: 0.2699 + penalty: 0.2 + label: reject + content_embedding: 0.5746 + axes: + anchor: 0.0 + cardinality: 0.0 + relation: 0.4 + slot: 0.5 + content: 0.5746 + v4_full_rank: 29 + - frame_id: '1171281212' + frame_number: 31 + template_id: industry_characteristics_three_col + confidence: 0.0073 + base: 0.3573 + penalty: 0.35 + label: reject + content_embedding: 0.5116 + axes: + anchor: 0.0 + cardinality: 0.5 + relation: 0.4 + slot: 0.5 + content: 0.5116 + v4_full_rank: 30 + - frame_id: '1171281203' + frame_number: 23 + template_id: app_sw_package_vs_solution + confidence: 0.0 + base: 0.3379 + penalty: 0.35 + label: reject + content_embedding: 0.4145 + axes: + anchor: 0.0 + cardinality: 0.5 + relation: 0.4 + slot: 0.5 + content: 0.4145 + v4_full_rank: 31 + - frame_id: '1171281211' + frame_number: 30 + template_id: industry_current_status_three_col + confidence: 0.0 + base: 0.341 + penalty: 0.35 + label: reject + content_embedding: 0.4302 + axes: + anchor: 0.0 + cardinality: 0.5 + relation: 0.4 + slot: 0.5 + content: 0.4302 + v4_full_rank: 32 + usable_count: 3 + reject_count: 29