wip: phase_z2 evidence 파이프라인 + matching 실험(phase2~26) + 프론트 trace 패널 진행분 스냅샷

- src: phase_z2 composition/mapper/pipeline/placement_planner/retry, ai_fallback(prompts/schema/validate), mdx_text_atoms 신규
- Front: PipelineTracePanel 신규, FramePanel/SlideCanvas/Home/designAgentApi 등 갱신 + 테스트 4종 추가
- templates/phase_z2: catalog(component_expansion_registry, node_slot_mapping 신규), frames, families, slide_base 갱신
- tests/matching: phase2~26 매칭 실험 스크립트·리포트·온톨로지 전체 (미커밋 진행분)
- tests: b4_v4 evidence, task5~28.5 시리즈, regression(imp95 baseline) 등 신규 테스트 대량 추가
- docs/reference: MDX 구조 인벤토리, MDX→Frame 구조 계약 문서
- scripts: mdx 계약/parity/coverage/viewport 체크, gitea comment, run sync 유틸
- .gitignore: tmp*.json, chromedriver, .orchestrator, *.pkl, Front_test* 등 임시/스냅샷 제외

미완성 작업의 보존용 스냅샷 커밋 (2026-07-02)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-02 17:03:42 +09:00
co-authored by Claude Opus 4.8
parent 97b7833a1b
commit b836e79ee1
527 changed files with 673036 additions and 717 deletions
@@ -0,0 +1,250 @@
"""Milestone 2 / Step 2: analysis.md 를 structure_ontology.yaml + keyword_base.yaml 기준으로 재작성.
원칙:
source of truth → structure_ontology.yaml + keyword_base.yaml
human review mirror → figma_to_html_agent/blocks/{fid}/analysis.md
각 frame 의 analysis.md 는 template-fit-v1 정보를 사람이 읽기 좋은 형식으로 반영.
원본은 .pre_milestone2.bak 로 백업.
생성 포맷:
# Frame {fid}{title}
## 내용 설명 (templates_v1[fid].description)
## 후보 키워드 (모든 anchor_sets terms flat)
## 정제 Anchor Sets (set_id + terms)
## 구조 매칭 정보 (visual_pattern + slots)
## 적합/부적합 기준 (fit_notes.suits / not_suits)
## 재구성 허용 (adaptation_allowed)
## 제외된 키워드 (pre_milestone2 대비 제거된 terms + 사유)
## 메타 (schema_version, source, synced_at)
"""
import sys
import shutil
from pathlib import Path
from datetime import date
import yaml
sys.path.insert(0, str(Path(__file__).parent))
ROOT = Path(r"d:\ad-hoc\kei\design_agent")
BLOCKS_DIR = ROOT / "figma_to_html_agent" / "blocks"
HERE = Path(__file__).parent
def load_keyword_base_excluded():
"""keyword_base.yaml 의 excluded 를 (term → (bucket, reason)) 딕트로."""
path = HERE / "keyword_base.yaml"
with open(path, encoding='utf-8') as f:
data = yaml.safe_load(f)
out = {}
for e in data.get('excluded', []):
out[e['term']] = {'bucket': e['bucket'], 'reason': e['reason']}
return out
def load_ontology(path):
with open(path, encoding='utf-8') as f:
return yaml.safe_load(f)
def get_removed_terms_per_frame(current_templates, backup_templates):
"""Pre-cleanup 대비 각 frame 에서 제거된 anchor terms."""
result = {}
for fid, cur_tpl in current_templates.items():
if fid not in backup_templates:
continue
cur_terms = set()
for s in cur_tpl.get('anchor_sets', []):
cur_terms.update(s.get('terms', []))
bak_terms = set()
for s in backup_templates[fid].get('anchor_sets', []):
bak_terms.update(s.get('terms', []))
removed = bak_terms - cur_terms
result[fid] = sorted(removed)
return result
def compose_analysis_md(fid, tpl, removed_terms, excluded_info):
"""analysis.md 본문 문자열 생성."""
title = tpl.get('source', {}).get('title', '?')
original_layout = tpl.get('source', {}).get('original_layout', '?')
lines = []
lines.append(f"# Frame {fid}{title}")
lines.append("")
# 1. 내용 설명
lines.append("## 내용 설명")
lines.append("")
desc = tpl.get('description', '').strip()
lines.append(desc)
lines.append("")
# 2. 후보 키워드 (flat)
lines.append("## 후보 키워드")
lines.append("")
all_terms = []
seen = set()
for s in tpl.get('anchor_sets', []):
for t in s.get('terms', []):
if t not in seen:
seen.add(t)
all_terms.append(t)
lines.append(", ".join(all_terms) if all_terms else "_(없음)_")
lines.append("")
# 3. 정제 Anchor Sets
lines.append("## 정제 Anchor Sets")
lines.append("")
for s in tpl.get('anchor_sets', []):
set_id = s.get('id', '?')
terms = ", ".join(s.get('terms', []))
extras = []
if 'min_hits' in s:
extras.append(f"min_hits={s['min_hits']}")
if 'confidence_cap' in s:
extras.append(f"cap={s['confidence_cap']}")
if 'cap_exempt_if_corroborated_by' in s:
extras.append(f"exempt_if≥{s['cap_exempt_if_corroborated_by']}")
extra_str = f" _[{', '.join(extras)}]_" if extras else ""
lines.append(f"- **{set_id}**: {terms}{extra_str}")
if 'note' in s:
note = str(s['note']).replace('\n', ' ').strip()
lines.append(f" - note: {note[:200]}")
lines.append("")
# 4. 구조 매칭 정보
lines.append("## 구조 매칭 정보")
lines.append("")
vp = tpl.get('visual_pattern', {})
card = vp.get('cardinality', {})
lines.append(f"- **family**: {vp.get('family', '?')}")
lines.append(f"- **layout**: {vp.get('layout', '?')}")
lines.append(f"- **axis**: {vp.get('axis', '?')}")
lines.append(f"- **relation_type**: {vp.get('relation_type', '?')}")
if card:
lines.append(f"- **cardinality**: ideal {card.get('ideal','?')} / "
f"min {card.get('min','?')} / max {card.get('max','?')}")
slots = tpl.get('slots', [])
if slots:
slot_ids = ", ".join(s.get('id', '?') for s in slots)
required = sum(1 for s in slots if s.get('required'))
lines.append(f"- **slots** ({len(slots)}개, required {required}개): {slot_ids}")
lines.append(f"- **source title**: {title}")
lines.append(f"- **original layout**: {original_layout}")
lines.append("")
# 5. 적합/부적합 기준
fit = tpl.get('fit_notes', {})
if fit:
lines.append("## 적합/부적합 기준")
lines.append("")
suits = fit.get('suits', [])
if suits:
lines.append("### suits")
for s in suits:
lines.append(f"- {s}")
lines.append("")
not_suits = fit.get('not_suits', [])
if not_suits:
lines.append("### not_suits")
for s in not_suits:
lines.append(f"- {s}")
lines.append("")
# 6. 재구성 허용
adapt = tpl.get('adaptation_allowed', {})
if adapt:
lines.append("## 재구성 허용")
lines.append("")
for k in ['split', 'merge', 'infer_missing_slot', 'rewrite_label', 'rewrite_body']:
if k in adapt:
lines.append(f"- **{k}**: {adapt[k]}")
lines.append("")
# 7. 제외된 키워드 (Milestone 2 정리 결과)
if removed_terms:
lines.append("## 제외된 키워드")
lines.append("")
lines.append("Milestone 2 anchor_sets 정리 시 이 frame 에서 제거된 term 들. "
"구조/레이아웃 서술어·row label·summary label 등 evidence-based 기준으로 제외.")
lines.append("")
# Frame 20 특수 케이스: 필수성 → 필수 대체
for t in removed_terms:
info = excluded_info.get(t, {})
bucket = info.get('bucket', '(대체 or unknown)')
reason = info.get('reason', '')[:80]
# 필수성 특별 케이스
if fid == '1171281198' and t == '필수성':
lines.append(f"- `{t}`: **대체** — Frame 20 실제 텍스트 'S/W가 필수다' 근거로 `필수` 로 치환")
else:
lines.append(f"- `{t}`: {bucket}{reason}")
lines.append("")
# 8. 메타
lines.append("## 메타")
lines.append("")
lines.append(f"- schema_version: template-fit-v1 mirror")
lines.append(f"- source_of_truth: structure_ontology.yaml + keyword_base.yaml")
lines.append(f"- structure_content_original_tagged_by: claude-opus-4-7 (2026-04-21)")
lines.append(f"- keyword_base_sync_at: {date.today().isoformat()}")
lines.append(f"- anchor_sets_cleaned_at: {date.today().isoformat()}")
lines.append("")
return "\n".join(lines)
def main():
print("[1] keyword_base.yaml excluded 로드...")
excluded_info = load_keyword_base_excluded()
print(f" excluded terms: {len(excluded_info)}")
print("[2] 현재 structure_ontology.yaml 로드...")
cur_path = HERE / "structure_ontology.yaml"
cur_onto = load_ontology(cur_path)
cur_templates = cur_onto.get('templates_v1', {})
print(f" templates_v1: {len(cur_templates)}")
print("[3] 백업 (pre_milestone2) 로드...")
bak_path = HERE / "structure_ontology.yaml.pre_milestone2.bak"
if not bak_path.exists():
print(f" WARN: 백업 없음. 제거된 키워드 섹션은 비움")
bak_templates = {}
else:
bak_onto = load_ontology(bak_path)
bak_templates = bak_onto.get('templates_v1', {})
print(f" backup templates_v1: {len(bak_templates)}")
print("[4] 프레임별 제거된 terms 계산...")
removed_map = get_removed_terms_per_frame(cur_templates, bak_templates)
total_removed = sum(len(v) for v in removed_map.values())
print(f" 총 제거된 term: {total_removed} (중복 포함)")
print("[5] 32 frame analysis.md 재작성...")
updated = 0
for fid, tpl in cur_templates.items():
frame_dir = BLOCKS_DIR / fid
if not frame_dir.exists():
print(f" WARN: {fid} 폴더 없음, skip")
continue
analysis_path = frame_dir / "analysis.md"
# 백업
if analysis_path.exists():
bak = frame_dir / "analysis.md.pre_milestone2.bak"
if not bak.exists():
shutil.copy(analysis_path, bak)
# 재작성
content = compose_analysis_md(fid, tpl, removed_map.get(fid, []), excluded_info)
analysis_path.write_text(content, encoding='utf-8')
updated += 1
print(f" 재작성: {updated}/{len(cur_templates)}")
print("\n완료.")
print(f"각 frame 의 analysis.md.pre_milestone2.bak 가 원본 보존용으로 생성됨.")
if __name__ == "__main__":
main()