- 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>
147 lines
6.0 KiB
Python
147 lines
6.0 KiB
Python
"""32 Figma frames 의 '후보 키워드' 전수 확인 + 통계.
|
||
|
||
출력:
|
||
- FIGMA_KEYWORDS_REPORT.md : 프레임별 후보 키워드 + 통계 + DF 기반 tier
|
||
|
||
용도: analysis.md 의 '후보 키워드' 필드가 어떻게 채워져 있는지 한눈 검수.
|
||
"""
|
||
import sys
|
||
import collections
|
||
from pathlib import Path
|
||
|
||
sys.path.insert(0, str(Path(__file__).parent))
|
||
|
||
from phase_common import load_32_frames, compute_df_idf_tier, load_frame_index
|
||
|
||
HERE = Path(__file__).parent
|
||
|
||
|
||
def main():
|
||
frames = load_32_frames()
|
||
df, idf, tier, N = compute_df_idf_tier(frames)
|
||
idx_data, frame_to_short = load_frame_index()
|
||
|
||
# 프레임 순 정렬 (short_id)
|
||
ordered = sorted(frames.items(), key=lambda kv: frame_to_short.get(kv[0], "99"))
|
||
|
||
lines = []
|
||
lines.append("# Figma 32 프레임 — 후보 키워드 검수 리포트")
|
||
lines.append("")
|
||
lines.append("**출처**: 각 프레임의 `figma_to_html_agent/blocks/{frame_id}/analysis.md` 의 `## 후보 키워드` 필드")
|
||
lines.append("")
|
||
lines.append(f"**작성자**: claude-opus-4-7 (2026-04-21, `tagged_by` 메타 참조)")
|
||
lines.append("")
|
||
lines.append(f"**생성 방법**: 각 프레임의 `texts.md` (MCP 추출 원본 텍스트 전수) + `flat.md` "
|
||
f"(bottom-up 평탄화 + 이상 탐지) 를 AI 가 읽고 15~25개 어휘를 수동 태깅.")
|
||
lines.append("")
|
||
|
||
# 1. 전체 통계
|
||
lines.append("## 1. 전체 통계")
|
||
lines.append("")
|
||
total_kws = sum(len(v["keywords"]) for _, v in frames.items())
|
||
unique_kws = len(set().union(*[set(v["keywords"]) for v in frames.values()]))
|
||
avg_kws = total_kws / len(frames)
|
||
core = [kw for kw in set().union(*[set(v["keywords"]) for v in frames.values()])
|
||
if tier.get(kw) == "core"]
|
||
mid = [kw for kw in set().union(*[set(v["keywords"]) for v in frames.values()])
|
||
if tier.get(kw) == "mid"]
|
||
general = [kw for kw in set().union(*[set(v["keywords"]) for v in frames.values()])
|
||
if tier.get(kw) == "general"]
|
||
lines.append(f"- 프레임 수: **{len(frames)}**")
|
||
lines.append(f"- 총 키워드 (중복 포함): **{total_kws}**")
|
||
lines.append(f"- 고유 키워드: **{unique_kws}**")
|
||
lines.append(f"- 프레임당 평균: **{avg_kws:.1f}개**")
|
||
lines.append("")
|
||
lines.append(f"- **core** (df ≤ 10%, 1~3 프레임 등장): **{len(core)}개** — 핵심 가중치 ×1.0")
|
||
lines.append(f"- **mid** (df 10~30%): **{len(mid)}개** — 가중치 ×0.5")
|
||
lines.append(f"- **general** (df ≥ 30%): **{len(general)}개** — 가중치 ×0.1")
|
||
lines.append("")
|
||
|
||
# 2. 프레임별 리스트
|
||
lines.append("## 2. 프레임별 후보 키워드")
|
||
lines.append("")
|
||
lines.append("| # | Frame | 제목 (content 1문장) | 키워드 수 | 키워드 |")
|
||
lines.append("|---|-------|---------------------|----------|--------|")
|
||
for fid, v in ordered:
|
||
sid = frame_to_short.get(fid, "?")
|
||
content = v.get("content", "").replace("\n", " ")[:60]
|
||
kws = v["keywords"]
|
||
kws_str = ", ".join(kws)
|
||
lines.append(f"| {sid} | {fid} | {content}… | {len(kws)} | {kws_str} |")
|
||
lines.append("")
|
||
|
||
# 3. tier 별 전체 키워드
|
||
lines.append("## 3. Tier 별 전체 키워드 (매칭 가중치 분류)")
|
||
lines.append("")
|
||
|
||
def sort_by_df(kws):
|
||
return sorted(kws, key=lambda k: (-df.get(k, 0), k))
|
||
|
||
lines.append(f"### 3-1. Core ({len(core)}개, df ≤ 10% — 가중치 ×1.0)")
|
||
lines.append("")
|
||
lines.append("특정 프레임에만 등장하는 고유 어휘. 매칭에서 결정적 신호.")
|
||
lines.append("")
|
||
core_sorted = sort_by_df(core)
|
||
lines.append(", ".join(f"`{k}`({df.get(k,0)})" for k in core_sorted[:80]))
|
||
if len(core_sorted) > 80:
|
||
lines.append(f"... (총 {len(core_sorted)}개)")
|
||
lines.append("")
|
||
|
||
lines.append(f"### 3-2. Mid ({len(mid)}개, df 10~30% — 가중치 ×0.5)")
|
||
lines.append("")
|
||
mid_sorted = sort_by_df(mid)
|
||
lines.append(", ".join(f"`{k}`({df.get(k,0)})" for k in mid_sorted))
|
||
lines.append("")
|
||
|
||
lines.append(f"### 3-3. General ({len(general)}개, df ≥ 30% — 가중치 ×0.1)")
|
||
lines.append("")
|
||
lines.append("여러 프레임에 등장해 변별력이 낮은 범용어.")
|
||
lines.append("")
|
||
general_sorted = sort_by_df(general)
|
||
lines.append(", ".join(f"`{k}`({df.get(k,0)})" for k in general_sorted))
|
||
lines.append("")
|
||
|
||
# 4. 어떻게 쓰는가
|
||
lines.append("## 4. 매칭에서의 활용 (Phase 23)")
|
||
lines.append("")
|
||
lines.append("```")
|
||
lines.append("MDX 본문 →")
|
||
lines.append(" synonyms.yaml 치환 →")
|
||
lines.append(" Kiwi 형태소 분석 (명사/영문/숫자만) →")
|
||
lines.append(" 위 vocab (32개 프레임 전체 키워드 합집합) 과 교집합 →")
|
||
lines.append(" MDX 키워드 셋 확보")
|
||
lines.append("")
|
||
lines.append("각 프레임 점수 = IDF 가중 Jaccard(MDX 키워드 셋, 프레임 키워드 셋)")
|
||
lines.append(" 여기서 각 키워드 기여도는 df 기반 tier 가중치 적용")
|
||
lines.append("```")
|
||
lines.append("")
|
||
lines.append("**사용처**:")
|
||
lines.append("- Phase 23 (키워드만), 24 (+내용), 25 (+구조) — **legacy 축**")
|
||
lines.append("- Phase 26 (template-fit-v1) 는 이 키워드 리스트 대신 "
|
||
"프레임별 `anchor_sets` (구조화된 named set) 사용")
|
||
lines.append("")
|
||
|
||
# 5. 원본 확인법
|
||
lines.append("## 5. 원본 파일 직접 확인")
|
||
lines.append("")
|
||
lines.append("각 프레임의 analysis.md 에 그대로 기록:")
|
||
lines.append("")
|
||
lines.append("```bash")
|
||
lines.append("cat figma_to_html_agent/blocks/1171281190/analysis.md")
|
||
lines.append("```")
|
||
lines.append("")
|
||
lines.append("전체 검수:")
|
||
lines.append("")
|
||
lines.append("```bash")
|
||
lines.append("grep -A 2 '## 후보 키워드' figma_to_html_agent/blocks/*/analysis.md")
|
||
lines.append("```")
|
||
lines.append("")
|
||
|
||
out = HERE / "FIGMA_KEYWORDS_REPORT.md"
|
||
out.write_text("\n".join(lines), encoding="utf-8")
|
||
print(f"완료: {out}")
|
||
|
||
|
||
if __name__ == "__main__":
|
||
main()
|