Phase X-B-1,2 완료: Kei 유형 A/B 선택 + 검증기 완화
X-B-1: KEI_PROMPT에 유형 B 옵션 추가 - 유형 A: 기존 배경/본심/첨부/결론 (참조자료 있는 콘텐츠) - 유형 B: 본심1(상단)+본심2(하단2분할)+결론 (본문만으로 구성) - Kei가 콘텐츠 보고 A/B 선택, layout_template 필드로 반환 - 검증: 01번→A, 02번→B 정확히 선택 X-B-2: 검증기 완화 - 유형 A: 본심 필수 유지 - 유형 B: 결론(footer)만 필수, 자유 역할명 허용 - 섹션 수 차이 허용 확대 (유형 B: 4) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
131
PHASE-X-B.md
Normal file
131
PHASE-X-B.md
Normal file
@@ -0,0 +1,131 @@
|
||||
# Phase X-B: 유형 B 템플릿 추가
|
||||
|
||||
> 작성일: 2026-04-06
|
||||
> 전제: 유형 A(배경+본심+첨부+결론) 기존 코드 건드리지 않음
|
||||
|
||||
---
|
||||
|
||||
## 유형 B 구조
|
||||
|
||||
02번 MDX (DX의 시행 목표 및 기대효과) 기준:
|
||||
|
||||
```
|
||||
┌──────────────────────────────────────────┐
|
||||
│ 제목 │
|
||||
├───────────────────────┬──────────────────┤
|
||||
│ 본심1: DX 궁극적 목표 │ │
|
||||
│ ┌안전과 품질──────────┐│ [그림 2] │
|
||||
│ │• 불릿 ││ 궁극적 목표 │
|
||||
│ ├생산성 향상──────────┤│ 이미지 │
|
||||
│ │• 불릿 ││ (비율 맞춤) │
|
||||
│ ├소통과 신뢰──────────┤│ │
|
||||
│ │• 불릿 ││ │
|
||||
│ └────────────────────┘│ │
|
||||
├───────────┬───────────┴──────────────────┤
|
||||
│ 본심2-좌 │ 본심2-우 │
|
||||
│ 프로세스 │ 주체별 기대효과 │
|
||||
│ 변화 │ [팝업→] │
|
||||
│ • 불릿 │ • 발주자: ... │
|
||||
│ • 불릿 │ • 시공자: ... │
|
||||
│ │ • 설계자: ... │
|
||||
├───────────┴──────────────────────────────┤
|
||||
│ 결론 배너 │
|
||||
└──────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
핵심:
|
||||
- 상단: 텍스트(좌) + 이미지(우) 나란히
|
||||
- 하단: 2분할
|
||||
- 배경 없음, 첨부(sidebar) 없음
|
||||
- 이미지는 원본 위치에서 가져와서 컨테이너 크기에 맞게 비율 조정
|
||||
- 결론 항상 있음
|
||||
- PNG 구조를 따르되 디자인은 그대로 따라가기
|
||||
|
||||
---
|
||||
|
||||
## 진행 현황
|
||||
|
||||
### X-B-1: KEI_PROMPT에 유형 B 옵션 추가 — ✅ 완료
|
||||
- 기존 유형 A 지시 유지
|
||||
- 유형 B 옵션 추가: "배경/첨부가 없는 콘텐츠는 본심1+본심2+결론 구조"
|
||||
- Kei가 콘텐츠를 보고 A 또는 B 선택
|
||||
- 검증 결과:
|
||||
- 01번 MDX → A 선택 ✅
|
||||
- 02번 MDX → B 선택 ✅ (역할: DX의_궁극적_목표/프로세스_혁신/주체별_기대효과/결론)
|
||||
- 하드코딩 없음 ✅
|
||||
- 파일: `src/kei_client.py` — KEI_PROMPT 수정
|
||||
|
||||
### X-B-2: 검증기(validators.py) 완화 — ✅ 완료
|
||||
- 유형 A: "본심" 필수 유지
|
||||
- 유형 B: 결론(footer) 필수, 본심 불필요, 자유 역할명 허용
|
||||
- 섹션 수 차이 허용: 유형 A는 2, 유형 B는 4
|
||||
- 하드코딩 없음 ✅
|
||||
- 파일: `src/validators.py`
|
||||
|
||||
### X-B-3: space_allocator에 유형 B 컨테이너 생성 — 미착수
|
||||
- 유형 A: 기존 `calculate_container_specs` 그대로
|
||||
- 유형 B: `build_containers_type_b()` 추가
|
||||
- 상단: 전체폭, weight 비율
|
||||
- 하단: 2분할, weight 비율
|
||||
- 이미지: 상단 우측, 실제 비율로 크기 계산
|
||||
- 결론: footer
|
||||
- 파일: `src/space_allocator.py`
|
||||
|
||||
### X-B-4: assemble_stage2에 유형 B 조립 로직 추가 — 미착수
|
||||
- 유형 A: 기존 코드 그대로
|
||||
- 유형 B: 새 조립 함수
|
||||
- 상단: 좌측 텍스트(카테고리별 카드) + 우측 이미지
|
||||
- 하단: 2분할 불릿
|
||||
- 이미지 아래 캡션, 팝업 링크 우측상단
|
||||
- 결론 배너
|
||||
- 좌표: 동적 계산
|
||||
- 파일: `scripts/assemble_stage2.py`
|
||||
|
||||
### X-B-5: pipeline.py 분기 — 미착수
|
||||
- Stage 1.5a에서 `layout_template`에 따라 분기
|
||||
- "A" 또는 없음 → 기존 파이프라인
|
||||
- "B" → `build_containers_type_b()`
|
||||
- Stage 1A에서 `layout_template`를 Analysis에 저장 (pipeline_context.py에 필드 추가 필요)
|
||||
- 파일: `src/pipeline.py`, `src/pipeline_context.py`
|
||||
|
||||
### X-B-6: 검증 — 미착수
|
||||
- 02번 MDX → 유형 B 선택 → 상단+이미지+하단2분할+결론
|
||||
- 01번 MDX → 유형 A 선택 → 기존과 동일 (깨지면 안 됨)
|
||||
- 텍스트 컨테이너 안에 있음
|
||||
- 이미지 비율 맞음
|
||||
- 공란 최소
|
||||
- 하드코딩 없음
|
||||
|
||||
---
|
||||
|
||||
## Kei 유형 선택 결과 (검증 완료)
|
||||
|
||||
### 01번 MDX → 유형 A
|
||||
```
|
||||
layout_template: A
|
||||
page_structure:
|
||||
배경: topics=[1], weight=0.20
|
||||
본심: topics=[2,3,4], weight=0.55
|
||||
첨부: topics=[5,6], weight=0.15
|
||||
결론: topics=[7], weight=0.10
|
||||
```
|
||||
|
||||
### 02번 MDX → 유형 B
|
||||
```
|
||||
layout_template: B
|
||||
page_structure:
|
||||
DX의_궁극적_목표: zone=top, topics=[1], weight=0.45
|
||||
프로세스_혁신: zone=bottom_left, topics=[2], weight=0.25
|
||||
주체별_기대효과: zone=bottom_right, topics=[3], weight=0.20
|
||||
결론: zone=footer, topics=[4], weight=0.10
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 주의사항
|
||||
|
||||
- 유형 A 코드 건드리지 않음
|
||||
- 유형 B는 별도 함수/분기로 추가
|
||||
- 01번이 깨지면 롤백 (git: c9677a6)
|
||||
- 하드코딩 절대 금지
|
||||
- 각 단계 완료 후 반드시 검증 (하드코딩, 모면용 코드, 기존 깨짐 여부)
|
||||
@@ -30,15 +30,30 @@ KEI_PROMPT = (
|
||||
"## 3단계: 슬라이드 스토리라인 설계\n"
|
||||
"핵심 메시지를 전달하기 위한 **흐름**을 설계해줘.\n"
|
||||
"각 꼭지에 purpose를 부여하고, topics 배열에 기록.\n\n"
|
||||
"## 4단계: 페이지 구조 판단 (비중 시스템)\n"
|
||||
"콘텐츠를 분석하여 이 페이지의 **구조와 비중**을 판단하라:\n\n"
|
||||
"- **본심**: 이 페이지가 말하려는 핵심. 가장 큰 공간을 차지해야 함.\n"
|
||||
" 비교라면 비교표, 관계라면 관계도, 프로세스라면 흐름도로 구조화.\n"
|
||||
" 비교 구조일 때 비교 목적(왜 비교하는가)을 summary에 명시.\n"
|
||||
"- **배경**: 본심을 이해하기 위한 도입/배경. 간결하게. 2-3줄이면 충분.\n"
|
||||
"- **첨부**: 본심을 보조하는 참조 정보 (용어 정의 등). sidebar 배치.\n"
|
||||
" role: 'reference'로 표시. 본문 흐름을 방해하지 않도록.\n"
|
||||
"- **결론**: 절대 잊으면 안 되는 핵심 한 줄. footer.\n\n"
|
||||
"## 4단계: 레이아웃 유형 선택 + 페이지 구조 판단\n"
|
||||
"먼저 콘텐츠에 맞는 **레이아웃 유형**을 선택하라:\n\n"
|
||||
"### 유형 A: 배경 + 본심 + 첨부(sidebar) + 결론\n"
|
||||
"- 참조자료(용어 정의, 부록 등)가 **별도로 존재**하는 콘텐츠\n"
|
||||
"- 좌측 body(배경+본심) + 우측 sidebar(첨부) + 하단 결론\n"
|
||||
"- page_structure 키: 배경, 본심, 첨부, 결론\n\n"
|
||||
"### 유형 B: 본심1(상단) + 본심2(하단 2분할) + 결론\n"
|
||||
"- 참조자료 없이 **본문 흐름만**으로 구성되는 콘텐츠\n"
|
||||
"- 배경/첨부가 없거나 억지로 만들어야 하면 이 유형 선택\n"
|
||||
"- 상단: 핵심 내용 전체폭 (이미지가 있으면 좌텍스트+우이미지 나란히)\n"
|
||||
"- 하단: 세부 내용 2분할 (좌/우)\n"
|
||||
"- page_structure 키: 자유 (예: 핵심목표, 프로세스변화, 기대효과, 결론)\n"
|
||||
"- 결론 키는 반드시 '결론'\n\n"
|
||||
"선택한 유형을 **layout_template** 필드에 'A' 또는 'B'로 기록하라.\n\n"
|
||||
"### 역할별 규칙 (유형 A)\n"
|
||||
"- **본심**: 이 페이지가 말하려는 핵심. 가장 큰 공간.\n"
|
||||
"- **배경**: 본심을 이해하기 위한 도입. 간결하게.\n"
|
||||
"- **첨부**: 본심을 보조하는 참조 정보. sidebar 배치. role: 'reference'.\n"
|
||||
"- **결론**: 핵심 한 줄. footer.\n\n"
|
||||
"### 역할별 규칙 (유형 B)\n"
|
||||
"- 상단 역할: 핵심 내용. 전체폭. zone: 'top'\n"
|
||||
"- 하단 좌측: zone: 'bottom_left'\n"
|
||||
"- 하단 우측: zone: 'bottom_right'\n"
|
||||
"- 결론: zone: 'footer'\n\n"
|
||||
"각 역할에 해당하는 topic_ids와 **공간 비중(weight, 합계 1.0)**을 결정하라.\n"
|
||||
"**콘텐츠에 따라 비중은 매번 달라진다. 고정값이 아니다.**\n"
|
||||
"page_structure 필드에 기록.\n\n"
|
||||
@@ -56,11 +71,13 @@ KEI_PROMPT = (
|
||||
"- 1페이지 적정 꼭지: 5개. 분량 적으면 1페이지로.\n"
|
||||
"- **슬라이드 제목(title)과 첫 번째 꼭지 제목은 달라야 한다.** 슬라이드 제목은 전체 주제, 꼭지 제목은 해당 위치의 구체적 내용.\n\n"
|
||||
"## 출력 형식 (JSON만)\n"
|
||||
"layout_template에 따라 page_structure가 달라진다.\n\n"
|
||||
"유형 A 예시:\n"
|
||||
"```json\n"
|
||||
'{"title": "제목", '
|
||||
'"core_message": "이 슬라이드의 핵심 메시지 한 줄", '
|
||||
'"core_message": "핵심 메시지", '
|
||||
'"total_pages": 1, '
|
||||
'"info_structure": "정보 구조 설명", '
|
||||
'"layout_template": "A", '
|
||||
'"page_structure": {'
|
||||
'"본심": {"topic_ids": [2, 3], "weight": 0.60}, '
|
||||
'"배경": {"topic_ids": [1], "weight": 0.15}, '
|
||||
@@ -79,6 +96,20 @@ KEI_PROMPT = (
|
||||
'"images": [{"topic_id": 1, "role": "key|supporting", "has_text": false, "description": "이미지 설명"}], '
|
||||
'"tables": [{"topic_id": 2, "rows": 5, "cols": 3, "fits_single_page": true, "description": "표 설명"}]}\n'
|
||||
"```\n\n"
|
||||
"유형 B 예시:\n"
|
||||
"```json\n"
|
||||
'{"title": "제목", '
|
||||
'"core_message": "핵심 메시지", '
|
||||
'"total_pages": 1, '
|
||||
'"layout_template": "B", '
|
||||
'"page_structure": {'
|
||||
'"핵심목표": {"zone": "top", "topic_ids": [1], "weight": 0.35}, '
|
||||
'"프로세스변화": {"zone": "bottom_left", "topic_ids": [2], "weight": 0.25}, '
|
||||
'"기대효과": {"zone": "bottom_right", "topic_ids": [3], "weight": 0.25}, '
|
||||
'"결론": {"zone": "footer", "topic_ids": [4], "weight": 0.15}}, '
|
||||
'"topics": [...],'
|
||||
'"images": [...]}\n'
|
||||
"```\n\n"
|
||||
"## 콘텐츠:\n"
|
||||
)
|
||||
|
||||
|
||||
@@ -171,22 +171,38 @@ def validate_stage_1a(
|
||||
"instruction": f"weight 합이 1.0에 가깝도록 조정하라. 현재 합: {total_weight:.2f}",
|
||||
})
|
||||
|
||||
# 본심 존재 + 본심 weight ≥ 0.3
|
||||
core_info = page_struct.get("본심", {})
|
||||
if not core_info or not isinstance(core_info, dict):
|
||||
errors.append({
|
||||
"severity": "RETRYABLE",
|
||||
"field": "page_structure.본심",
|
||||
"localization": "본심 역할이 page_structure에 없음",
|
||||
"instruction": "page_structure에 본심 역할을 추가하라. 본심은 슬라이드의 핵심 콘텐츠이다.",
|
||||
})
|
||||
elif core_info.get("weight", 0) < 0.3:
|
||||
errors.append({
|
||||
"severity": "RETRYABLE",
|
||||
"field": "page_structure.본심.weight",
|
||||
"localization": f"본심 weight {core_info['weight']:.2f} < 0.3",
|
||||
"instruction": "본심은 슬라이드의 핵심. weight 0.3 이상 필요.",
|
||||
})
|
||||
# 유형에 따른 구조 검증
|
||||
layout_template = analysis.get("layout_template", "A")
|
||||
if layout_template == "A":
|
||||
# 유형 A: 본심 필수
|
||||
core_info = page_struct.get("본심", {})
|
||||
if not core_info or not isinstance(core_info, dict):
|
||||
errors.append({
|
||||
"severity": "RETRYABLE",
|
||||
"field": "page_structure.본심",
|
||||
"localization": "본심 역할이 page_structure에 없음",
|
||||
"instruction": "page_structure에 본심 역할을 추가하라. 본심은 슬라이드의 핵심 콘텐츠이다.",
|
||||
})
|
||||
elif core_info.get("weight", 0) < 0.3:
|
||||
errors.append({
|
||||
"severity": "RETRYABLE",
|
||||
"field": "page_structure.본심.weight",
|
||||
"localization": f"본심 weight {core_info['weight']:.2f} < 0.3",
|
||||
"instruction": "본심은 슬라이드의 핵심. weight 0.3 이상 필요.",
|
||||
})
|
||||
elif layout_template == "B":
|
||||
# 유형 B: 결론(footer) 필수, 나머지 자유
|
||||
has_footer = any(
|
||||
isinstance(info, dict) and info.get("zone") == "footer"
|
||||
for info in page_struct.values()
|
||||
)
|
||||
if not has_footer and "결론" not in page_struct:
|
||||
errors.append({
|
||||
"severity": "RETRYABLE",
|
||||
"field": "page_structure.footer",
|
||||
"localization": "결론(footer) 역할이 없음",
|
||||
"instruction": "유형 B에서도 결론 역할(zone: footer)은 필수이다.",
|
||||
})
|
||||
|
||||
# 필수 필드 검증
|
||||
for t in topics:
|
||||
@@ -226,7 +242,9 @@ def validate_stage_1a(
|
||||
if clean_text:
|
||||
# 원본 ## 섹션 수 vs topic 수 비교
|
||||
original_sections = re.findall(r"^## .+$", clean_text, re.MULTILINE)
|
||||
if len(original_sections) > 0 and abs(len(topics) - len(original_sections)) > 2:
|
||||
# 유형 B에서는 하나의 섹션을 여러 꼭지로 나눌 수 있으므로 허용 폭 확대
|
||||
max_diff = 4 if layout_template == "B" else 2
|
||||
if len(original_sections) > 0 and abs(len(topics) - len(original_sections)) > max_diff:
|
||||
errors.append({
|
||||
"severity": "RETRYABLE",
|
||||
"field": "topics",
|
||||
|
||||
Reference in New Issue
Block a user