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:
@@ -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()
|
||||
Reference in New Issue
Block a user