Phase U: 하드코딩 10건 제거 — 범용 프롬프트 시스템
제거: - _get_popup_data() 함수 삭제 (DX/BIM 비교표 하드코딩) - "📊 DX와 BIM의 상세 비교" 팝업 링크 → Claude 자율 판단 - "BIM ≠ DX" 예시 → core_message 변수만 - "상위개념/하위기술/포함관계" 금지어 → 범용 "임의 라벨 금지" - fallback 키워드 ["혼용","사례"], ["관계","핵심기술","DX"] → source_hint 동적 추출 - "사례 카드" → "토픽" 범용화 - "BIM (Building Information Modeling)" 예시 → 제거 추가: - _extract_keywords_from_hints(): source_hint에서 섹션명 키워드 동적 추출 - 팝업: 원본에 비교 구조 있으면 Claude가 자체 판단, 없으면 팝업 없음 - content_verifier: body_bg overflow 패턴 OR 수정, popup-link 필수 해제 회귀 테스트: 기존 MDX 전체 PASS (1차 시도) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -284,7 +284,7 @@ def detect_invented_text(
|
|||||||
"""
|
"""
|
||||||
# 허용 예외 (구조적 라벨)
|
# 허용 예외 (구조적 라벨)
|
||||||
allowed_labels = {
|
allowed_labels = {
|
||||||
"용어 정의", "핵심 메시지", "상세 비교", "DX와 BIM의 상세 비교",
|
"용어 정의", "핵심 메시지", "상세 비교",
|
||||||
}
|
}
|
||||||
|
|
||||||
html_texts = extract_text_from_html(generated_html)
|
html_texts = extract_text_from_html(generated_html)
|
||||||
@@ -377,12 +377,10 @@ def verify_no_forbidden_content(
|
|||||||
# ═══════════════════════════════════════════════════════════
|
# ═══════════════════════════════════════════════════════════
|
||||||
|
|
||||||
REQUIRED_PATTERNS: dict[str, list[str]] = {
|
REQUIRED_PATTERNS: dict[str, list[str]] = {
|
||||||
"body_bg": ["overflow:hidden", "overflow: hidden"],
|
"body_bg": ["overflow:hidden|overflow: hidden"],
|
||||||
"body_core": [
|
"body_core": [
|
||||||
"overflow:hidden|overflow: hidden",
|
"overflow:hidden|overflow: hidden",
|
||||||
"float:right|float: right",
|
|
||||||
"key-msg",
|
"key-msg",
|
||||||
"popup-link",
|
|
||||||
],
|
],
|
||||||
"sidebar": [
|
"sidebar": [
|
||||||
"overflow:hidden|overflow: hidden",
|
"overflow:hidden|overflow: hidden",
|
||||||
|
|||||||
@@ -52,10 +52,10 @@ BG_PROMPT = """다음 콘텐츠를 배경(보조) 영역 HTML로 만들어라.
|
|||||||
- 전체 padding: 10px 14px (여백 최소화)
|
- 전체 padding: 10px 14px (여백 최소화)
|
||||||
- 제목: 12px bold #334155, margin-bottom: 4px
|
- 제목: 12px bold #334155, margin-bottom: 4px
|
||||||
- 본문: 11px #475569, line-height: 1.4, 핵심 키워드 <strong style="color:#1e293b"> 처리
|
- 본문: 11px #475569, line-height: 1.4, 핵심 키워드 <strong style="color:#1e293b"> 처리
|
||||||
- 사례가 여러 건이면 가로로 나란히 (flex, gap:8px)
|
- 토픽이 여러 개이면 가로로 나란히 (flex, gap:8px)
|
||||||
- 사례 카드: background:#ffffff, border-left: 2px solid #94a3b8, padding: 6px 8px (여백 최소화)
|
- 각 토픽 구분: background:#ffffff, border-left: 2px solid #94a3b8, padding: 6px 8px (여백 최소화)
|
||||||
- 사례 제목: 10px bold #334155, margin-bottom: 2px
|
- 토픽 제목: 10px bold #334155, margin-bottom: 2px
|
||||||
- 사례 내용: 9px #64748b, line-height: 1.3
|
- 토픽 내용: 9px #64748b, line-height: 1.3
|
||||||
- 들여쓰기: 불릿은 인라인 style만 사용. CSS class 사용 금지 (<style> 블록 금지).
|
- 들여쓰기: 불릿은 인라인 style만 사용. CSS class 사용 금지 (<style> 블록 금지).
|
||||||
불릿 마커: 텍스트로 "• " 직접 삽입 (::before 금지)
|
불릿 마커: 텍스트로 "• " 직접 삽입 (::before 금지)
|
||||||
들여쓰기: style="padding-left:14px; text-indent:-14px;" 인라인으로.
|
들여쓰기: style="padding-left:14px; text-indent:-14px;" 인라인으로.
|
||||||
@@ -174,7 +174,6 @@ CORE_PROMPT = """다음 콘텐츠를 본심 영역 HTML로 만들어라.
|
|||||||
<div class="core">
|
<div class="core">
|
||||||
<div class="core-header">
|
<div class="core-header">
|
||||||
<div class="core-label">제목 라벨</div>
|
<div class="core-label">제목 라벨</div>
|
||||||
<span class="popup-link">📊 DX와 BIM의 상세 비교</span>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="core-text">
|
<div class="core-text">
|
||||||
<div class="fi">
|
<div class="fi">
|
||||||
@@ -184,16 +183,14 @@ CORE_PROMPT = """다음 콘텐츠를 본심 영역 HTML로 만들어라.
|
|||||||
<div class="bp">메인 불릿 1</div>
|
<div class="bp">메인 불릿 1</div>
|
||||||
<div class="bp">메인 불릿 2</div>
|
<div class="bp">메인 불릿 2</div>
|
||||||
<div class="sp"><b>하위 항목</b> : 설명</div>
|
<div class="sp"><b>하위 항목</b> : 설명</div>
|
||||||
<div class="sp"><b>하위 항목</b> : 설명</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="key-msg">
|
<div class="key-msg">
|
||||||
<em>BIM ≠ DX</em> — 핵심 메시지 (analysis.core_message 사용)
|
핵심 메시지 텍스트
|
||||||
</div>
|
</div>
|
||||||
주의: "상위개념", "하위기술", "포함관계" 같은 임의 라벨을 넣지 마라. core_message 텍스트를 그대로 사용.
|
|
||||||
</div>
|
</div>
|
||||||
```
|
```
|
||||||
|
|
||||||
## 핵심 메시지 (하단 key-msg 박스에 이 텍스트 그대로 사용)
|
## 핵심 메시지 (하단 key-msg 박스에 이 텍스트 그대로 사용. 임의 라벨/요약을 넣지 말고 아래 텍스트 그대로.)
|
||||||
{core_message}
|
{core_message}
|
||||||
|
|
||||||
## 콘텐츠 (축약/요약/삭제 금지. 원본 텍스트 80-95% 그대로 사용.)
|
## 콘텐츠 (축약/요약/삭제 금지. 원본 텍스트 80-95% 그대로 사용.)
|
||||||
@@ -204,17 +201,19 @@ CORE_PROMPT = """다음 콘텐츠를 본심 영역 HTML로 만들어라.
|
|||||||
2. 마침표(.)로 끝나는 문장이 2개 이상이면 각각 별도 불릿(•)으로 분리.
|
2. 마침표(.)로 끝나는 문장이 2개 이상이면 각각 별도 불릿(•)으로 분리.
|
||||||
3. 개조식 어미 변환: 문장 끝 1-2글자만 변환. 그 외 단어는 절대 건드리지 마라.
|
3. 개조식 어미 변환: 문장 끝 1-2글자만 변환. 그 외 단어는 절대 건드리지 마라.
|
||||||
- "~있다" → "~있음", "~한다" → "~함", "~이다" → 삭제, "~된다" → "~됨"
|
- "~있다" → "~있음", "~한다" → "~함", "~이다" → 삭제, "~된다" → "~됨"
|
||||||
예: "실현 가능한 상위개념이다" → "실현 가능한 상위개념" (끝의 "이다"만 삭제)
|
예: "활용하는 과정이다" → "활용하는 과정" (끝만 변환)
|
||||||
4. 원본에 없는 텍스트를 추가하지 마라.
|
4. 원본에 없는 텍스트를 추가하지 마라.
|
||||||
|
|
||||||
{img_instruction}
|
{img_instruction}
|
||||||
|
|
||||||
## 팝업 비교표 데이터 (<details>/<summary>로 구현)
|
## 팝업 (<details>/<summary>)
|
||||||
{popup_data}
|
원본 콘텐츠에 비교표/대조 구조가 있으면 <details>/<summary>로 팝업 구현하라.
|
||||||
|
없으면 팝업을 만들지 마라. 팝업 제목은 콘텐츠에 맞게 자동 결정.
|
||||||
|
팝업은 우측 상단에 1개만. 중복 금지.
|
||||||
|
|
||||||
## 절대 규칙
|
## 절대 규칙
|
||||||
- "DX와 BIM의 상세 비교" 팝업 링크는 우측 상단에 1개만. 하단이나 다른 곳에 중복으로 넣지 마라.
|
|
||||||
- .core에 margin-top을 넣지 마라 (간격은 외부에서 처리됨). margin-top: 0 필수.
|
- .core에 margin-top을 넣지 마라 (간격은 외부에서 처리됨). margin-top: 0 필수.
|
||||||
|
- 원본에 없는 텍스트나 라벨을 임의로 넣지 마라.
|
||||||
|
|
||||||
HTML + inline <style>만 반환. 위 CSS와 HTML 구조를 정확히 따르라. 설명 없이 코드만."""
|
HTML + inline <style>만 반환. 위 CSS와 HTML 구조를 정확히 따르라. 설명 없이 코드만."""
|
||||||
|
|
||||||
@@ -229,7 +228,7 @@ SIDEBAR_PROMPT = """다음 용어 정의를 sidebar 카드로 만들어라. {wid
|
|||||||
2. 상단에 "용어 정의" 라벨 (카드 좌측 시작점에 맞춰 좌측 정렬, 10px #64748b)
|
2. 상단에 "용어 정의" 라벨 (카드 좌측 시작점에 맞춰 좌측 정렬, 10px #64748b)
|
||||||
3. 각 용어를 카드로:
|
3. 각 용어를 카드로:
|
||||||
- 배경: #f8fafc, 테두리: 1px solid #e2e8f0, border-radius: 8px, padding: 14px
|
- 배경: #f8fafc, 테두리: 1px solid #e2e8f0, border-radius: 8px, padding: 14px
|
||||||
- 용어명: 11px bold #1e293b (예: "BIM (Building Information Modeling)")
|
- 용어명: 11px bold #1e293b
|
||||||
- 부제 금지: 원본에 없는 텍스트를 만들어 넣지 마라. 용어명 아래에 임의 설명을 추가하지 마라.
|
- 부제 금지: 원본에 없는 텍스트를 만들어 넣지 마라. 용어명 아래에 임의 설명을 추가하지 마라.
|
||||||
- 불릿: 10px #475569, line-height: 1.6, 불릿 마커 "•"
|
- 불릿: 10px #475569, line-height: 1.6, 불릿 마커 "•"
|
||||||
- 들여쓰기: 인라인 style만 사용 (CSS class 사용 금지).
|
- 들여쓰기: 인라인 style만 사용 (CSS class 사용 금지).
|
||||||
@@ -319,7 +318,9 @@ async def generate_slide_html(
|
|||||||
if bg_topics:
|
if bg_topics:
|
||||||
logger.info("[Phase S] 배경 생성...")
|
logger.info("[Phase S] 배경 생성...")
|
||||||
sections = _slice_mdx_sections(content)
|
sections = _slice_mdx_sections(content)
|
||||||
bg_content = _map_sections_for_role(sections, bg_topics, ["혼용", "사례"])
|
bg_content = _map_sections_for_role(
|
||||||
|
sections, bg_topics, _extract_keywords_from_hints(bg_topics),
|
||||||
|
)
|
||||||
prompt = BG_PROMPT.format(
|
prompt = BG_PROMPT.format(
|
||||||
height=bg_h,
|
height=bg_h,
|
||||||
content_block=bg_content,
|
content_block=bg_content,
|
||||||
@@ -332,8 +333,9 @@ async def generate_slide_html(
|
|||||||
# ── 본심 ──
|
# ── 본심 ──
|
||||||
if core_topics:
|
if core_topics:
|
||||||
logger.info("[Phase S] 본심 생성...")
|
logger.info("[Phase S] 본심 생성...")
|
||||||
core_content = _map_sections_for_role(sections, core_topics, ["관계", "핵심기술", "DX"])
|
core_content = _map_sections_for_role(
|
||||||
popup = _get_popup_data(content)
|
sections, core_topics, _extract_keywords_from_hints(core_topics),
|
||||||
|
)
|
||||||
|
|
||||||
img_instruction = ""
|
img_instruction = ""
|
||||||
img_margin = 60
|
img_margin = 60
|
||||||
@@ -354,7 +356,6 @@ async def generate_slide_html(
|
|||||||
core_message=analysis.get("core_message", ""),
|
core_message=analysis.get("core_message", ""),
|
||||||
content_block=core_content,
|
content_block=core_content,
|
||||||
img_instruction=img_instruction,
|
img_instruction=img_instruction,
|
||||||
popup_data=popup,
|
|
||||||
)
|
)
|
||||||
html = await _call_claude(client, prompt)
|
html = await _call_claude(client, prompt)
|
||||||
if html:
|
if html:
|
||||||
@@ -470,15 +471,28 @@ def _map_sections_for_role(
|
|||||||
return result.strip()
|
return result.strip()
|
||||||
|
|
||||||
|
|
||||||
def _get_popup_data(content: str) -> str:
|
def _extract_keywords_from_hints(topics: list[dict]) -> list[str]:
|
||||||
"""팝업 비교표 데이터."""
|
"""source_hint에서 섹션명 키워드를 동적 추출.
|
||||||
return """비교표 (<details>/<summary> 팝업으로):
|
|
||||||
| 기준 | DX | BIM |
|
예: "원본의 '용어의 혼용' 섹션 전체 내용" → ["용어의", "혼용"]
|
||||||
| 범위 | BIM << DX (Engineering + Management 통합) | Only 3D (형상 구현 중심) |
|
하드코딩 fallback 키워드를 대체한다.
|
||||||
| 프로세스 | 근본적 문제의식을 통한 개선 | 기존 2D 설계 방식 유지 |
|
"""
|
||||||
| 활용 | 설계/시공 생산성 혁신 | 3D 모델에 의한 일반적 이해 향상 |
|
keywords = []
|
||||||
| 확장성 | 전 생애주기 활용 시스템 | (설계/시공/운영) 분야별 단절 |
|
for t in topics:
|
||||||
| 주체 | 자체 수행 능력 — 지속가능성 확보 | S/W 제작사 판매 정책에 의존 |"""
|
hint = t.get("source_hint", "")
|
||||||
|
if not hint:
|
||||||
|
continue
|
||||||
|
# 따옴표 안의 섹션명 추출
|
||||||
|
match = re.search(r"['\"]([^'\"]+)['\"]", hint)
|
||||||
|
if match:
|
||||||
|
section_name = match.group(1)
|
||||||
|
words = [w for w in section_name.split() if len(w) >= 2]
|
||||||
|
keywords.extend(words)
|
||||||
|
else:
|
||||||
|
# 따옴표 없으면 hint에서 2글자 이상 단어 추출
|
||||||
|
words = [w for w in hint.split() if len(w) >= 2]
|
||||||
|
keywords.extend(words[:3]) # 최대 3개
|
||||||
|
return keywords
|
||||||
|
|
||||||
|
|
||||||
def _get_definitions(content: str) -> str:
|
def _get_definitions(content: str) -> str:
|
||||||
|
|||||||
Reference in New Issue
Block a user