mordor-wiki projects/mordor/visual-summary · 기준 커밋 a0f8b01 · 2026-10-22 갱신 · 저자 홍지혜(jhhong) · 문서 v1.1.1 · 규약 폴더 전량 · 찾기 · 이전판

Visual Summary 규약 명세

규약 저장소 한 폴더를 통째로 옮겨 한 권으로 엮었다. 흩어진 57편을 9부 57장으로 세우고, 문서끼리 걸려 있던 링크 1,050개를 이 책 안의 자리로 이었다. 원문을 줄이거나 고쳐 쓰지 않았다 — 순서와 길만 새로 냈다.

9부 57장원문 Markdown 57편 전량
1,180개절 — 모두 주소가 있다
1,167,638자원문 분량
1,050개책 안으로 이은 링크
80개용어 색인
69건Jira 티켓 색인

책 전체에서 찾는다 — 장 제목, 절 제목, 본문까지. 찾은 자리를 누르면 접힌 장이 열리면서 그리로 가고, 찾던 말에 표시가 붙는다.

낱말을 띄어 쓰면 모두 든 곳만 남는다. 대소문자는 가리지 않는다. 어디서든 / 를 누르면 이 칸으로 오고, ↑↓ 로 고르고 Enter 로 연다. Esc 는 비운다.

이 책을 읽는 법

장은 접혀 있다. 제목 줄을 누르면 펼쳐진다. 목차·색인·본문 어디서든 장이나 절로 가는 링크를 누르면 접힌 것이 저절로 열리고 그 자리로 간다. 주소창의 # 뒤 부분을 그대로 복사해 공유하면 받는 사람도 같은 자리에서 열린다.

문서마다 무게가 다르다. 제목 옆 배지가 그 문서를 얼마나 믿어도 되는지 알려 준다. 같은 사실이 두 곳에서 다르게 적혀 있으면 정본 쪽이 맞다.

배지뜻다르면 누가 이기나
정본그 주제를 정하는 문서 (SSoT)이 문서가 이긴다
파생정본에서 뽑아 만든 사본 — 보고서·덱·요약정본이 이긴다
초안아직 확정되지 않은 제안결정이 아니다
계획하기로 했지만 착수 전현재 구현이 아니다
참고밖에서 가져온 사실의 사본원 출처가 이긴다
보존대체된 옛 판지금 규약은 이것을 따르지 않는다
링크 모양어디로 가나
밑줄만 있는 링크이 책 안의 다른 장·절. 새 창을 열지 않는다
점선 + ↗GitHub 의 원문, 또는 이 책에 싣지 않은 파일(CSV·PDF·스크립트·그림)
점선만Jira·Slack·Confluence 같은 바깥 자료
없음원문이 가리키지만 이 커밋에는 없는 경로다. 원문 쪽 사정이고 고치지 않았다

원문에 있던 그림 파일(report/sections/img/ 의 PNG 10장)과 PDF·CSV 는 이 책에 싣지 않고 GitHub 링크로 남겼다. 대신 핵심 구조는 아래처럼 글자가 선택되는 그림으로 다시 그렸다.

한 장으로 보는 전체 구조

규약을 읽기 전에 이 그림 하나만 머리에 넣으면 나머지가 제자리를 찾는다. 갈래가 둘이고, 둘은 끝에서 만난다.

질의 쪽 — 사용자가 입력한 글 사용자 입력 input_text Summary 생성기 Markdown Summary현행 · 1·2단계 Visual SummaryYAML · 3단계 이후 Layout Description→ Taxonomy 분류 structureHash[]buildCompSearchData() 질의 쪽 semantic tagv7 중분류 1개 이상 색인 쪽 — 이미 있는 템플릿 템플릿 페이지 layouts ALSC → Structuriser → RLSC 유효 컴포넌트 게이트 v3.4 / v3.5design_objects · 컴포넌트 83,771건 comp-signbuildCompSignatures() 컴포넌트 semantic 태깅rule + LLM Search Signature v2최대 3건 / 컴포넌트 design_object_semantic_tags 둘이 만나는 곳 ① 구조로 거른다structure_hash IN (…) ② 태그 군으로 좁힌다0군 → 1군 → 2군 ③ 채점rankCompCandidates() 컴포넌트 확정군이 점수보다 먼저 내용 대치 buildContentReplacement() RLSC 가 슬롯 구조를 준다
사용자 입력은 RLSC 가 되지 않는다. 질의 쪽은 입력을 검색 조건으로 바꾸고, 색인 쪽은 이미 있는 템플릿을 미리 검색 가능한 형태로 뽑아 둔다. 둘은 구조로 거르는 자리와 태그로 좁히는 자리 두 곳에서 만난다. structure 에서 RLSC 로 가는 화살표는 변환이 아니라 capability 대조다. 출처는 3부 10장이다.

처음이라면 이 순서로

57장을 앞에서부터 읽을 필요는 없다. 아래 일곱 걸음이면 전체 뼈대가 선다.

1

왜 하는가

현행 Markdown Summary 가 표·시간·순서를 뭉갠다. 55장 공유 덱이 가장 빠르고, 근거는 38장 비교 샘플에 있다.

2

무엇을 만드는가

10장 전체 흐름. 질의 쪽과 색인 쪽이 따로 돌다가 BE 검색에서 만난다. 위 그림이 이 장의 요약이다.

3

규약의 뼈대

4장 스키마 v0.5. component 와 structure 가 무엇이고 LLM 이 YAML 을 어떻게 쓰는지.

4

45종이 각각 무엇을 받나

5장 capability 테이블. 성립 조건·선택 속성·fallback 순서가 전부 여기 있다. 실물 예시는 6장.

5

빈 결과를 어떻게 막나

5장 3절 fallback과 27장 fallback map v2. 규약이 보장하는 것은 「빈 화면을 보내지 않는다」 한 줄이다.

6

고른 다음에 무엇을 하나

11장 내용 대치 contract. 어느 글이 어느 슬롯에 들어가는지의 계약이다.

7

잘 되고 있나

4부 실측. 가설을 세우고 재고, 틀렸던 기록까지 남긴 자료다.

I.I.S. 는 어디에 나오나

I.I.S.(Incremental Information Structuriser)는 한 장에 모여 있지 않고 설계·계획·참고에 흩어져 있다. 한자리에 모은다.

어디무엇이 있나
12장 정합성 게이트 정본설계 SSoT. 무엇을 거르고, GC 게이트와 무엇이 다르고, 어디에 저장하고, 언제 도는가
1장 5절프로젝트에서 I.I.S. 가 맡는 자리
10장4단계에서 색인 쪽에 무엇이 더해지는가
22장 4절진행 방침 (2026-08-22)과 5단계 property 대치
25장 9절경계 지도와 속성 지도 계약 — I.I.S. 가 내놓는 두 산출물
31장속성 골든셋을 사람이 확정하는 편집 화면 설계
42장iui.comp_frames 10만 건에 I.I.S. 결과가 빠졌던 사고와 재적재 기록
사전 · I.I. Structuriser (IIS)이 사전 안의 구현·운영 설명. 규약이 아니라 「지금 돌고 있는 것」이다
사전 · RLSC — 컴포넌트의 일생I.I.S. 앞뒤의 공급 흐름 전체

지금 상태 — 원문에서 자동으로 끌어온 표

아래 세 표는 손으로 옮기지 않는다. sync-sources.py 가 mordor-wiki origin/main 을 읽어 그대로 끼워 넣고, 원본이 바뀌면 낡았다고 알려 준다. 본문 각 장은 기준 커밋 a0f8b01 에 고정돼 있으므로 이 표가 더 최신일 수 있다.

로드맵 5단계, 지금 어디까지 왔나

단계 무엇이 바뀌는가 목표 시점 지금 상태
1단계 (현행)Markdown Summary, 임베딩 검색지금운영 중, 변동 없음
2단계컴포넌트에 Semantic Component Category2026-08-18 사실상 스킵2026-08-07 확정. 4단계로 직행
3단계YAML + structure 검색없음실행하지 않음 (2026-08-06 Cancelled)
4단계RLSC node 태그로 정확한 범위 특정단독 단계로는 건너뛴다 (2026-09-08 확정)실행 티켓 여섯이 Cancelled. 끝난 산출물(fallback map, 꾸밈 축 등)은 5단계가 그대로 쓴다
5단계node 에 semantic category 속성까지여기로 직행한다 (2026-09-08 확정, 정원석)경계 4,764건·속성 3,955건 로컬 파일럿(09-06), 노드 검색 엔진 배선(09-07). 정확도 판정(K-9-1)과 10만 건 추출(MOR-2415)이 남았다

↻ 로드맵 5단계 현재 상태 · DASHBOARD.md — 원본에서 자동 동기화

원본 커밋 61bfcfa6b268

절별 남은 일

절 무엇을 다루는가 마감 남은 것
A규약 확정2026-08-071건 (A-4)
B규약 열린 결정A 에 종속0건
C2단계 배포사실상 스킵 (2026-08-07)2건 (C-6, C-7)
D스쿼드 차원에서 정할 것각각 다름1건 (D-8)
E7/24 회의 후속미정3건
F사후 검증 (마감 후로 미룸)마감 후1건 (F-2)
G최근 해소된 것없음기록만
H문서 정합 백로그없음3건
I내용 대치 contract미정14건
J생성 모델 파인튜닝착수 전8건
KWeek 27 PoCweek 272건 (K-9-1, K-11)

↻ 절별 남은 일 · DASHBOARD.md — 원본에서 자동 동기화

원본 커밋 61bfcfa6b268

v0.4 → v0.5 변경 이력

# v0.4 v0.5
1LLM 이 JSON 을 생성하고 YAML 로 직렬화LLM 이 YAML 을 직접 생성한다. 이유는 추론 속도다
2최상위가 structure(단일) 또는 components[](다중)항상 components[] 배열이다. 하나면 길이 1이다
3component.type enum 11종type 문자열 하나, 대분류.중분류 결합값 45종. 종전에는 대분류 21값을 더해 62값이었으나 2026-08-29 에 뺐다 (6절 상자). v7 단어장 49종에서 지도 전용 4종을 뺀 45종이다 — 지도는 지원하지 않는다(capability-table.md 범위 절)
4component.confidence optional제거했다
5없음alternatives[] 를 열었다. 못 쓸 때 대신 쓸 카테고리를 선호 순서로 적는다
64-1절이 traits(계층, 장식) 도입을 제안도입하지 않는다 → 2026-08-03 에 뒤집혔다 (아래 상자). traits 묶음은 두지 않고 꾸밈은 decoration, 계층은 entity.components[] 로 나눠 갔다. 2026-08-04 에 2절에 반영했다
7dataset.type 3종(gauge, stacked_bar, combo)dataset.shape 2종(measure, matrix)
8relation.type 1종(intersects)2종(intersects, compares)
9entity.id 필수생성물에서 뺐다. 파싱 뒤 기계가 매긴다 (3.2절)
10없음page 수준 relations 를 예약했다. Summary 생성기는 채우지 않는다
113절에 capability 테이블 11행별도 문서 41행으로 분리했다
1211절에 열린 결정 15건TODO.md B절이 SSoT 다

↻ v0.4 → v0.5 변경 12건 · schema-v0.5.md — 원본에서 자동 동기화

원본 커밋 61bfcfa6b268

목차

장 57개 · 절 1,180개

1부 · 프로젝트 전체

무엇을 만들고 있고, 어디까지 왔고, 무엇이 남았는지. 규약을 읽기 전에 여기부터 본다.

  1. 1장프로젝트 허브정본로드맵·용어·태그 분포·마일스톤·I.I.S.
  2. 2장진행 현황 대시보드파생한눈에 보는 요약
  3. 3장할 일과 정할 것정본할 일·열린 결정의 SSoT

2부 · 규약 — spec/

이 책의 핵심이다. 스키마가 무엇을 정하고, 45종이 각각 무엇을 받고, 빈 결과가 왜 사용자에게 가지 않는지.

  1. 4장스키마 규약 v0.5정본규약 SSoT · core schema·생성·검증·fallback
  2. 5장Capability 테이블 45종 전량정본성립 조건·선택 속성·fallback 순서
  3. 6장YAML 예시정본질의 상위 9종과 안티패턴 5종
  4. 7장표면 형식 대안 · KDL 검토참고토큰 실측과 다시 볼 조건
  5. 8장KDL 예시참고YAML 예시와 절 번호가 같다
  6. 9장기계가 읽는 규약정본JSON Schema 와 capability.json

3부 · 설계 — design/

규약이 왜 그 모양인지. 흐름·대치 계약·I.I.S. 게이트·꾸밈구조 넷이 설계의 뼈대다.

  1. 10장전체 흐름정본입력 → Visual Summary → Search Signature → RLSC
  2. 11장내용 대치 contract정본고른 다음에 무엇을 어디에 어떻게 넣나
  3. 12장I.I.S. 산출물 정합성 게이트정본구조 대응에서 빠진 텍스트 노드를 색인 전에 거른다
  4. 13장꾸밈구조정본등급 하나에서 축 여럿으로

4부 · 실측 — evaluation/

새 경로를 도입할지 판단할 때 쓰는 숫자. 가설을 세우고, 재고, 틀린 기록까지 남긴다.

  1. 14장실측 자료 색인정본무엇을 넣고 무엇을 넣지 않나
  2. 15장Placeholder Text Slot 효과 · 분석 보고서정본judge 는 안 움직이고 사용자 채택만 떨어진다
  3. 16장Placeholder 효과 · 공유용 요약파생위 보고서의 요약판
  4. 17장judge 재측정 · 상세 보고서정본운영 요약 방식과의 비교 (2026-09-27)
  5. 18장judge 재측정 · 운영 요약파생위 보고서의 요약판
  6. 19장재순위 규칙 회차 26·27 비교정본정반대 결과가 나온 이유
  7. 20장재현 스크립트참고수치를 다시 만드는 순서

5부 · 계획 — plan/

무엇을 어떤 순서로 만들 것인가. Blueprint 와 PoC 기록이 섞여 있다.

  1. 21장PoC 안내서정본처음 보는 사람의 진입점
  2. 22장PoC 모듈 지도정본모듈별 구현 상태와 I.I.S. 진행 방침
  3. 23장Eregion PoC 구현 기록정본질의 갈래 end-to-end
  4. 24장comp-sign bridge정본structure 를 이석님 파이프라인에 잇는다
  5. 25장노드 태그와 노드 속성 PoC Blueprint정본경계 지도·속성 지도 계약
  6. 26장속성 정의와 이종 컴포넌트 구조 대치 리뷰초안초안
  7. 27장component.type fallback map v2정본MOR-1909 · 49행 전체
  8. 28장골든셋 v1 설계정본제작 경로와 자동 게이트
  9. 29장생성 모델 파인튜닝 계획계획착수 전
  10. 30장L1/L2 혼합 랭킹 Blueprint정본L2 조합 펼치기
  11. 31장속성 골든셋 편집 UI Blueprint정본I.I.S. 속성 주석 화면
  12. 32장zod + OpenAPI + 시그니처 수집 Blueprint정본API 검증
  13. 33장Jira Artifact 재편 기록정본티켓 구조와 대응표
  14. 34장PageNavigation 4종 성립 조건초안제안 초안
  15. 35장Blueprint 작성 가이드참고헤더·본문·Checklist·Changelog

6부 · 참고 — reference/

밖에서 가져온 사실의 사본. 단어장, 스마트 다이어그램, 실사용 샘플, 출처 색인.

  1. 36장Semantic Taxonomy v7 단어장 전량참고일반 29종 + 표·차트 23종
  2. 37장스마트 다이어그램 데이터 모델참고32종과 스키마, 세 층의 격차
  3. 38장Markdown Summary vs Visual Summary 비교참고같은 입력, 무엇을 잃고 되찾나
  4. 39장Markdown Summary 실사용 샘플 전량참고기준선 · 분량이 크다
  5. 40장골든셋 초안 1,000건 회수 요약참고오분류 축 분석
  6. 41장골든셋 제작에서 배운 것참고프롬프트 작법
  7. 42장iui.comp_frames 10만 건 IIS 재적재참고반영 누락과 복구 기록
  8. 43장L1/L2 전시 정책 · 결정 vs 구현 갭참고2026-09-25 조사
  9. 44장호출당 비용 실측 기록참고2026 상반기
  10. 45장이종중첩 컴포넌트 수량 산출참고1,000건 표본 실측 재현 묶음
  11. 46장이종중첩 산출 · 서브에이전트 지시 프롬프트참고판정 규약 원문
  12. 47장출처 인덱스참고Confluence·Jira·Drive·Slack·코드

7부 · 통합 보고서 — report/

PDF 보고서의 직전 원고 여섯 편. 원문의 스냅샷이고 SSoT 가 아니다 — 원문과 다르면 원문이 맞다.

  1. 48장통합 PDF 보고서 만드는 법참고구성과 재생성
  2. 49장보고서 1 · 개요파생파생물
  3. 50장보고서 2 · 규약파생파생물 · 4장의 보고서판
  4. 51장보고서 3 · 일정과 작업 상태파생파생물
  5. 52장보고서 4 · 구현물 로직파생파생물
  6. 53장보고서 5 · 카테고리와 필드 전량 명세파생파생물 · 5장의 보고서판
  7. 54장보고서 6 · 45종 데이터 구조 예시파생파생물

8부 · 발표 — slides/

스쿼드 공유용 Marp 덱. 규약을 처음 설명한 자리의 기록이다.

  1. 55장2026-08-04 스쿼드 공유 덱 24장파생파생물

9부 · 보존 — archive/

대체된 판. 지금 규약은 이것을 따르지 않는다. 왜 바뀌었는지 볼 때만 연다.

  1. 56장스키마 규약 v0.4보존보존 전용 · 장식과 계층 원 논의
  2. 57장투영 모델보존보존 전용 · v0.5 는 이 철학을 따르지 않는다

1부

프로젝트 전체

무엇을 만들고 있고, 어디까지 왔고, 무엇이 남았는지. 규약을 읽기 전에 여기부터 본다.

1장프로젝트 허브 정본로드맵·용어·태그 분포·마일스톤·I.I.S.

원문 README.md GitHub 에서 보기 38 KB 원문 제목 「Visual Summary 프로젝트 허브 (내용시각화 Summary 고도화)」

이 문서의 역할 내용시각화 Summary 고도화의 프로젝트 허브다. 로드맵, 고정할 용어, semantic tag 분포, 마일스톤별 범위, I.I.S. 를 한곳에 모은다. 세부 스펙과 일정은 각 티켓과 문서를 인용한다. 이 폴더가 SSoT이다. Confluence 원본은 2026-08-01 부터 보존 전용이다 (아래 0절). 오너 정원석 (MORDOR) · 상태 DRAFT · 최종 갱신 2026-09-05 (D-6 — semantic tag 용어를 Semantic Component Category로 확정) 관련 spec/schema-v0.5.md 규약 · TODO.md 할 일 · MOR-1923 Artifact · MOR-1773 (Done)

먼저 볼 것 셋 PoC 가 무엇이고 어디까지 왔는지는 plan/poc-guide.md, 모듈별 구현 상태와 집중 우선순위는 plan/poc-module-map.md, 규약은 spec/schema-v0.5.md, 할 일은 TODO.md 다.

이 프로젝트를 처음 본다면 plan/poc-guide.md 하나만 읽으면 전체가 잡힌다. 왜 만들었는지, 입력에서 판정까지 무엇이 어떤 순서로 도는지, 어디까지가 실물이고 어디부터가 대행인지, 어떤 정책을 왜 정했는지, 지금 수치가 얼마인지, 무엇이 남았는지가 그 문서에 있다.

통합 PDF (visual-summary-overview.pdf) 인쇄와 통독용 스냅샷이다. 최종 파일은 report/pdf/visual-summary-overview.pdf. PDF에 바로 들어가는 원고는 report/sections/ 의 01-overview.md 부터 06-examples.md 까지 여섯 개다. 원문 SSoT(README.md, TODO.md, spec/, design/, plan/)가 바뀌면 이 파일들을 고친 뒤 report/build/build.py로 PDF를 다시 만든다. 절차와 다이어그램 소스는 report/README.md. 이 PDF와 sections/ 는 파생물이고 SSoT가 아니다.

이 문서 훑기

절 무엇에 답하나
(상단) 통합 PDF 위치와 직전 원고(report/sections/)
0 어느 파일에 무엇이 있나
1 단계마다 무엇이 바뀌나
2 같은 말을 같은 뜻으로 쓰려면
3 설계 판단의 실데이터 근거는 무엇인가
4 단계별 범위와 진행 상황은 어디까지인가
5 node 단위 태그를 누가 다나
6 지금 걸린 마감은 무엇인가

0. 문서 구성#

폴더를 장르로 나눴다. 규약은 spec/, 왜 그런가는 design/, 외부 사실의 사본은 reference/, 계획은 plan/, 도입 판단에 쓰는 실측은 evaluation/, 대체된 판은 archive/ 다.

파일 무엇이 있나
README.md (이 문서) 로드맵, 용어, semantic tag 분포, 마일스톤 상세, I.I.S.
TODO.md 할 일과 정할 것의 SSoT. 2단계 배포(8/18), 열린 결정, 사후 검증, 해소된 것
DASHBOARD.md 한눈에 보는 진행 현황 요약 (Mermaid 간트차트 포함, GitHub 용). 파생물이고 SSoT 아님. 손으로 갱신한다
dashboard/index.html없음 위 대시보드의 인터랙티브 HTML 판. Mermaid 를 렌더링하지 않는 뷰어(Paseo 등)에서 볼 때 쓴다. 데이터는 dashboard/data.js없음 에서 관리한다
spec/
schema-v0.5.md 규약 SSoT. core schema, 생성 메커니즘과 검증 절차, fallback 규칙, 설계 제약, YAML 예시. 2026-08-02 확정. 현행 버전은 0.5.4 이고 버전 규칙은 그 문서 0.1절이다
capability-table.md 45종 전량의 성립 조건, 선택 속성과 기본값, fallback 순서. rlsc-capability 상수의 내용이다. 3.6절이 노드가 여럿인 질의의 순서 N1~N5 (2026-08-28 확정)
yaml-examples.md 사용자 입력을 Visual Summary YAML 로 옮긴 예시. 질의 상위 9종과 안티패턴 5종
format-alternatives.md KDL 검토 자료. 같은 스키마의 KDL 표현 설계와 토큰 실측(KDL 이 YAML 보다 12.9% 적음), 다시 볼 조건
kdl-examples.md KDL 검토 자료. yaml-examples.md 의 전체 문서 예시 18종에 1:1 대응하는 KDL. 절 번호가 같다
schema/ 기계가 읽는 생성물. JSON Schema 파일과 capability 41종 JSON. SSoT 는 위 두 문서이고, 어긋나지 않는지 보는 검사 14가지가 그 폴더 README 에 있다
design/
pipeline-flow.md 사용자 입력에서 Search Signature 와 RLSC 까지의 전체 흐름 다이어그램과 소유 경계
content-binding-contract.md 내용 대치 contract. 고른 다음에 무엇을 어디에 어떻게 넣는가. fallback 이후 대치 계층(3.4), structure vs property projection(3.5), 복합 구조 fallback 을 한 단계씩 따라간 예시(3.7), 용어(2.5). comp-sign 실물 현황
iis-consistency-gate.md I.I.S. 산출물 정합성 게이트 설계안 SSoT. 구조 대응(structure)에 참조되지 않은 텍스트 노드가 있는 경계를 색인 전에 찾아 기록하는 게이트. 규칙 interface 와 추가 절차, 저장(테이블 2 + 컬럼 1), pg-boss worker 실행 모델, GC 게이트 대조. MOR-2447, 2026-09-15
decoration-model.md 꾸밈구조 설계 의도 SSoT. 등급 하나가 아니라 축 여럿으로 두는 이유, N / N+α / N+β 의 차용가치 판정, 축과 값과 선언 층의 초기 결정, 요소 대치와의 관계. 1단계는 시스템 전역 기준만 잡고 Summary 는 생성하지 않는다. 2026-08-03
evaluation/
evaluation/README.md 새 경로 도입 판단에 쓰는 실측 자료 색인. 새 경로가 없던 문제를 만드는가, 기존 문제의 어느 면을 고치는가, 얼마나 고치는가, 제품 지표로 환산하면 얼마인가
evaluation/placeholder-effect.md Placeholder Text Slot이 judge 판정과 사용자 채택에 주는 효과. 운영 경로에서는 judge 가 감점하지 않고 사용자 채택만 떨어진다(odds ratio 0.688, 성향점수 IPTW 로도 0.663). 새 경로는 자리째 비어 judge 도 움직인다(이건 버그이고 MOR-2504 로 고친다. 고치면 결론이 바뀔 수 있다). 도입 판단은 9절
evaluation/rerank-rule-rounds-26-27.md 재순위 규칙 회차 26 과 27 이 정반대 결과를 낸 이유. 결함 분류 체계의 공백
reference/
semantic-taxonomy-v7.md v7 중분류 단어장 전량의 repo 사본과 질의 쪽 실사용 빈도
smart-diagram-data-model.md 스마트 다이어그램 32종과 스키마. 저작 문서, 레이아웃 엔진, 에디터 저장 contract 세 층의 격차
markdown-summary-samples.md 현행 Markdown Summary 실사용 샘플. Visual Summary 대비 무엇을 잃는지의 기준선
summary-comparison-samples.md 같은 입력의 Markdown Summary 와 Visual Summary 비교 4종. 무엇을 잃고 무엇을 되찾는가
2026-08-13-이종중첩-수량-산출/ 이종중첩 컴포넌트 실측 재현 묶음. 1,000개 표본에서 1.10%(전체 약 2,014개), 측정 방법·프롬프트·실행 지시·판정 원본. TODO.md D-7 의 후속 액션 결과
iui-comp-frames-100k-iis-pool-reload.md iui.comp_frames(검색 pool) 10만 건 IIS 반영 누락과 재적재 기록. miridih/miricanvas-iui 저장소 iui.text_capacity에는 반영됐으나 iui.comp_frames는 갱신되지 않았던 사례, --iis --fresh 재적재와 gc_passed backfill 결과
sources.md Confluence, Jira, Drive, Slack, 코드 출처 색인
slides/
2026-08-04-visual-summary.md 스쿼드 공유용 Marp 덱 24장. 오해 정리, 로드맵, v0.5 결정, capability 와 fallback, 꾸밈구조, 소유 경계, 사람별 질문. 파생물이고 SSoT 가 아니다. TODO.md A-5 의 산출물이다
plan/
finetuning-plan.md Summary 생성 모델 파인튜닝 계획. 역생성과 round-trip 검증과 사람 라벨 설계. 착수 전
poc-guide.md PoC 안내서. 처음 보는 사람이 이것 하나만 읽으면 되는 진입점. 왜 만들었나, 전체 흐름, 실물과 대행의 경계, 두 검색 경로, 결함 채점과 귀속, 정책과 설계 결정, 돌리는 법, 기준선 수치, 남은 것
poc-module-map.md PoC 모듈 지도. 파이프라인 다이어그램, 모듈별 구현 상태, fallback·projector·I.I.S. 이름 정리, 지금 집중할 것
eregion-poc.md Eregion PoC 구현 기록 (2026-08-12). 질의 갈래 end-to-end 코드·화면, 인라인 실시간 렌더 캔버스, 실측, 구현이 임시로 정한 판단 4건
report/
README.md 통합 PDF 보고서. 최종 PDF는 pdf/visual-summary-overview.pdf. PDF 직전 원고는 report/sections/01~06-*.md 여섯 개. 다이어그램은 report/diagrams/, 재생성은 report/build/build.py. 원문(README, TODO, spec, design, plan)의 스냅샷이며 파생물이고 SSoT가 아니다
archive/
schema-v0.4.md 이전 판. 보존 전용. 4-1절의 장식과 계층 원 논의(N, N+α, N+β 정의)만 여기 남아 있다
projection-model.md v0.2 와 v0.3 의 설계 철학. v0.5 는 이 철학을 따르지 않는다. 보존 전용

핵심 링크 MOR-1923 Artifact (2026-08-06 신설, 실행 트랙의 상위) / MOR-1773 규약 정의 (Done) / 마일스톤 MOR-1774 (2026-08-07 사실상 스킵, 8/18 폐기) MOR-1775 (Cancelled) MOR-1776 (Artifact 하위, Subtask [4-1]~[4-15]) / Visual Summary 스펙 인덱스

0.1 Confluence 에서 이 저장소로 옮긴 경위#

2026-08-01 부터 이 저장소(miridih/mordor-wiki)의 문서가 SSoT다. Confluence 내용시각화 Summary 고도화 허브는 갱신을 멈추고 기록으로만 보존한다. 그 페이지에는 이관 사실을 comment 로 고지해 두었다.

이관은 2026-07-31 #mordor스쿼드 공지를 따른다. 이유는 둘이다. Confluence 는 MCP 로 갱신할 때 결과가 불확실하고, AI 에게 SSoT 를 컨텍스트로 넣기에도 repo 쪽이 낫다.


1. 로드맵#

Summary 고도화는 아래 단계로 간다. 단계마다 바뀌는 건 네 가지다. Summary 형식, 검색 방식, 태그를 어디에 다는가, 그리고 그 태그가 무엇을 가리키는가이다.

단계 Summary 형식 검색 방식 태그 다는 범위 태그가 가리키는 것 시점 마일스톤
1단계 (현행) Markdown 임베딩 유사도 안 단다 - 지금 -
2단계 (시멘틱 검색 v2 Phase 1) Markdown semantic tag 컴포넌트 하나에 태그 이 컴포넌트 어딘가에 그 타입이 있다. root 인지 하위 node 인지는 모른다 사실상 스킵 (2026-08-07 확정) MOR-1774
3단계 YAML semantic tag + structure 를 검색 질의로 컴포넌트 하나에 태그 2단계와 같음 실행하지 않음 (2026-08-06 Cancelled) MOR-1775
4단계 (tag 까지만) YAML semantic tag + RLSC 내부 node 태그 컴포넌트 안 node 하나하나까지 어느 node 가 그 타입인지 특정된다. 검색과 대치를 그 범위에 정확히 건다 (Layout Container type 확장) 단독 단계로는 건너뛴다 (2026-09-08 확정). 남은 실행 티켓은 Cancelled MOR-1776
5단계 (tag + props) YAML 4단계와 같음 컴포넌트 안 node 하나하나까지 4단계에 더해, tagged node 에 semantic category 속성을 붙이고 그 category 고유의 props 를 단다 여기로 직행한다 (2026-09-08 확정). 경계와 속성을 한 묶음으로 추출해 노드 단위로 검색한다 MOR-2087

2단계를 사실상 건너뛰기로 한 경위 (2026-08-07 확정) 2026-08-03 에 내용시각화 제품의 8/25 production 배포가 취소(연기)로 정해지면서, 2단계 배포(MOR-1774)의 8/18 마감이 이 연기에 포함되는지가 미정으로 남아 있었다. 이 질문을 MOR-1939가 승계했고, 2026-08-07 김수현님이 Visual Summary 발행본 Confluence 코멘트로 답했다. 8/18 마감은 이 연기에 포함된다. 8/18 은 폐기하고 재조정한다. 새 날짜는 아직 안 나왔다. 같은 자리에서 D-2(List 편중 81.1%를 그대로 두고 2단계를 낼지)도 답이 나왔다. 2단계를 안 하고 4단계로 직행하므로 결정할 것이 없다. List 편중 대응은 4단계 fallback map(MOR-1909)으로 넘어간다. MOR-1774 티켓 자체는 MOR-1775처럼 Jira 상 Cancelled 로 전환되지는 않았다(2026-08-08 기준 Draft, due date 필드도 8/18 잠정 유지). 다만 실행 방향은 위 결정으로 사실상 스킵이다. due date 필드 갱신은 새 날짜가 나오는 대로 반영한다.

4단계를 단독 단계로 건너뛰고 5단계로 직행하기로 했다 (2026-09-08 확정, 정원석) 결정은 #mordor스쿼드 스레드에서 났다. 같은 날 세 대화가 이어졌다. (1) 15:21 허재영이 "4단계 바운더리까지만 깎아 두고 있었는데 5단계 프로퍼티 깎는 체계를 구축하겠다"고 밝혔다 (Slack). (2) 16:54 정원석이 이석, 홍지혜와의 구두 논의를 공지하면서 검색·대치 PoC 에 쓸 컴포넌트 풀을 I.I.S. (Semantic Component - Structure and Property) 로, 즉 경계와 속성을 한 묶음으로 10만 건 추출한다고 적었다(실행 허재영) (Slack). (3) 17:46 허재영이 MOR-1776 하위의 정원석 담당 티켓을 닫거나 나누자고 제안했고, 17:51 정원석이 **"4단계 관련해서는 cancel 하고, 문서상으로 결정된 것들 최신화 하겠다"**고 답했다 (Slack). 같은 스레드에서 황현진과의 PoC scope 조정(Complexity-3-Containment 보류, 1-Single·2-Juxtaposition 집중)도 함께 정해졌다.

어떻게 하기로 했나 노드에 태그만 다는 단계를 따로 완주하지 않는다. 경계(I.I.S. Semantic Component Boundary)와 속성(I.I.S. Semantic Component Property)을 한 추출 묶음으로 뽑고, 검색과 대치도 노드 단위 경로로 바로 붙인다. 이미 실행이 그 방향으로 가 있었다 — 로컬 파일럿에서 경계 4,764건·속성 3,955건을 뽑았고(2026-09-06, TODO.md K-10), 그 위에 노드 검색 엔진을 플레이그라운드에 배선했다(2026-09-07, K-9). 후속은 속성 추출 평가(MOR-2381)와 경계·속성 10만 건 추출(MOR-2415)이다. 두 검색 경로는 서로 폴백하지 않는다(정원석 확정, K-9).

Jira 반영 상태 (2026-09-14 조회) 4단계 실행 티켓 여섯이 Cancelled 다 — MOR-1924, MOR-1925, MOR-1926, MOR-1927, MOR-1929, MOR-1936. 스킵 여부를 판정하기로 했던 MOR-1926 자체가 Cancelled 이므로, 이 결정은 그 PoC 실측 없이 났다. 마일스톤 MOR-1776 은 아직 In Progress 이고 due date 도 2026-09-30 그대로다 — 아래 "9월 말 dev/stg, 10월 중순 production 판정"이 5단계 기준으로 그대로 유지되는지는 아직 답이 없다.

3단계를 건너뛰기로 한 이유 7/24 회의에서 정리됐다. 3단계에서 개발하는 내용 대치 로직이 4단계에서는 버려질 수 있어서, 3단계를 건너뛰고 정보가 확실한 4단계로 바로 가는 편이 대치 로직의 불확실성이 적다고 봤다. 다만 스킵 여부는 대치 로직 관점에서 한 번 더 확인이 필요하다.

4단계와 5단계로 쪼갠 이유 원래 4단계였던 것을 둘로 나눴다. node 에 태그를 다는 데까지는 빠르게 갈 수 있고, property 주입(예: 스텝의 연도 표기)은 복잡도가 높은 데다 다이어그램처럼 특정 유형에만 붙을 확률이 높다. 4단계만으로도 제품단 효과가 나므로 우선 4단계에 집중하고, 5단계 실행 여부는 나중에 정한다. 이 판단은 2026-09-08 에 뒤집혔다 — 위 문단대로 4단계를 단독으로 완주하지 않고 5단계로 직행한다. 쪼갠 이유 자체는 기록으로 남긴다.

8/4 회의록 표현과의 대응 (2026-08-06 정원석 확인) 회의록의 "core component 경계 정의"는 4단계의 node 태그와 같은 말이다. node 에 태그가 붙으면 곧 core component 의 경계가 특정된다. 회의록의 "fallback 시 속성 호환"은 5단계다. tagged node 에 semantic category 속성을 붙이고 그 category 고유의 props 를 다는 것이 속성 호환의 전제다. 두 문서의 서술은 같은 것의 다른 표현이고 정의 충돌이 아니다.

단계를 나누는 진짜 이유 태그를 컴포넌트 단위로 다는 한, 그 태그가 무엇을 가리키는지 두 경우를 구분할 수 없다. root node 자체가 그 타입이라 붙은 것인가, 하위 어딘가에 그 타입의 node 가 있어서 컴포넌트 전체가 받은 것인가.

2단계부터 3단계까지는 이 모호성을 안고 간다. 4단계에서 어느 node 부터가 그 semantic tag 에 해당하는 layout container type 인지 특정되면 그때 범위가 정확해진다.

실데이터에서 이것을 읽을 수 있다. 한 컴포넌트에 태그가 최대 4개까지 붙는다(UNICORN-77432). 예를 들어 design_object_id 4757227 에는 Comparison Table, Hierarchical Tree, List, Highlighted Message 넷이 함께 붙어 있다. root 가 이 넷을 동시에 가질 수는 없으므로, 하위에 여러 타입의 node 를 품고 있다는 뜻이다. 복수 태그가 나온다는 사실 자체가 두 번째 경우가 실재한다는 증거다.

7/21 체크인 결정 다이어그램 계열 내용시각화를 스마트 다이어그램이나 스마트 블록으로 갈지, 기존 RLSC 추출로 갈지 논의한 결과 앞단(Summary 생성, 검색 조건 정의)은 기존 RLSC 방식으로 먼저 간다. 두 방식이 앞단에서는 거의 같고 스마트 시리즈의 표현 품질이 아직 검증되지 않았기 때문이다. 스마트 시리즈는 품질이 확인되면 유형별로 전환을 검토한다.


2. 용어#

회의와 문서가 같은 말을 같은 뜻으로 쓰도록 고정한다.

용어 뜻 비고
semantic tag semantic taxonomy 에서 고른 값. Summary, Page, Component 에 공통으로 붙이는 분류이자 매칭 단위 'category class', 'tag', '시멘틱 태그', '중분류'는 전부 같은 것이다. semantic tag 로 통일한다. 예: Component.semanticTags = ["Steps","List"]
semantic taxonomy (v7 중분류 단어장) 쓸 수 있는 태그 값 목록. 단어장 밖 값은 못 쓰고, 한 대상에 여러 태그는 허용 단어장은 집합, 태그는 그 집합에서 고른 값들이다. 실제 분포는 3절 참고
Summary 사용자 입력을 검색과 대치에 쓰려고 변환한 산출물
Summary 생성기 사용자 입력을 받아 Summary 를 써 내는 LLM 모듈. 문서에서 '생성기'로 줄여 부른다 현행은 Markdown 을 쓰고, 4단계에서 YAML 로 바꾼다(4절). 오너는 허재영, 황현진
Markdown Summary Markdown 형식의 Summary 지금 운영 중인 것이 이것이다
Visual Summary Summary 를 의미 구조로 구조화하는 스펙 (v0.1 ~ v0.5) 규약은 schema-v0.5.md, 2026-08-02 확정. 현행 값은 0.5.4
규약 버전 MAJOR.MINOR.PATCH, 각 자리 0~99. 규약을 고치면 반드시 올린다 자리 판정은 "이 변경으로 실제로 무효가 되는 산출물이 있는가" 하나다. 검증기는 MAJOR·MINOR 만 대조하고 PATCH 는 보지 않으며, 파일 이름도 MAJOR.MINOR 만 따라간다. 규칙은 spec/schema-v0.5.md 0.1절
Phase 1 1절 로드맵의 2단계와 같은 말. 시멘틱 검색 v2 의 단기 출시 범위 Phase 번호와 단계 번호와 v0.x 는 서로 다른 것을 센다. 항상 같이 적는다
component-level tag (root-only tagging) 컴포넌트 하나에 붙는 태그. 2·3단계 root 를 가리키는지 하위 node 를 가리키는지 구분되지 않는다
node-level tag (node-level tagging) 컴포넌트 안 node 마다 붙는 태그. Layout Container type 확장. 4단계 I.I.S. (Semantic Component Boundary) 로 대응한다 (5절)
I.I.S. (Semantic Component Boundary) 증분 정보 구조화기(시맨틱 컴포넌트 경계). 컴포넌트 안 어느 노드 묶음이 어느 semantic category 인가를 정하는 모듈 계열. 4단계 RAT pod(류민) 소관, 멈춰 있음. 코드 식별자 iisSemanticComponentBoundary. 상세는 5절
I.I.S. (Semantic Component Property) 증분 정보 구조화기(시맨틱 컴포넌트 속성). 경계가 잡힌 묶음에서 둘을 뽑는 모듈 계열. 카테고리 고유 속성(properties)과 구조 대응(structure)이다. 5단계 상동. 코드 식별자 iisSemanticComponentProperty. 이 이름의 'Property' 만 넓은 뜻이고 줄여 쓰지 않는다 (아래 표기 규칙). 상세는 5절
복잡도 레벨 (complexity level) Visual Summary 하나가 가질 수 있는 위상의 범위. L1 단일 컴포넌트, L2 병치 위상(최상위 components[] 여럿), L3 전체 위상(entity.components[] 중첩까지) 누적이다(L1 ⊂ L2 ⊂ L3). 산출물 한 건의 모양이 아니라 생성기에 허용된 범위를 가리킨다. 정의는 spec/schema-v0.5.md 8.8절. '질의'가 아니라 Visual Summary 의 성질이다 — 질의로 바뀌는 것은 질의 변환기를 거친 뒤다. 실서비스 1단계 생성기는 L2 까지이고 PoC 생성기는 이미 L3 를 낸다. L2 의 '병치'는 8.7절 위상 관계의 Juxtaposition 과 다른 층이다
RLSC 레이아웃 구조와 요소 역할을 담은 트리
Search Signature v2 컴포넌트 쪽 검색용 정적 artifact. design object 하나가 여러 row 를 가질 수 있다 RLSC 와는 분리된 contract 다. RLSC 를 입력 feature 로 참조할 수는 있어도 대체하지 않는다
꾸밈구조 컴포넌트에 붙는 꾸밈 선호. 축들을 담는다 component.decoration 과 page.decoration 이다. RLSC role Decoration(장식 요소)과 다른 것이고 앞에 무엇이 붙는지로 구분한다 (design/decoration-model.md 0절)
꾸밈 축 꾸밈구조 안의 항목 하나. 축 하나가 RLSC role 하나에 대응한다. ornament(Element.Decoration)와 background(Element.Background) 둘 축에 담기는 값은 선호값이고 prefer, indifferent, avoid 셋이다. 기본값은 indifferent 다. '장식 등급'이나 '장식 수준'으로 부르지 않는다
N / N+α / N+β 7/24 회의의 누적 장식 등급 3단 더 쓰지 않는다. N 과 N+β 는 문서와 회의에서 부르는 별명으로만 남고 스키마 값이 아니다. N+α 는 개념 자체가 사라졌다(내용 유무이지 표현 밀도가 아니다). 원 정의는 archive/schema-v0.4.md 4-1절에 보존한다
검증기 (Validator) Visual Summary YAML 을 파싱하고 id 를 부여하고 스키마로 검사하는 모듈 PoC 코드 validate.ts, parse.ts. plan/poc-guide.md 2절
확정기 (Establisher) 카테고리 성립 조건을 판정하고 fallback 을 따라 내려가는 모듈 PoC 코드 establish.ts, fallback.ts, projection.ts
질의 변환기 (Query Converter) 확정된 카테고리와 구조를 검색이 쓸 태그·제약으로 바꾸는 모듈 PoC 코드 query.ts. MOR-1924, 정원석 스펙+PoC due 8/28, 9월 이석님이 구현을 인수한다
시멘틱 검색 (Semantic Search) semantic tag 로 후보를 찾는 검색 경로. Visual Summary PoC 의 자체 구현이고 실서비스 코드가 아니다 PoC 코드 search.ts, rlsc.ts. plan/poc-guide.md 4절
구조 검색 (Structural Search) 구조 해시로 후보를 찾는 검색 경로. comp-sign 의 buildCompSearchData/rankCompCandidates 를 그대로 쓴다 입력이 markdown 이라 structure → markdown 다리(plan/comp-sign-bridge.md)를 거친다. 이석님 소유, markdown 기준
자체 대치 (RLSC Binding) RLSC 슬롯에 사용자 내용을 직접 넣는 대치 경로. Visual Summary PoC 의 자체 구현 PoC 코드 binding.ts. Visual Summary structure → RLSC role 슬롯
실물 대치 (comp-sign Content Replacement) comp-sign 의 buildContentReplacement 를 그대로 호출하는 대치 경로 markdown → textTree + text_map / text_node_roles. design/content-binding-contract.md 2.2·2.5절
structure (질의 구조) Summary YAML의 entities/relations/dataset. 검색·fallback·P1~P8 입력 schema-v0.5.md 2절
textTree comp-sign 대치용 텍스트 트리. markdown 파싱 산출물 comp-sign buildCompSearchData
structure projection fallback 엣지에서 structure/params를 도착 카테고리에 맞게 접는 규칙 P1~P8 projection.ts, capability-table 3.4절
복합 구조 fallback 노드가 여럿인 질의에서 어느 노드부터 포기하는지의 순서 N1N5 와 규칙 N-R1N-R6 capability-table 3.6절, 예시는 content-binding-contract 3.7절
property projection 이종 type 간 카테고리 고유 속성(params/properties)을 슬롯에 맞추는 5단계 설계 (T-8). 좁은 뜻의 property 다 blueprint T-8, 미구현. 아래 표기 규칙
구조 대응 (structure map) 색인 쪽에서 어느 노드가 structure 의 어느 필드(label, items, value …)인지의 대응표. 질의 쪽 structure(내용)와 다른 것이다 iis_semantic_component.structure, 2026-08-23 신설. 아래 표기 규칙

용어 변경 (2026-09-05 확정, 정원석, D-6) semantic tag 대신 Semantic Component Category 로 부른다. 과거 RLSC Role v1.0 의 Role.Component.* 와 유사한 목적의 값을 RLSC tree 안 node 마다 property 로 심는 것과 같기 때문이다.

'property / 속성' 표기 규칙 (2026-08-29 고정)#

이 말 하나가 넓은 뜻과 좁은 뜻으로 함께 쓰이고 있었다. 2026-08-23 에 5단계 산출물을 둘로 넓히면서 모듈 이름은 그대로 두기로 했기 때문이다(1.0.1절). 그래서 표기를 아래로 고정한다. 맨 이름 '속성'이나 'property' 를 단독으로 쓰지 않는다.

무엇 질의 쪽 (Summary, 생성기가 적는다) 색인 쪽 (컴포넌트, 추출기가 뽑는다)
카테고리 고유 속성 params properties (iis_semantic_component.properties)
구조 structure — 내용 자체(entities/relations/dataset) 구조 대응(structure map, iis_semantic_component.structure) — 노드 ↔ 구조 필드 대응표
  • params 와 properties 는 같은 어휘를 쓰지만 같은 것이 아니다. 어휘는 둘 다 capability 의 reads.params(선택 속성 열)이고, 한쪽은 요구(이렇게 그려 달라)이고 다른 쪽은 실측(이 컴포넌트는 이렇게 그려져 있다)이다. 둘을 맞대는 것이 T-8 이다.
  • structure 라는 낱말도 두 층이다. 질의 쪽은 내용이고, 색인 쪽은 그 내용을 어느 노드에 넣을지의 대응표다. 색인 쪽을 가리킬 때는 반드시 구조 대응 또는 iis_semantic_component.structure 로 적는다.
  • 넓은 뜻은 한 자리에만 있다. 5단계 국면 이름 I.I.S. (Semantic Component Property) 의 'Property' 는 위 오른쪽 열 두 칸을 합쳐 부르는 이름이다. 이 이름은 줄이지 않는다. "Property 모듈", "속성 추출기" 로 줄이면 좁은 뜻과 충돌한다. blueprint 6절의 작업 이름 T-7 시맨틱 컴포넌트 속성 추출기 도 실제로는 두 벌을 다 내므로 넓은 뜻이다.
  • '질의' 도 같은 종류의 겹침이 있다. 좁은 뜻은 질의 변환기(query.ts) 산출물이고, 넓은 뜻은 색인 쪽과 대비하는 진영 이름('질의 쪽')이다. 생성기 산출물 자체를 가리킬 때는 Visual Summary 라고 적는다. 복잡도 레벨처럼 생성 단계의 성질을 말할 때 '질의'라고 쓰면 변환기 뒤의 것으로 읽힌다.
  • property projection(T-8)은 좁은 뜻이다. 옮기는 것은 params/properties 뿐이고, 내용을 옮기는 structure projection(P1~P8)과 다르다.

헷갈리기 쉬운 셋

첫째, semantic tag(의미 분류)와 Layout Container type(물리 배치, HStack/VStack/Grid)은 다르다. 단어장에 배치명과 겹치는 라벨이 있어 헷갈리기 쉽다. 다만 단어장 v7 이 2단계 배포 이후 또 바뀔 수 있으므로 지금은 capability 규약을 확정하지 않는다.

둘째, Content Archetype(이 페이지가 문서에서 어떤 역할인가, 표지/목차/본문/마무리)과 semantic tag(이 콘텐츠가 어떤 의미 구조인가)는 서로 다른 것을 분류한다. 지금 실데이터에는 이 둘이 섞여 있다. 현재는 Page-level, Component-root-level, Node-level 세 층위의 semantic tag(visual category)로 나눠 볼 수 있다.

셋째, 색인 쪽과 질의 쪽도 다르다. Search Signature v2 는 컴포넌트를 색인하는 쪽이고, Summary 의 structure 는 사용자 입력을 질의로 만드는 쪽이다.

2.1 포맷: YAML 로 확정 (초안)#

결정 (2026-08-01, 초안 확정, 정원석) 표면 형식은 YAML 로 간다. KDL 은 채택하지 않는다.

이 결정이 정하는 것은 하나다. LLM 이 무엇을 써 내는가. 그 답이 YAML 이다.

스키마와 직렬화 형식은 다른 문제다. 의미 스키마(필드, 필수 조건, 유형, 대체 규칙, 버전 규칙)를 먼저 확정하고, 값의 validation 은 YAML 을 JSON 으로 변환한 뒤 진행한다. LLM 은 YAML 을 직접 생성한다(규약 3절 생성 메커니즘, 2026-08-02 정정). 이유는 추론 속도다. YAML 은 JSON 보다 구두점 토큰이 적어 같은 내용을 더 적은 생성 토큰으로 쓴다. JSON 으로 생성한 뒤 YAML 로 직렬화하면 이 이득이 없으므로 쓰지 않는다.

왜 YAML 인가#
# 근거 내용
1 사전 조사에서 YAML 이 앞선다 YAML 은 학습 데이터 노출량이 많고 생성 성공률 90% 이상이 검증돼 있다. KDL 은 LLM 생성 신뢰성 벤치마크가 0건이다. 아래 '사전 조사 결과'
2 이미 서 있던 결정이고 산출물이 그 위에 쌓여 있다 v0.2(2026-07-13)에서 "YAML 과 힌트형으로 확정"했고, 그 뒤 예시가 전부 YAML 로 쌓였다
3 전환 비용이 크다 KDL 로 가면 파서, 검증 연결, 정규화, 호환 계층을 직접 만들어야 한다. YAML 과 JSON 양방향 변환 코드는 MORDOR 의 RLSC Structuriser 전후처리에서 이미 많이 써 왔다
4 마감이 걸려 있다 규약 확정 마감이 2026-08-07(W32 말)이라 아래 실측 설계를 돌릴 시간이 없다

2번은 되돌리는 성격이다. 2026-07-16 회의에서 이석님이 KDL 을 제기하며 다시 열렸던 항목을 원래 자리로 되돌린다.

3번은 전환 조건도 함께 정한다. 생성 안정성에서 뚜렷한 차이가 나올 때만 전환할 값어치가 있는데, 지금 근거로는 그 차이를 기대할 이유가 없다.

'초안 확정'이 뜻하는 것#

아래 실측 설계는 지우지 않고 사후 검증으로 남긴다. 실측 결과가 YAML 을 뒤집으면 그때 재검토한다. 다만 뒤집으려면 위 3번대로 KDL 이 뚜렷하게 앞서야 한다.

정렬이 필요한 곳 KDL 을 제기한 것은 이석님이다(2026-07-16 회의, 회의록). 왜 안 골랐는지 위 근거를 전달하고, 이견이 있으면 사후 검증 범위를 같이 정한다.

판단 기준 (7/24 확정) 사람이 읽고 쓰기 좋은지로 고르지 않는다. 이 산출물은 사람이 손으로 쓰는 게 아니라 LLM 이 생성한다. 따라서 기준은 LLM 이 그 형식으로 뽑을 때 결과가 얼마나 안정적인가이다. 스키마 위반율, 들여쓰기나 따옴표 때문에 깨지는 비율, 같은 입력에서 같은 결과가 나오는 정도를 본다.

사전 조사 결과#

KDL 과 YAML 을 LLM 생성 안정성으로 직접 비교한 공개 실측 자료를 찾지 못했다. YAML 쪽은 데이터가 풍부하고, KDL 은 LLM 벤치마크에 거의 등장하지 않는다. 확인한 것은 넷이다.

무엇 확인된 것 출처
YAML 의 파싱 유효율 JSON 100%, S-expression 95.8%, YAML 91.7%. YAML 은 구조 오류 시 '조용한 손상(silent corruption)'이 상대적으로 많다 비교 실측
YAML 생성의 난도 Text 에서 YAML 을 만드는 일은 대부분 모델이 90% 이상으로 이미 "마스터된" 쉬운 쪽이다. 반면 YAML 이 얽힌 변환(YAML 을 XML 로, CSV 를 YAML 로)은 난도가 확 올라간다 StructEval (arXiv 2505.20139)
포맷이 정확도에 주는 영향 11개 모델과 4개 포맷(YAML, Markdown, JSON, TOON), 9,649 trial 에서 포맷은 집계 정확도에 유의한 영향이 없었다(모델별 민감도 -7.7% ~ +2.7%) McMillan(2026)
KDL 쪽 자료 언어 스펙과 구현체(kdl.dev, python-cuddle)뿐이고 생성 신뢰성 벤치마크는 0건이다. 정량 비교는 메시지 크기 하나인데(KDL 은 JSON 보다 compact, YAML 과는 거의 동등) 생성 안정성과는 무관하다 kdl.dev, python-cuddle

여기서 셋을 읽는다.

YAML 의 알려진 취약점은 둘이다. 들여쓰기가 토크나이저에 민감해서 스트리밍이나 긴 출력에서 잘못된 들여쓰기를 만들기 쉽다. 그리고 암묵적 타이핑 때문에 문자열이 boolean 이나 숫자로 재해석된다(예: no 가 false 로).

재현성은 포맷이 아니라 디코딩이 정한다. temperature 0 에서 0.2 사이에서 구조 준수율이 높지만, temperature 0 이라도 완전 결정론은 보장되지 않는다. 재현성은 샘플링 설정과 constrained decoding 으로 확보하는 것이 정석이다.

모델 역량 격차가 포맷 효과를 압도한다. 프론티어와 오픈소스 사이 21%p 다. 확인한 소스 여럿이 이 결론에서 일치한다.

그래서 KDL 이 불리하다는 것은 사전 확률이지 실측이 아니다. 학습 데이터 노출량이 YAML 이나 JSON 대비 극히 적고, 생성 안정성은 문법의 우아함보다 학습 코퍼스 내 빈도에 좌우된다는 점에서 나온 추정이다.

실무적 결론 공개 사례로는 우열을 가릴 수 없다. 사전 확률(YAML 우세)로 초안을 확정하고, 자체 실측은 사후 검증으로 돌린다.

YAML 을 골랐으므로 감시해야 할 것 "파싱은 됐는데 타입이 뒤바뀐" silent corruption 을 파싱 실패와 별도로 센다. 조용한 손상은 실패로 보이지 않아서, 안 세면 없는 것처럼 보인다. 생성기를 만들 때 이 지표를 처음부터 넣는다.

사후 검증 설계 (실측)#

초안 확정을 뒤집을 근거가 있는지 나중에 확인하는 용도다. 다른 마감이 걸려 있지 않을 때 돌린다.

  1. 같은 스키마 N개 케이스를 YAML, KDL, JSON 각각으로 생성한다. JSON 을 비교군으로 넣는 이유는 문헌 다수가 애초에 JSON 을 권장하기 때문이다(constrained decoding 이나 JSON mode 로 스키마 위반을 원천 차단할 수 있다).
  2. 지표는 넷이다. 파싱 실패율, 스키마 위반율, silent corruption 율(파싱은 성공했는데 의미가 변질된 것), 재현성(temperature 0 에서 같은 입력을 k회 반복했을 때 출력 일치율).
  3. 프론티어와 오픈소스 두 티어 이상을 교차해, 포맷 효과가 모델 역량에 가려지지 않게 한다.

출처 StructEval (arXiv 2505.20139), Notation Matters: Token-Optimized Formats benchmark (arXiv 2605.29676), Beyond JSON: Picking the Right Format for LLM Pipelines, Best Structured Prompt Formats for LLMs Ranked (MightyBot), Which LLMs Actually Produce Valid JSON?, Which Table Format Do LLMs Understand Best?, KDL Document Language (official), python-cuddle, Does Temperature 0 Guarantee Deterministic LLM Outputs?


3. Semantic Tag 분포 (설계의 실증 근거)#

  • 출처 design_object_semantic_tags, 2026-07-22 적재
  • 모수 design object 83,771건
  • 조사일 2026-07-24

이 절은 색인 쪽 분포다. 질의 쪽과 섞으면 안 된다. 이 절은 색인 쪽(이미 존재하는 컴포넌트에 어떤 태그가 붙어 있나) 분포다. 질의 쪽(사용자가 어떤 구조를 요구하나) 분포는 semantic-taxonomy-v7.md 3절에 따로 있다.

카테고리 색인 쪽 질의 쪽
List 81.1% 31.96%
ListingTable 0.5% 15.08%
Steps 2.4% 10.92%
ComparisonTable 0.2% 이하 7.24%

사용자가 원하는 구조는 다양한데 대치할 풀이 리스트에 쏠려 있다. 아래 'List 편중' 논의의 무게가 여기서 나온다.

핵심 지표#

지표 값
총 design object 83,771
atomic category 수 약 50종
평균 태그 수 / object 1.00
단일 태그 비율 99.7% (복수 태그 0.3%, 260개)
최빈도 category List(리스트) 81.1%

Category 분포 (design object 기준)#

순위 Category Object 수 비율
1 List(리스트) 67,962 81.1%
2 Big Number(빅넘버) 3,032 3.6%
3 Steps(단계) 1,979 2.4%
4 Long-form Text Layout(장문 설명) 1,786 2.1%
5 Timeline(타임라인) 1,494 1.8%
6 Quote / Testimonial(인용/후기) 1,060 1.3%
7 Versus(버서스) 903 1.1%
8 Profile Card(프로필 카드) 797 1.0%
9 Problem-Solution(문제-해결) 617 0.7%
10 Highlighted Message(하이라이트) 611 0.7%
11 Transformation(전후 변화) 592 0.7%
12 Q&A Card 464 0.6%
13 Listing Table(목록형 표) 438 0.5%
14 Resume / CV(이력서) 311 0.4%
15 Bar Chart(막대 차트) 236 0.3%
16 그 외 리치/구조형 (SWOT, Tree, Nested Scope, Pyramid, Quadrant, Venn, Cycle 등) 각 164 이하 각 0.2% 이하
17 데이터 차트 (Gauge, Pie, Donut, Bubble, Stacked Bar, Combo, Radar 등) 각 51 이하 각 0.1% 이하

상위 8종(List 와 준리치 7종)이 전체의 94.5% 이고, List 하나가 81% 이다.

이 분포를 설계에 어떻게 쓰는가#

방침 규약을 정할 때 샘플 데이터로 별도 검색 실험을 크게 돌리지 않는다. 지금 붙어 있는 컴포넌트 데이터의 분포를 근거로 설계하고, 실험은 꼭 필요한 지점(포맷 실측 등)에만 국한한다. 이유는 두 가지다. 하나는 2단계 배포 전에 규약을 확정해야 해서 대규모 실험을 돌릴 시간이 없다는 것이고, 다른 하나는 아래에서 보듯 분포만으로도 설계 판단에 필요한 답이 대부분 나온다는 것이다.

분포가 말해 주는 것은 세 가지다.

첫째, 실제 검색 풀이 두터운 건 List 와 BigNumber 둘뿐이다. VennDiagram, HierarchicalTree, NestedScope, Pyramid 는 각 164건 이하이고, Gauge, StackedBarChart, ComboChart 같은 데이터 차트는 각 51건 이하다. 8만 건 중 51건이면 검색이 거의 못 맞힌다. 그러니 이 카테고리들의 성립 조건을 엄격하게 걸면 후보가 0이 되어 결국 List 로 fallback되기만 한다.

이 근거로 2026-08-02 에 성립 조건의 정의를 좁혔다. 조건에는 '그 종류로 배치가 불가능해지는 것'만 남기고, 나머지는 기본값을 가진 선택 속성으로 옮겼다. 41종 전량은 capability-table.md, 판단 근거는 projection-model.md 에 있다.

둘째, 복수 태그가 0.3%(260개)뿐이라는 것은 지금 태깅이 컴포넌트 root 기준으로만 붙고 있다는 뜻이다. 그런데 그 260개는 한 컴포넌트가 하위에 여러 타입을 품고 있다는 뜻이다(1절의 design_object_id 4757227 예시). 즉 node-level tag 없이는 이 260개를 제대로 쓸 방법이 없고, 4단계가 필요한 이유가 여기서 다시 확인된다.

셋째, List 편중 81%를 그대로 두고 2단계를 내면 태그를 붙여도 검색 결과가 지금과 크게 달라지지 않는다. 이것이 TODO.md D-2 이고, 7/24 회의에서 "리스트를 먼저 제품화한다"로 이어진 판단의 근거다.

주의: 이 스냅샷은 곧 바뀐다. 유효 컴포넌트 v3.4 조건 6(중복 제거)에서 FLCS 비교가 빠져 태깅 대상이 적게 집계됐다. FLCS 를 계산해 원래 기준으로 되돌리면 태깅 대상이 약 9만에서 약 16만(+79%)으로 늘어난다(7/24 이강윤님 공유).

절대 수치는 늘겠지만 category 별 비율은 크게 흔들리지 않을 것으로 보고 설계에 쓴다. 증량분이 반영되면 이 절을 다시 측정해 갱신한다 (TODO.md C-7).


4. 마일스톤 상세#

1단계, 현행#

Markdown Summary 에 임베딩 검색. 컴포넌트는 변동 없음. 별도 티켓 없다.

2단계, 시멘틱 검색 v2 Phase 1 (MOR-1774, 사실상 스킵)#

2026-08-07 확정, 사실상 스킵. 8/18 마감은 폐기됐고 재조정 없이 4단계로 직행한다. 근거와 경위는 1절 로드맵 표 아래 문단에 있다. 아래 진행 상황은 태깅·적재 인프라 자체는 4단계에서도 그대로 쓰이므로 기록으로 남긴다.

지원 범위는 이번에 태깅되는 카테고리 전부다. steps 는 태깅 작업 순서일 뿐 지원 범위가 아니다. 47종이 붙는데 검색이 일부만 받으면 나머지는 붙여 놓고 안 쓰는 셈이 된다.

알려진 한계 태그가 컴포넌트 root 를 가리키는지 하위 node 를 가리키는지 구분되지 않는다(1절). 검색이 알 수 있는 건 '이 컴포넌트 어딘가에 그 타입이 있다'까지다. 4단계에서 해소된다.

범위 입력과 페이지와 컴포넌트를 같은 v7 단어장으로 태깅한다. 태그를 DB 에 적재하고 BE 에 넘긴다. 태그로 검색하고 순서대로 내용을 대치한다(Summary 와 RLSC 는 안 건드리는 범위). 태그가 없는 데이터는 재태깅하거나 보정한다.

확정된 것 layouts 태깅은 RAT pod 용이고 검색에는 component 태깅만 쓴다(MOR-1755). 복수 태그는 허용(최대 4개 관측), 단어장 밖 값은 불허.

진행 상황 (7/24 기준)

  • 전달 형식 합의 완료. 태그가 하나든 여럿이든 문자열 배열.
  • 적재 기능은 MOR-1744로 7/21 프로덕션 배포 완료(v1.28.104).
  • 전달 경로는 류민 → 지우 → 운식 이다. 7/23 늦게 정리됐다. 데이터가 많아 MORDOR DB 에서 바로 처리하는 편이 낫다는 판단으로 황현진님 단계가 빠졌다.
  • 7/23 적재 검증에서 design_object_semantic_tags 약 10,410건이 원본과 불일치했다. 값이 아니라 배열 직렬화(따옴표 이스케이프) 형식이 깨진 것이었고, 박지우님이 같은 날 S3 재업로드로 정리했다.

태깅 대상이 늘어난다 3절 마지막 문단 참고. 이번 프로덕션 배포는 기존 계획대로 나간다. 증량분과 시멘틱 태그 재실행분은 묶어서 약 2주 뒤 한 번에 BE 로 전달하기로 김수현님과 합의했다(7/24).

layouts 태깅 건은 닫혔다 태그 적재 컬럼은 layouts 와 design_objects 에 semantic_tags 배열로 담고 S3 object path 도 확정됐다.

한동안 "컴포넌트 태깅값만 주면 되고 layouts 태깅은 BE 에 안 줘도 되는지"가 미결로 적혀 있었으나 이미 결론이 나 있다. MOR-1755 본문 '확인 필요' 절이 이렇게 답해 두었다.

레이아웃 태깅도 있나? → 있긴한데 rat pod 에서 사용하는 데이터이며, 검색에는 쓰이지 않는다

근거 Slack 링크까지 달려 있다. 위 '확정된 것'과 같은 내용이고, 미결로 남아 있던 것은 문서 정합 문제였다 (TODO.md G절).

UNICORN-77437 은 존재하지 않는 키다. 여러 문서가 스키마와 contract 협의 티켓으로 이 키를 걸어 놨다. 7/15 번호 구간에 구멍만 남아 있어 삭제됐거나 다른 프로젝트로 옮겨진 것으로 보인다. 살아 있는 BE 티켓은 둘이고 둘 다 최운식님이다. UNICORN-77316(파이프라인에 태깅 정보 적재, Open)과 UNICORN-77328(시그니처v2에 태그 검색 적용, In Progress)이다.

관련 티켓 MOR-1741 상위 Semantic Tagging / MOR-1744 DB 적재 / MOR-1745 BE 전달 / MOR-1755 전달 프로세스 문서 / UNICORN-77318 컴포넌트 태깅 / UNICORN-77432 복수 태그 max

3단계, Summary 를 YAML 로 바꾸고 구조 정보를 검색에 쓴다 (MOR-1775, 실행하지 않음)#

범위 Summary 생성기가 YAML 로 내보내게 바꾸고, YAML 을 JSON Schema 로 검증한다. structure 를 검색 질의와 대치 계획으로 바꾸는 변환기를 만든다(같은 입력이면 같은 결과가 나와야 한다). 기존 Markdown 호환 계층을 둔다.

현재 상태 7/24 회의에서 건너뛰기로 정했다(1절). 스킵 여부 최종 확인이 TODO.md D-3 에 남아 있다.

생성기 교체는 4단계로 갔다 (2026-08-02 확정) 이 단계는 생성기 교체와 검색 로직 변경을 한 번에 하는 범위였다. 3단계를 건너뛰기로 하면서 생성기 교체만 갈 곳이 없었는데, 4단계(MOR-1776) 범위에 넣기로 정했다. MOR-1775 는 실행하지 않음 상태를 유지하고 티켓을 새로 만들지 않는다.

감수하는 것 4단계에서 생성기 교체와 node 태그와 검색 변경이 한 번에 일어난다. 품질이 떨어졌을 때 원인이 셋 중 무엇인지 가리기 어렵다. 4단계 착수 때 단계별 계측 지점을 설계로 보완한다 (TODO.md C-3).

오너 스키마는 정원석, 생성 실무는 규약이 확정된 뒤 허재영님과 황현진님.

4단계, RLSC node 태그 (MOR-1776)#

범위 I.I.S. 로 신규 layout type 을 page 단위로 태깅하고 추론한다. RLSC 스키마를 확장하고 node 마다 태그를 단다. 피라미드 방향이나 순서 같은 레이아웃 고유 속성은 RLSC page 단위 추론에서 뽑는다. 여기에 Summary 생성기의 YAML 전환이 더해진다 (2026-08-02, 원래 3단계 범위였다).

여기서 해소되는 것 태그가 컴포넌트 어디를 가리키는지가 특정된다. 검색과 대치를 해당 node 범위에 정확히 걸 수 있다.

관련 티켓 (RAT pod, 본편은 2026-06 중순 이후 정지) MOR-1425 I.I.S. skeleton / MOR-1448 Layout Type 편입 / MOR-1426 차트 I.I.S. / MOR-1427 텍스트 role I.I.S. Visual Summary PoC 는 재개를 기다리지 않고 같은 출력을 src/lib/visual-summary/iis/ 에서 대행 생산한다 (TODO.md D-5, 2026-08-22).

실행 티켓 (2026-08-06 재편) 상위 Artifact 는 MOR-1923 이고, 이 마일스톤 하위에 Subtask [4-1][4-15] 가 있다. [4-1][4-3](MOR-19091911)은 생성기 쪽(허재영, 황현진), [4-4][4-9](MOR-19241929)는 변환기 스펙, capability 상수, 대치 PoC, 계측 설계, 평가셋, 판정 지표, [4-10][4-15](MOR-1932~1937)는 내용 대치 contract 와 열린 결정 해소다. 티켓 대응표는 plan/jira-artifact-blueprint.md 8절이다.

PoC 코드와 화면이 세워졌다 (2026-08-12). 질의 갈래 전체(생성 → 검증 → 확정 → 검색 질의 → 실색인 검색 → 대치 → 인라인 실시간 렌더)가 Eregion /visual-summary 화면에서 돈다. capability 상수(MOR-1925)와 질의 변환기(MOR-1924)의 PoC 구현, 규약 예시와 실사용 입력 실측, 구현이 임시로 정한 판단 4건은 plan/eregion-poc.md 다. Summary 생성 튜닝과 검색과 대치 고도화는 이제 시작하는 단계다.


5. I.I.S.#

I.I.S. 는 여기서 새로 만드는 개념이 아니라 이미 있는 모듈이다(원 설계 MOR-1425). 새 element 나 role, layout type 이 생겼을 때 Structuriser 를 바로 재학습하지 않고 두 단계 추출로 대응한다.

이름 (2026-08-15 정원석 확정) I.I.S. 는 하나의 모듈이 아니라 계열이다. 이 프로젝트가 필요로 하는 것은 둘이다: I.I.S. (Semantic Component Boundary)(증분 정보 구조화기, 시맨틱 컴포넌트 경계, 4단계)와 I.I.S. (Semantic Component Property)(증분 정보 구조화기, 시맨틱 컴포넌트 속성, 5단계). 속성은 경계를 포함하므로 4단계 ⊂ 5단계다. 코드 식별자는 iisSemanticComponentBoundary / iisSemanticComponentProperty (규칙: iis + 대상 + 국면). 근거와 전체 표기 규칙은 plan/node-tag-and-property-poc-blueprint.md 1절.

진행 방침 (2026-08-22, D-5 닫힘) RAT pod 본편 I.I.S. 재개를 4단계 착수 조건으로 두지 않는다. Visual Summary PoC 가 같은 출력을 대행 생산하며 경계(T-4), 노드 검색(T-5), 노드 대치(T-6), 속성(T-7)을 계속 진행한다. 대치 경로 비교는 PoC 규약 RLSC(binding.ts, 실서비스 아님) / comp-sign 4단계 / property 5단계(T-8) 셋을 목표로 하며, 같은 평가 pool bake(TODO I-8)가 선행이다. 출력 모양이 같으면 RAT pod I.I.S. 가 재개될 때 통째로 교체 가능하다. 모듈 위치와 구현 상태는 plan/poc-module-map.md 다.

지금 (증분 대응):
  ALSC → Structuriser → Old RLSC → I.I.S. → New RLSC

New RLSC 가 충분히 쌓인 뒤 (자체모델 재학습):
  ALSC → New Structuriser(자체모델) → New RLSC

로드맵과의 접점은 4단계의 node 단위 태그를 I.I.S. 가 담당한다는 것이다. 규약(MOR-1773)이 확정되면 출력 계약을 RAT pod 와 제안과 공유하되, PoC 는 그 답을 기다리지 않고 진행한다 (plan/poc-module-map.md 4절).

2·3단계:  [component] → tag: "Steps"     (컴포넌트에만. root 인지 하위인지 미상)
4단계:          [component] → tag: "Steps"
                  ├─ [node A] → tag: "LayeredDiagram"
                  └─ [node B] → tag: "Timeline"

MOR-1773 에서 정한 것 레이아웃 고유 속성은 RLSC page 단위 추론에서 뽑는다. 아래에서 위로 올라가며 클래스를 추론하는 방식은 쓰지 않는다. 클래스가 어느 영역인지는 잡을 수 있지만 그 속성이 어느 요소에 붙는지를 못 가리기 때문이다.

7/21 체크인 결정 유효성 판단을 layout container 기준으로만 보고 role page 로 보지 않아서 Structuriser 가 전체를 한 페이지로 잡고 역할을 잃는 케이스가 있다. Q3 에 I.I.S. 방식으로 RLSC 전체를 놓고 고치기로 했다.

RAT pod 본편 I.I.S. 계열 티켓 네 건은 2026년 6월 중순 이후 RAT pod 쪽에서 정지 상태다. Visual Summary 4단계는 PoC 대행 생산으로 진행하며, 본편 재개는 교체 시점의 외부 이벤트로만 본다 (D-5).


6. 할 일#

TODO.md 가 SSoT 다. 무엇을 언제 누가 하는지는 전부 거기 있다. 이 문서에는 왜 그 결정이 필요한가만 남긴다.

규약 확정은 2026-08-02 에 끝났다. 2026-08-03 에 장식과 계층 결정(B-2, B-3)이 뒤집혔고, 2026-08-04 에 규약 표기까지 반영했다 (spec/schema-v0.5.md 2.7절). 설계 의도는 design/decoration-model.md 가 갖는다.

2026-08-04 에 자기정합 검사를 돌렸다. 스키마와 capability 와 예시가 서로 어긋나지 않는지 14가지를 봤고, 그 목록은 spec/schema/README.md 에 있다. 이 검사로 결함 다섯을 찾아 고쳤고 열린 결정 둘(B-27, B-28)이 새로 열렸다. fallback 은 그래프로 못 박았다(B-26).

2단계 배포 마감은 폐기됐다 (2026-08-07 확정). 2026-08-03 에 제품 배포가 연기된 뒤(Slack) 그 연기가 MOR-1774 의 2026-08-18 까지 포함하는지가 MOR-1939로 넘어가 미결로 남아 있었는데, 2026-08-07 김수현님이 Confluence 코멘트로 포함된다, 8/18 은 폐기하고 재조정한다고 답했다. 같은 자리에서 List 편중 대응(D-2)도 "2단계를 안 하고 4단계로 직행하므로 결정할 것이 없다"로 정리됐다. 다음 주요 작업은 서머리 생성, 스마트시리즈, 조합 순이다.

2026-08-06 에 Jira 를 재편했다. Artifact MOR-1923 을 신설하고 실행 트랙을 MOR-1776 아래 Subtask [4-1]~[4-15] 로 정리했다. MOR-1773 은 Done, MOR-1775 는 Cancelled 다. 2026-08-04 회의로 목표는 9월 말 dev/stg, 10월 중순 production 판정이고, 판정 지표는 시멘틱 검색과 구조 검색의 교집합 상위 10개 품질이다. 상세는 plan/jira-artifact-blueprint.md 다.

7. 참고 문서와 티켓#

↑ 목차로 · 1부 머리로

2장진행 현황 대시보드 파생한눈에 보는 요약

원문 DASHBOARD.md GitHub 에서 보기 4 KB 원문 제목 「Visual Summary 진행 현황 대시보드」

이 문서의 역할 프로젝트 상태를 한눈에 보기 위한 요약판이다. 파생물이고 SSoT 가 아니다. 세부 근거와 결정 사유는 전부 README.md(로드맵·마일스톤)와 TODO.md(할 일)에 있고, 모듈별 구현 상태와 집중 우선순위는 plan/poc-module-map.md 가 담는다. 자동 갱신되지 않으므로, SSoT 가 바뀌면 이 문서도 같이 고쳐야 최신 상태가 유지된다. 오너 정원석 (MORDOR) · 최종 갱신 2026-09-14 (4단계 스킵·5단계 직행 확정 반영)


1. 지금 한 줄로 말하면#

규약(스펙)은 2026-08-02 에 확정됐고 현행 값은 0.5.4 다. 질의 갈래 PoC 와 I.I.S. 색인 갈래 코드는 이미 돈다. RAT pod I.I.S. 본편은 기다리지 않고 PoC 에서 같은 출력을 계속 만든다 (D-5). B절 열린 결정은 2026-09-05 에 전량 닫혔다. Week 27 PoC 는 로컬 인덱스 12,348건 위에서 자유 입력부터 렌더까지 한 번에 돈다. 노드 검색은 컴포넌트 검색과 폴백 없이 분리돼 있다. "스펙과 규모 있는 로컬 PoC 는 있고, I절 contract, 노드 검색 정확도, 프로덕션 I.I.S. 추출이 다음 병목" 으로 읽는 것이 정확하다.

모듈 지도 plan/poc-module-map.md 에 파이프라인 다이어그램, 세 가지 대치 경로, 모듈별 구현 상태, 집중 우선순위 10항이 있다.


2. 로드맵 5단계, 지금 어디까지 왔나#

로드맵의 각 단계는 Summary 형식과 검색 방식과 태그 범위 셋이 동시에 바뀌는 지점이다. 자세한 내용은 README.md 1절에 있고, 여기서는 진행 여부만 본다.

단계 무엇이 바뀌는가 목표 시점 지금 상태
1단계 (현행) Markdown Summary, 임베딩 검색 지금 운영 중, 변동 없음
2단계 컴포넌트에 Semantic Component Category 2026-08-18 사실상 스킵 2026-08-07 확정. 4단계로 직행
3단계 YAML + structure 검색 없음 실행하지 않음 (2026-08-06 Cancelled)
4단계 RLSC node 태그로 정확한 범위 특정 단독 단계로는 건너뛴다 (2026-09-08 확정) 실행 티켓 여섯이 Cancelled. 끝난 산출물(fallback map, 꾸밈 축 등)은 5단계가 그대로 쓴다
5단계 node 에 semantic category 속성까지 여기로 직행한다 (2026-09-08 확정, 정원석) 경계 4,764건·속성 3,955건 로컬 파일럿(09-06), 노드 검색 엔진 배선(09-07). 정확도 판정(K-9-1)과 10만 건 추출(MOR-2415)이 남았다

3. 절별 남은 일, 몇 건씩 남았나#

TODO.md 는 할 일을 A~K 절로 나눈다.

절 무엇을 다루는가 마감 남은 것
A 규약 확정 2026-08-07 1건 (A-4)
B 규약 열린 결정 A 에 종속 0건
C 2단계 배포 사실상 스킵 (2026-08-07) 2건 (C-6, C-7)
D 스쿼드 차원에서 정할 것 각각 다름 1건 (D-8)
E 7/24 회의 후속 미정 3건
F 사후 검증 (마감 후로 미룸) 마감 후 1건 (F-2)
G 최근 해소된 것 없음 기록만
H 문서 정합 백로그 없음 3건
I 내용 대치 contract 미정 14건
J 생성 모델 파인튜닝 착수 전 8건
K Week 27 PoC week 27 2건 (K-9-1, K-11)

품질을 좌우하는 것은 I절(내용 대치 contract) 이다. 실행 1순위는 I-8(평가 pool bake) 이다. K절은 규모와 화면을 먼저 세운 것이다.


4. 지금 가장 급한 병목 셋#

첫째, 평가 pool + 사전 적재 (I-8). 로컬 색인 12,348건과 I.I.S. 파일럿(경계 4,764, 속성 3,955)이 있다. 세 경로를 같은 DO subset 에서 비교하려면 bake 정의가 남는다.

둘째, 노드 검색 정확도 (K-9-1) + 프로덕션 I.I.S. 추출 (K-11). 화면과 로컬 데이터는 있다. 판정과 프로덕션 쓰기가 남는다.

셋째, 내용 대치 contract (I-7, I-9, I-11). T-8 과 복합 구조 오케스트레이션은 규약만 있고 코드가 없다.

해소됨 (2026-09-05) B절 전량. K-1~K-8. 로컬 인덱스 12,348건.

해소됨 (2026-09-07) K-9 노드 검색 엔진 배선. K-10 로컬 파일럿 추출.

해소됨 (2026-08-22) D-5 I.I.S. 재개 대기.

해소됨 (2026-08-07) 2단계 배포 8/18 폐기, 4단계 직행.

해소됨 (2026-09-08) 4단계를 단독 단계로 완주할지. 완주하지 않고 5단계로 직행한다 (정원석 확정). 경위는 README.md 1절.


5. 남의 답을 기다리는 것 ([?])#

TODO D 절에 [?] 항목은 없다. K-11 은 권한 문제라 에이전트가 못 움직인다.


6. 일정을 시간순으로 보면#

시작일 종료일 절 내용 상태
2026-07-13 2026-08-02 규약 확정 (A) 스키마 규약 v0.5 확정 완료
2026-08-01 2026-09-15 4단계 (내용 대치) 내용 대치 contract 정의 (I절) 진행 중
2026-08-12 없음 4단계 (PoC) 질의 갈래 Eregion, I.I.S. PoC 대행 (D-5 닫힘) 진행 중
2026-08-22 없음 4단계 (I.I.S.) T-4 검수 · T-5/T-6 통합 · T-7 검수 진행 중
2026-09-05 없음 K절 1차 인덱스 12,348, L1/L2/L3, 여정 뷰, B절 닫힘 완료
2026-09-07 없음 K절 2차 노드 검색 엔진 배선 완료 (정확도·프로덕션 추출은 아래 09-08 이후 항목으로 이어짐)
2026-09-08 없음 로드맵 4단계 단독 실행 취소, 5단계 직행 확정 (정원석, Slack) 결정
2026-09-30 없음 5단계 dev/stg 판정 (MOR-1776 due date 그대로, 5단계 기준 재확인 필요) 마일스톤
2026-10-15 없음 5단계 production 판정 (같은 단서) 마일스톤

참고#

README.md · TODO.md · plan/poc-module-map.md 모듈 지도 · plan/poc-guide.md · spec/schema-v0.5.md

↑ 목차로 · 1부 머리로

3장할 일과 정할 것 정본할 일·열린 결정의 SSoT

원문 TODO.md GitHub 에서 보기 95 KB 원문 제목 「Visual Summary: 할 일 목록」

이 문서의 역할 이 프로젝트에서 남아 있는 일을 한곳에 모은다. 이 문서가 할 일의 SSoT다. 각 문서에는 맥락(왜 이 결정이 필요한가)만 남긴다. 오너 정원석 (MORDOR) · 최종 갱신 2026-09-09 (I-17 신설 + capability.json 에 표 2절 필드 추가 — 허재영, 정원석 확인 대기. 직전 2026-09-05: K절 신설 — week 27 PoC 목표 가시화. B-10·B-21·B-36·D-4·D-6·F-1·A-6·C-1·C-2·C-4 닫힘, B-34·B-35·B-30 포함 B절 전량 확정 — 정원석 결정, 타인 확인 대기 없이 확정) 관련 README.md 허브 · schema-v0.5.md 규약

2026-09-05 갱신 B-34·B-35·B-30 을 닫았다 (정원석). 셋 다 "OO님 확인 대기"로 남아 있었지만 오너가 직접 결정했다.

  • B-34 NestedScope 는 children 을 안 쓴다. 개수 2 이상, entities 배열 순서 자체가 안쪽→바깥쪽이고 direction(inward/outward, 기본 inward) 속성만 둔다. 9/4 정원석 제안 그대로 확정. 후속 5건(capability.ts 반영, fallback 규칙 문구 정정, 골든셋 6건 재작성, 프롬프트 지시문, I.I.S. 추출기 어휘)은 구현 작업으로 남는다.
  • B-35 구표기 version: "0.5" 는 JSON Schema 패턴을 넓혀 0.5.0 으로 그대로 수용한다(마이그레이션 대신 패턴 확장). 골든셋 135행을 안 건드린다.
  • B-30 measure↔matrix projection 규칙(P9, P10)은 만들지 않는다. 단일 값과 복합 데이터는 형태가 근본적으로 달라 서로 변환하면 원래 의도를 왜곡한다. KPIKeyMetric_Gauge·ProportionComposition_PictogramChart fallback 체인에서 서로를 뺐다(capability-table.md 3.2·3.4·3.5절). 2026-08-17에 plan/fallback-map-review-2026-08-13.md 가 이미 같은 결론을 냈었는데 이후 문서 동기화 중 유실됐던 것을 복원한 것이다.

2026-09-02 갱신 B-20 을 닫았다 (허재영). entity.components[] 를 어느 생성기가 채울지를 갈랐다. PoC 생성기(MOR-1910)는 L3 테스트 범위로 채운다 — 프롬프트 기본값이 이미 L3 다. 실서비스 Summary 생성기(허재영·황현진)는 아직 채우지 않는다. I-11·D-7 과 골든셋 「포함」행은 이 결정의 범위가 아니다. 버전 자리는 올리지 않는다(칸은 원래 optional). 규약 8.7절·8.8절.

지금 상태 규약은 2026-08-02 에 확정됐다(schema-v0.5.md). 마감 2026-08-07 보다 닷새 앞섰다.

2026-08-03 갱신 둘이 바뀌었다. 첫째, 장식과 계층 결정(B-2, B-3)이 뒤집혀 다시 열렸다. 초기 결정은 design/decoration-model.md 에 있고 규약 표기 반영이 남았다(B-18~B-21). 둘째, 제품 배포가 연기됐다. 김수현님이 강창석님과 정렬해 확정했고, 다음 주요 작업 셋을 서머리 생성, 스마트시리즈, 조합 순으로 제시했다 (Slack). 2단계 배포 마감 2026-08-18(MOR-1774)이 그 연기에 포함되는지는 티켓에 반영이 없어 확인이 필요하다.

2026-08-06 갱신 Jira 를 재편했다. Artifact MOR-1923 신설, MOR-1773 Done, MOR-1775 Cancelled, 4단계 MOR-1776(due 9/30)이 Artifact 하위로 옮겨졌고 실행 Subtask [4-1]~[4-15] 가 그 아래 있다. 대응표는 plan/jira-artifact-blueprint.md 8절이다. B-24 는 49종으로 닫혔다 (2026-08-06 정원석 확정, MOR-1909 에 결정 기록).

2026-08-07 갱신 MOR-1939가 8/3 부터 미결이던 질문 셋을 닫았다(김수현님, Confluence 코멘트). (1) semantic tag 없이 검색풀 증량만으로 8/25 배포하는 안은 다루지 않는다, 배포 잠정 연기. (2) judge 실행 여부는 배포 보류. (3) MOR-1774 의 8/18 마감은 이 연기에 포함된다. 8/18 은 폐기하고 재조정한다. 새 날짜는 아직 안 나왔다. 같은 코멘트로 D-2 도 닫혔다 — 2단계를 안 하고 4단계로 직행하므로 List 편중 대응은 결정할 것이 없고, 4단계 fallback map(MOR-1909)으로 넘어간다. C 절 전체와 D-2 를 이 갱신으로 정리한다.

2026-08-14 갱신 대치 결과물의 결함을 기계가 센다. 그동안 이 구간의 품질 판정은 사람이 렌더를 눈으로 보는 것뿐이라 커밋마다 다시 잴 수가 없었다. 결함 15종(나쁜 결과의 원인 넷에 1:1)을 세는 채점기와, 결함을 생성·검색·대치로 나누는 귀속(MOR-1927)과, 두 검색의 교집합 상위 10건 품질(MOR-1929)이 실제로 계산된다. PoC 전체 안내서는 plan/poc-guide.md 다 — 처음 보는 사람은 그것부터 읽는다. 실측 상세는 plan/comp-sign-bridge.md 6.2절. 기준선(실사용 입력 50건·컴포넌트 65개)이 가리키는 것 셋: 넘침이 양쪽 경로 모두 1위 결함이고 수가 거의 같아 경로를 바꿔도 안 줄어든다, 결함의 대부분이 검색 탓이다(생성 36 · 검색 466 · 대치 17), 두 검색은 맨 위에서 거의 합의하지 않는다(1위 일치 65개 중 2개). 마지막 것은 I-5 정합 논의와 MOR-1929 판정 절차 확정의 입력이다.

2026-08-12 갱신 질의 갈래 전체의 PoC 코드·화면이 Eregion 에 세워졌다 → plan/eregion-poc.md. capability 상수(MOR-1925)와 질의 변환기(MOR-1924)의 PoC 구현이 존재하고, 대치 결과를 합성기 없이 브라우저에서 실물 렌더하는 인라인 캔버스가 동작한다. 이 과정에서 B-27 과 P6 에 구현 판단이 임시로 들어갔다(도착 판정은 투영 구조로, P6 은 비교형 도착에서 열 전치 — 그 문서 4절). MOR-1934 확정 시 이 구현 판단을 함께 다룬다. 2026-08-29 갱신 (7) 생성물 둘을 다시 내보내고 자기정합 검사 14가지를 전량 다시 돌렸다. 마지막 전량 실행이 2026-08-04 이었고 그 사이 개정 다섯 번이 검사 없이 들어가 있었다(2026-08-06 병합 헤더, 2026-08-17 fallback v2, 2026-08-21 ComboChart, 2026-08-28 복합 순서, 2026-08-29 type 필수·대분류 제거·enum 45종). 고친 것 셋. (1) spec/schema/visual-summary-0.5.schema.json 을 규약 2.2절에서 다시 내보냈다 — 2026-08-06 병합 헤더 경로(structure.columns, entity.values[])가 그때까지 이 파일에만 빠져 있었다. (2) spec/schema/capability.json 에 PageNavigation 4종을 넣어 41종·엣지 123개에서 45종·엣지 131개가 됐다 — 파일 주석이 "C-4 결정 대기라 뺐다"고 적고 있었으나 그 전제는 2026-08-27 에 이미 깨져 있었다(spec/capability-table.md 2.6절: C-4 가 정하는 것은 뽑힌 뒤의 템플릿 공급이지 성립 조건이 아니다). (3) spec/yaml-examples.md 5.2절 예시가 type: Hierarchy(대분류 단독)를 쓰고 있어 0.5.2 개정 뒤 검증에 걸렸다 — 결합값 + alternatives 로 고쳤다. 검사 정의 자체도 낡아 있었다. F2(두 칸 상한)는 2026-08-17 에 상한이 없어져 폐기했고, F3 은 "종단 셋으로 끝나는가"였는데 같은 날 List 뒤에 ListingTable 을 안전망으로 덧붙인 행이 넷 생겨(List, SWOT, ProfileCard, TableOfContents) "거치거나 자신이 그 셋인가"로 고쳤다. S3·C1·F1·F4·N1 의 수치도 45종·131개로 맞췄다. 14가지 전량 통과. 실행 뒤 상태와 검사 정의는 spec/schema/README.md.

2026-08-29 갱신 (6) 꾸밈 축 값의 모양을 셋으로 갈라 확정했다 (정원석). 종전 문서가 "수치 가중치를 두지 않는다"만 적어 두어서 객체까지 함께 기각된 것처럼 읽혔다. 갈라 적는다. enum(prefer·indifferent·avoid)이 4단계와 5단계가 쓰는 것이고, 객체는 나중 단계의 자리다 — 기각이 아니라 4·5단계 범위가 아닐 뿐이고 그 단계에서 무엇을 담을지는 정하지 않았다. 수치 점수(ornament: 0.8)는 앞으로도 두지 않는다 — 값에 점수를 매기는 것이 의미가 없다고 판단했다(종전 근거인 "채점 코드를 안 건드린다"와 "entity.weight 가 다른 뜻으로 있다"도 그대로다). 그래서 열린 결정이던 "prefer 와 avoid 가 몇 점인지"는 물음 자체가 성립하지 않게 됐고, 남는 것은 점수가 아닌 형태로 검색이 이 선호를 순위에 어떻게 반영하는가와 fallback 보다 먼저 보는가다(이석). 반영은 spec/schema-v0.5.md 2.7절과 design/decoration-model.md 5.2절·6절.

2026-08-29 갱신 (5) page layout 은 규약 범위 밖이고 5단계 이후로 미룬다 (정원석). 여러 컴포넌트를 한 페이지에 어떻게 놓는가를 정하는 규약은 5단계 범위에도 없다 — 5단계가 뽑는 것은 노드 태그와 properties 와 구조 대응이지 배치가 아니다. page 수준에 열려 있는 relations 와 decoration 은 각각 의미 관계와 꾸밈 선호이지 배치 지시가 아니다. 없는 것이 아니라 미룬 것이고, 그때까지 질의 쪽 스키마에 좌표나 그리드를 넣지 않는다(넣으면 색인 쪽과 두 벌로 갈린다). 그 전에 B-32(최상위 컴포넌트 수 상한)와 I-11(조각 조립 오케스트레이션)이 먼저 풀려야 한다 — 개수와 조립이 안 정해진 상태에서 배치 규약을 쓸 수 없다. 규약 2.7절에 상자로 적었다.

2026-08-29 갱신 (4) 규약 enum 을 45종으로 맞추고 지도는 지원하지 않기로 확정했다 (정원석). B-24 가 2026-08-06 에 페이지 역할 4종의 어휘 편입을 정했고 spec/capability-table.md·코드(vocabulary.ts)·생성기 프롬프트는 전부 45종으로 돌고 있었는데, 규약 semanticCategory enum 만 41종에 머물러 있었다 — 그래서 PageNavigation 예시 넷이 스키마 검증에서 거부됐다(부록 F 2번의 결함). enum 을 45종으로 맞췄다. 버전은 0.5.2 → 0.5.3(PATCH) — 이미 정해지고 지켜지던 것을 규약 텍스트에 반영하는 미반영 결함 수정이다. 함께 지도 전용 4종(Distribution, Location, Highlight, Flow)은 지원하지 않기로 확정했다. B-24 원문의 "49종 확정, 지도 4종은 성립 불가로 표시"는 2026-08-17 스쿼드 확인("지도 데이터가 있어야 성립해 structure 가 표현할 대상이 아니다")과 어긋난 채 남아 있었고, 나중 결정이 이긴다. B-24 항목 문구를 45종으로 정정했다.

2026-08-29 갱신 (3) type 어휘에서 대분류 21값을 뺐다 (정원석). type 과 alternatives 는 이제 결합값(대분류.중분류) 41종만 받는다. 근거는 대분류가 실제로 쓰이는 컴포넌트 유형이 아니라는 것이다 — Hierarchy 로는 아무것도 그릴 수 없고, 그릴 수 있는 것은 Hierarchy.HierarchicalTree 이거나 Hierarchy.Pyramid 다. 대분류는 결합값을 묶어 부르는 분류 축이지 표현형이 아니므로 값이 될 수 없다. 종전에는 "중분류를 못 정하면 대분류만 적고 검색이 그 안 전체를 후보로 본다"였는데, 이것은 갱신 (1) 이 막은 것과 같은 회피이고 정도만 약하다. 중분류를 못 정하겠으면 종단 셋 중 하나를 고른다. 대분류 개념 자체가 사라지는 것은 아니다 — fallback 근접도의 "대분류 일치는 강한 신호"와 규약 6절 셋째의 신호는 그대로다. 값으로 못 쓸 뿐 축으로는 계속 쓴다. 깨지는 산출물이 없다 — 골든셋 v1 의 컴포넌트 type 454개가 전부 결합값이고 생성기 프롬프트도 결합값 45종만 준다. 버전은 0.5.1 → 0.5.2(PATCH). fallback.ts 의 major-expansion 경로는 규약상 도달 불가가 됐다 — I-14. 남은 어휘는 결합값 45종이다(갱신 (4)).

2026-08-29 갱신 (2) 규약 버전 규칙을 세웠다 (정원석). 종전에는 v0.5 를 자리에서 네 번 고치면서 버전을 그대로 뒀다(2026-08-06 entities+columns, 2026-08-17 fallback 상한 폐지, 2026-08-28 fallback 규칙 6번, 2026-08-29 type 필수). 그래서 "지금 규약이 무엇인가"를 버전으로 가리킬 수 없었다. 앞으로 규약을 고치면 반드시 올린다. 형식은 MAJOR.MINOR.PATCH 이고 각 자리 0~99 다. 자리 판정 기준은 하나 — 이 변경으로 실제로 무효가 되는 기존 산출물이 있는가. 있으면 MAJOR, 없고 새로 쓸 수 있는 것이 늘면 MINOR, 둘 다 아니면 PATCH 다. 원리가 아니라 실측으로 판정한다. 검증기는 MAJOR·MINOR 만 대조하고 PATCH 는 보지 않는다(PATCH 는 정의상 유효성을 안 바꾸므로). 파일 이름도 MAJOR.MINOR 만 따라간다 — patch 마다 파일명과 링크 30여 곳이 바뀌면 얻는 것보다 잃는 것이 크다. 현행 값은 0.5.1 이고 종전 표기 0.5 는 0.5.0 으로 읽는다. 규칙은 spec/schema-v0.5.md 0.1절.

2026-08-29 갱신 (1) component.type 을 필수로 바꿨다 (정원석). 종전에는 optional 이었고 근거는 "필수로 걸면 '카테고리를 못 고르겠다'가 곧 생성 실패가 된다" 였는데, 뒤집었다. 근거는 이 단계의 존재 이유다 — 규약 1절 설계 원칙 첫째가 "생성 단계에서 최적 semantic category 를 정한다"이고, 어떤 시각 표현형으로 그릴지를 먼저 정해야 표현 방식이 거기서 따라 나온다. type 을 비우고 내보내는 것은 그 판단을 검색 쪽으로 미루는 시각화 책임 회피다. 원칙 1과 optional 은 처음부터 어긋나 있었다. '적을 의무'만 바뀌고 '따를 의무'는 그대로다 — 검색은 여전히 성립 조건과 색인 공급을 보고 fallback 으로 벗어난다. 못 고르겠으면 비우는 것이 아니라 종단 셋 중 하나를 고른다(개수 조건만 보므로 언제나 하나는 성립한다). 깨지는 산출물이 없다 — 골든셋 v1 컴포넌트 470개가 전부 이미 type 을 갖고 있고 PoC 프롬프트도 적으라고 지시하고 있었다. 버전은 0.5.0 → 0.5.1 로 올렸다(PATCH — 실제로 무효가 되는 산출물이 0건이므로. 위 갱신 (2) 의 판정 기준). 고친 자리는 spec/schema-v0.5.md 2절 타입·JSON Schema, 5절, 6절 개정 상자다. 코드 반영은 아직이다 — 아래 I-14. 남은 결정 하나는 대분류만 적는 것을 계속 허용할지다.

2026-08-28 갱신 노드가 여럿인 질의의 fallback 순서를 확정했다. 종전에는 카테고리 하나를 내리는 사다리만 있었고(엣지 131개), 중첩이나 병치로 노드가 여럿일 때 어느 노드부터 포기하는지가 어느 문서에도 없었다. 다섯 단계(N1 전체 일치, N2 모양 보존 강등, N3 최상위 벗기기, N4 반복, N5 평탄화)와 규칙 여섯(N-R1~N-R6)으로 정했고 SSoT 는 spec/capability-table.md 3.6절이다. 한 단계씩 따라가는 예시는 design/content-binding-contract.md 3.7절이다. 정한 사람은 정원석이고 대화로 정했으므로 Slack 이나 Jira 근거 링크가 없다. 핵심 판단은 위부터 벗긴다는 것이다 (그 절 3.6.3). 부모를 벗기면 질의가 여럿으로 갈라져 후보 풀이 커지는데, 자식부터 떼면 내용을 지불하고도 부모가 여전히 복합으로 남는다. 이 확정으로 I-12 가 닫히고 B-31, B-32, D-8, I-11 이 새로 열렸다. 규약 5절에도 fallback 규칙 6번으로 반영했다.

2026-08-11 갱신 MORDOR 체크인에서 D-7 이 새로 열리고 곧바로 닫혔다. 4단계 boundary 태깅 PoC 준비 중 나온 이종중첩 컴포넌트(heterogeneous nested component) — 하나의 컴포넌트 바운더리 안에 서로 다른 type 이 동시에 중첩된 경우 — 를 검색 풀에서 제외하기로 참석자 간 공감대가 형성됐다. 대부분 조합 Fallback 으로 해결되고, 전용 대치 로직을 새로 만들기엔 WSJF 가 낮다고 판단했다. 수량 파악(허재영, 황현진)은 후속 액션으로 남는다. spec/schema-v0.5.md 8.7절에도 용어를 반영했다.

2026-08-13 갱신 D-7 의 후속 액션인 수량 파악이 끝났다. 1,000개 표본에서 이종중첩이 11개로 관측 비율 1.10%(95% Wilson 구간 0.62~1.96%)이고, 전체 183,052개로 환산하면 약 2,014개(1,126~3,586)다. 상한에서도 전체의 2% 미만이라 제외로 잃는 양이 작고, D-7 의 제외 결정과 어긋나지 않는다. 측정 방법과 비용(0원)은 reference/2026-08-13-이종중첩-수량-산출/ 에 적었다. MOR-2048

2026-08-22 (후속) 갱신 대치 경로 역할과 PoC 우선순위를 문서에 못 박았다. binding.ts(규약 RLSC)는 내용시각화·comp-sign 실서비스 경로가 아니다. 4단계 comp-sign vs 5단계 property 비교는 같은 평가 pool에 comp-sign signature와 I.I.S. 속성을 미리 bake한 뒤, 이종 type 간 속성을 슬롯에 맞추는 규칙인 **T-8(cross-type property projection, plan/node-tag-and-property-poc-blueprint.md 6절)**을 붙여야 한다. SSOT: design/content-binding-contract.md 3.6절, I-8/I-9, plan/poc-module-map.md 3절.

상태 표기 [ ] 미착수 · [~] 진행 중 · [x] 완료 · [?] 남의 답을 기다리는 중(내가 못 움직임). 2026-08-22 기준 D 절에 [?] 항목은 없다.

이 문서 훑기

절 무엇인가 마감 남은 것
A 규약 확정 2026-08-07 1건 (A-4) — A-2·A-6 은 2026-09-05 닫힘
B 규약 열린 결정 37건 (B-36·B-37 신설) A 에 종속 0건 — 2026-09-05에 전량 닫힘 (B-30·B-34·B-35, B-25·B-19·B-27·B-28·B-29·B-31·B-32·B-22·B-23, B-10·B-21·B-36 확정, B-37 신설 후 즉시 확정)
C 2단계 배포 사실상 스킵, 8/18 폐기 (2026-08-07) 2건 (C-6, C-7) — C-1·C-2·C-4 는 2026-09-05 낡은 전제로 닫힘
D 스쿼드 결정 (내 결정이 아닌 것) 각각 다름 1건 (D-8) — D-4·D-6 은 2026-09-05, D-9(4단계 스킵·5단계 직행)는 2026-09-08 닫힘
E 7/24 회의 후속 미정 3건
F 사후 검증 마감 후 1건 (F-2) — F-1 은 2026-09-05 닫힘
G 최근 해소된 것 (되살아나지 않게 기록) - -
H 문서 정합 백로그 - 7건 (B-37 과 3.4절 항목은 3.4절 P2 보충으로, P8 항목은 코드 반영으로 2026-09-10 에 닫혔다)
I 내용 대치 contract 미정 16건 (I-11·I-13·I-14·I-15·I-16·I-17 추가, I-12 닫힘)
J 생성 모델 파인튜닝 착수 전 8건
K Week 27 PoC — 10,000 인덱스 위 전체 파이프라인 week 27 3건 (K-9·K-10·K-11 — I.I.S. 5단계 default 전환) — K-1~K-8은 2026-09-05 전량 닫힘

A. 규약 확정#

마감 2026-08-07 (MOR-1773)

# 할 일 상태 담당
A-1 정규화 적용된 최종 단어장 수령 확인 [x] 허재영 정리 → 류민 replace
A-2 이석님께 KDL 미채택 근거 전달 → 시급하지 않음 [x] 정원석 → 이석
A-3 Summary 와 RLSC 공용 스키마 규약 초안 완결 [x] 정원석
A-4 스마트 다이어그램과 스마트 오브젝트 데이터 모델 정합 확인 [~] 정원석
A-5 규약을 그림으로 그려서 공유 [x] 정원석 (2026-08-04 세션에서 공유 완료)
A-6 UNICORN-77437 깨진 링크 정리 [x] 정원석

A-1 근거 단어장은 확보됐다. 허재영님이 2026-07-24 Semantic Identifier 를 최종 정리했다({Major}_{Minor} PascalCase 결합, Q&A → QnA, A+B=C → CombinedResult, Slack). 류민님 replace 는 MOR-1792 Done(7/28, major_minor_identifier 컬럼 추가), 원본 컬럼 교체는 MOR-1870 Done(7/31). repo 사본 semantic-taxonomy-v7.md 가 이 판을 반영한다. 남은 것은 적재분 검사다. 7/28 시점 dev 적재값이 아직 minor 만(List)이었고, 2.2 로 고쳐 올리는 것이 MOR-1844(8/3~8/5)다. C-6 에서 추적한다.

A-2 근거 2026-08-01 정원석 판단으로 fallback했다. 규약 확정 마감과 무관하게 나중에 정렬한다. 포맷은 YAML 로 확정됐고 산출물이 이미 그 위에 쌓여 있어, 정렬이 늦어도 작업이 막히지 않는다 (README.md 2.1절). 닫힘 (2026-09-05 정원석). 지난 MORDOR 체크인에서 강창석·이석과 함께 이미 논의해 전달 완료.

A-3 근거 2026-08-02 확정 → schema-v0.5.md. B절 열린 결정 12건이 닫혔고(G절), 남은 셋(B-7, B-9, B-10)은 규약을 막지 않는다. 스키마는 JSON Schema 검증기로 자체 유효성, 예시 7종 통과, 부정 사례 12종 거부를 확인했다. 부속 문서는 capability-table.md(41종)와 projection-model.md(설계 의도)다.

A-4 근거 문서 조사는 2026-08-02 에 끝났다 → smart-diagram-data-model.md. 실물 코드로 종류 32종, 슬롯 어휘, 저장 contract 를 확인했고, schema-v0.4.md 10절의 진술 셋이 어긋난 것을 정정했다. 둘은 같은 것이고 2026-07-03 에 제품 명칭만 바뀌었다. 남은 것은 어댑터 실측이고 B-13 과 같은 건이다.

A-5 근거 pipeline-flow.md 로 1차 작성했다(2026-08-01). 그림을 스쿼드가 볼 형태로 옮긴 것이 slides/2026-08-04-visual-summary.md 다(2026-08-03, Marp 24장). 남은 것은 2026-08-04 세션에서 실제로 공유하는 것이다.

A-6 근거 UNICORN-77437 은 존재하지 않는 키다(7/15 번호 구간에 구멍). 새로 만들지, UNICORN-77316 과 UNICORN-77328 에 흡수시킬지 정해야 한다. 같은 깨진 링크가 MOR-1774, MOR-1741, 절차 명세, 허브에도 남아 있다. 닫힘 (2026-09-05 정원석). 새 티켓을 만들지 않는다. 이 repo 안 문서(README.md·sources.md 등)는 이미 존재하지 않는 키로 정확히 플래그해뒀고 대체 티켓(UNICORN-77316·77328)도 적어 뒀다 — repo 쪽은 조치 끝. MOR-1774·MOR-1741 등 repo 밖 Jira 티켓 본문의 잔존 링크는 이 결정의 범위가 아니다(Jira 접근 필요, 별도 처리).


B. 규약 열린 결정#

schema-v0.5.md 9절과 짝을 이룬다. A-3 '초안 완결'은 아래가 닫히는 것이다.

2026-08-02 갱신 장식과 계층 4건 중 B-2 와 B-3 은 '질의 쪽 선언 필드를 두지 않는다'는 결정으로 닫혔다(projection-model.md 6절). 질의 쪽 필드가 없어졌으므로 이 둘은 더 이상 I-2 정렬에 걸리지 않는다. 남은 B-1(요구 등급)과 B-4(포기 가능 여부)는 색인 쪽 capability 상수와 카테고리 fallback 순서의 설계 항목이라, I-2 정렬과 E-2 폴백 테이블과 한 덩어리로 진행한다.

2026-08-03 갱신: 위 문단이 뒤집혔다. B-2 와 B-3 이 다시 열렸고, 질의 쪽 필드가 생기므로 이 둘이 다시 I-2 정렬에 걸린다. 설계 의도는 design/decoration-model.md 로 옮겼고, 그 문서가 I-2 세 축 중 둘(등급, 노드 분류)에 답을 냈다. 남은 절반은 comp-sign 변형 3종을 합치는 것이다(B-19). B-1 과 B-4 는 그대로 닫혀 있다. 장식 축에 require 를 두지 않기로 했으므로 요구 등급 3분이 다시 열리는 것은 아니다.

B-1~B-4: 장식과 계층#

# 결정할 것 상태 결론
B-1 필수 요구를 필수 / 선호 / 무시 3단계로 나눌지 [x] 나누지 않는다
B-2 장식 수준(N / N+α / N+β)을 Summary 질의 쪽 필드로 둘지, 색인 쪽 변형 생성으로만 둘지 [x] 질의 쪽 필드를 두지 않는다 → 2026-08-03 에 뒤집혔다. Summary 가 선호를 적는다. 다만 등급이 아니라 축으로 둔다 (design/decoration-model.md)
B-3 계층 필수 여부(essential / incidental / none)를 structure 에 선언할지 [x] 선언하지 않는다 → 2026-08-03 에 뒤집혔다. 컴포넌트를 nested 구조로 두어 해결한다
B-4 '리스트는 포기 불가, 빅넘버는 포기 가능' 같은 포기 가능 여부를 capability 테이블에 명시할지 [x] 명시하지 않는다

B-1 근거 (2026-08-02) 성립 조건을 '그 종류로 배치가 불가능한 것'으로 좁히고, 나머지는 기본값을 가진 선택 속성으로 옮긴다. 채점 코드는 안 건드린다. 기본값은 표현 선택에만 주고 내용에는 주지 않는다. 41종 전량은 capability-table.md 에 있다.

B-2 근거 (2026-08-02) 타이틀과 디스크립션 수준은 구조에서 파생하고, 그림 장식은 색인 쪽에서 처리한다 (projection-model.md 6절).

B-2 뒤집힘 (2026-08-03 정원석) 위 결정이 판정 책임을 색인 쪽으로 넘겼는데 색인 쪽에 그것을 받을 선언이 없었다. capability-table.md 에 장식 언급이 0건이다. 그리고 근거표가 보존 전용 문서에만 있었다. Summary 가 선호를 적고 시스템 전역 선호도 함께 두는 방향으로 바꿨다. 등급으로 두지 않는다. 누적 등급 3단(N / N+α / N+β)은 확장성이 없다. 한 등급에 서로 다른 것이 섞여 있고, 누적이라 '배경은 원하지만 일러스트는 싫은' 조합을 적을 수 없고, 축이 늘 때 끼워 넣을 자리가 없다. 전용 속성은 DECO 계열에만 둔다 (2026-08-03 정원석). 판정 기준은 '대치가 내용으로 채우는 노드는 선호가 필요 없다'다. 그래서 DESC 계열(Element.Title, Subtitle, Description)과 강조(Element.Highlight)는 축이 아니다. 앞은 structure 와 nested component 가 담고 뒤는 entity.emphasis 가 이미 담는다. 축은 ornament(Element.Decoration)와 background(Element.Background) 둘이고 각각 RLSC role 하나에 대응한다. 값은 셋(prefer, indifferent, avoid)이다. 근거와 차용가치 판정은 design/decoration-model.md 다. N+α 는 개념 자체가 사라졌다. 타이틀과 디스크립션 유무는 표현 밀도가 아니라 내용이다. archive/projection-model.md 6절 규칙 3("구조에서 파생한다")이 이 대목에서는 유효했고, 뒤집히는 것은 규칙 4 하나다. 다만 규칙 3 의 수단은 정정했다 (2026-08-03 정원석 지적). 원문이 파생 근거를 "entities 의 label 과 items 보유 여부"로 적었는데 items 는 평탄화 결과물이다. 규약 1절과 8.7절이 "entity.components[] 를 못 읽는 컴포넌트는 자식 entities 를 부모 items 로 편다"로 정의하므로, items 로 판정하면 설명 문단과 하위 항목 목록과 평탄화된 중첩이 구별되지 않는다. 정확한 수단은 entity.label 과 entity.components[](nested component)다. 이 정정으로 계층(B-3)과 DESC 가 같은 수단을 쓰게 되고, B-20 이 둘 다의 전제가 된다.

B-3 근거 (2026-08-02) 계층 보존 요구는 component.type 이 담고, 평탄화 허용 비용은 카테고리 fallback 순서가 정한다 (projection-model.md 6절).

B-3 뒤집힘 (2026-08-03 정원석) 컴포넌트를 nested 구조로 두어 해결한다. 계층의 존재는 이미 entity.children[](같은 컴포넌트 안)과 entity.components[](컴포넌트 경계를 넘는 중첩)가 표현하므로, 새 필드를 만드는 것이 아니라 그 구조를 실제로 쓰는 것이 결정 내용이다. 전제가 하나 걸려 있었다. entity.components[] 는 스키마에만 열려 있고 생성기가 채우지 않았다 (schema-v0.5.md 1절, 8.7절). 중첩을 못 읽는 컴포넌트에서는 자식 entities 가 부모 items 로 평탄화된다. 언제부터 채울지는 B-20 이고, 2026-09-02 에 닫혔다 — PoC 는 채우고 실서비스는 아직 안 채운다.

B-4 근거 (2026-08-02) 회의록 원문 맥락상 '포기'는 검색 조건에서 그 요구를 빼는 것이고 내용 삭제가 아니다("나머지 영역은 일반 텍스트 쪽으로 흘려보냄"). 여러 요구를 가진 입력에서 어느 종류도 탈락하지 않고 후보로 남으므로, 못 채운 요구는 채점 결과와 fallback 규칙 3번이 처리한다. 별도 선언이 필요 없다.

B-5~B-17: 기존 항목#

# 결정할 것 상태 결론 또는 남은 것
B-5 components[] 다중 컴포넌트 래퍼를 core 스키마에 정식 편입할지, 단일 형태를 없앨지 [x] 항상 components[] 로 확정 (2026-08-02, G절). 컴포넌트가 하나면 길이 1 배열
B-6 component.confidence 를 스키마에서도 제거할지 [x] 제거로 확정 (2026-09-04 정원석). 생성기가 못 채우는 값이라 불필요, 스키마상 deprecate. v0.5 JSON Schema(spec/schema/visual-summary-0.5.schema.json)에는 이미 없어 실질 반영 완료, v0.4 archive/schema-v0.4.md 는 이력 보존용이라 그대로 둔다
B-7 갭 3종(big_number, quote, dialogue)을 투영으로만 둘지, RLSC 에 전용 Role 조합을 표준화할지 [x] 둘 다 아니다로 확정 (2026-09-04 정원석). RLSC 스키마에 전용 컨테이너를 새로 만들지 않는다는 결론은 같지만, 정적 role 조합으로 흉내내는 '투영'도 아니다. 실제 처리는 I.I.S.(4·5단계)가 한다 — RLSC 노드를 보고 그 노드가 big_number/quote/dialogue 인지 동적으로 추론·태깅하고, 그 태깅 결과 자체가 검색·대치의 스키마가 된다. RLSC 와 structure 는 앞으로도 절대적(1:1 고정) 매핑을 하지 않는다는 원칙(content-binding-contract.md 3.6절 질의 쪽 순서, 5단계 추출기 I.I.S. Semantic Component Property)과 일치한다. '갭' 표기 정정: 이 표기는 지금 살아있는 capability-table.md(41~45종)가 아니라 폐기된 archive/schema-v0.4.md 3절(11종 시절 표)의 분류였고, 현재 표에는 '있음/부분/갭' 열 자체가 없다
B-8 역방향 갭 중 2단계 지원 범위 확정 (Quadrant, Pie/Donut 우선순위) [x] 41종 전량을 capability-table.md 에 넣어 갭이 사라졌다 (2026-08-02)
B-9 tree 의 children → Graph relations 변환 규칙, nested_scope 와 DisjointEuler 의미 정합 [x] 고정 변환 규칙을 만들지 않는다로 확정 (2026-09-04 정원석). B-7 과 같은 문제다 — structure 와 RLSC 사이에 미리 못 박은 정적 변환표를 두는 대신, I.I.S.(4·5단계)가 실제 RLSC 노드를 보고 그게 tree/nested_scope 인지 동적으로 추론·태깅하고 그 결과가 스키마가 된다. HierarchicalTree→Graph, NestedScope↔DisjointEuler 대응표를 미리 만들 필요가 없다
B-10 tree 와 nested_scope 를 SmartObject 로 볼지 TreeItem 으로 볼지 [x] 아래 근거·결정
B-11 component.type enum(11종)을 v7 단어장에 맞춰 확장할지, enum 은 좁게 두고 tag 를 병기할지 [x] 확장으로 확정 (2026-08-02, G절). v7 41종, { major, minor } 중첩 구조
B-12 stacked_bar 의 stacking 을 RLSC ChartType 어디에 표식할지 [x] RLSC 도 질의 쪽 별도 필드도 안 만든다로 확정 (2026-09-04 정원석). 근거 셋. (1) 엔진 자체가 stacking 을 chartType(BAR_CHART_VER/HOR)과 무관한 독립 옵션(ChartOptions.isStacked, XML showStacked)으로 이미 다룬다 — RLSC 가 여기 새 enum 값을 추가하면 엔진 설계와도 어긋난다. (2) 검색·fallback 은 이미 StackedBarChart ↔ BarChart ↔ LineAreaChart 를 서로의 fallback 대상으로 취급하고 있어(capability-table.md 3절), 후보가 원래 stacked 였는지는 매칭에 안 쓰인다. (3) params.stacked 같은 질의 쪽 필드도 불필요하다 — type: StackedBarChart 선택 자체가 이미 stacked 여부의 신호이므로 중복 선언이다(다른 카테고리도 이름 자체가 신호이지 별도 boolean 을 안 둔다). 남는 일은 대치 시점 쓰기 하나뿐이다 — StackedBarChart 가 최종 선택되면 대치 로직이 대상 컴포넌트의 엔진 isStacked 를 켠다. RLSC 추출도 질의 스키마도 안 건드린다. 구현 시 안 잊게 I-7 에 각주로 남김
B-13 dataset 을 DataViz Matrix 와 encoding 으로 바로 넘길 수 있는지 어댑터로 실측 [x] 범위 좁혀서 정리, 급하지 않음으로 확정 (2026-09-04 정원석). 아래 근거 참고
B-14 capability 테이블을 repo 어디에 두고 검색 모듈이 어떻게 로드할지 [x] TS 상수를 packages/comp-sign 에 두고 BE 소비용은 JSON 생성물로 내보낸다 (2026-08-02)
B-15 원소 간 관계(예: '이 수치가 저 차트의 요약') 표현 필요 여부, 한 페이지 원소 수 상한 [x] 상한은 두지 않는다. 원소 간 관계는 자리만 예약한다 (2026-08-02)
B-17 fallback 할 때 못 읽는 속성을 어디로 보낼지 [x] 엣지 131개 전량에 정의했다 (2026-08-04 재계산 75개 → 2026-08-17 v2 반영 128개 → 2026-08-21 ComboChart 개정 131개). capability-table.md 3.4절 규칙 여덟, 3.5절 적용표. 초판은 55행이었고 fallback 을 그래프로 못 박으면서 다시 계산했다 (B-26)
B-16 relation.type 어휘 확장 [x] compares 추가로 확정 (2026-08-02). 규약 스키마 반영 완료
B-18 꾸밈구조의 규약 표기 확정 [x] 2026-08-04 에 규약에 넣었다. 키는 decoration, page 수준도 연다. 자리와 해석 순서는 schema-v0.5.md 2.7절, 스키마는 같은 문서 2.1절과 2.2절. 1단계에서 생성기는 안 채운다
B-19 comp-sign 변형 3종을 대치 시점 처리로 옮길지 [x] 옮기지 않는다로 확정 (2026-09-05 정원석). comp-sign은 색인 시점에 3벌(원본/Text:Subtitle 제거/Text:Hl 제거)을 미리 계산해 저장하는 현행 방식을 유지한다. I-2 의 남은 절반이었으나 이 항목은 이걸로 닫혔다 → MOR-1933
B-20 entity.components[] 를 생성기가 언제부터 채울지 [x] 2026-09-02 닫힘 (허재영). PoC 생성기는 채운다(L3). 실서비스 Summary 생성기는 아직 안 채운다(L2). MOR-1911
B-21 장식 축 둘이 맞는지, 카테고리 층을 둘지 [x] 2026-08-04 에 role 대조를 마쳤다. Element role 8종 중 대치가 안 채우는 것은 넷(Decoration, Background, Separator, Marker)이고, 축이 둘인 것은 둘을 미뤘기 때문이다. Marker 는 순서를 담아 축이 아닌 것이 확인됐다. Separator 판정 닫힘 (2026-09-05 정원석). 세 번째 축을 새로 만들지 않고 기존 ornament 축에 흡수한다 — Separator도 순서·내용을 안 담는다는 점에서 Element.Decoration과 판정 기준이 같기 때문이다 (design/decoration-model.md 4.1절). PageHeader/PageFooter 판정 닫힘 (2026-09-05 정원석). decoration 모델 관할 밖이다 — 컴포넌트도 위성도 아닌 페이지 프레임이라 애초에 Visual Summary 스코프에 안 들어온다 (design/decoration-model.md 6절). 카테고리 층 여부는 보류(2026-09-05 정원석) — decoration 자체가 지금 실사용 비중이 낮아 구체화 시점에 다시 정한다. 신영빈님 노드 분류 DECO 대조 건은 무효화됨 — 그 노드 분류가 IIS가 아니라 RAT pod 파생물로 확인돼(content-binding-contract.md 1.1절) 대조 대상이 아니다. B-21 전량 닫힘. 41종 카테고리 기본값 채우기(I-4 선행)는 decoration 구체화 시점으로 이월
B-22 장식 role 표식을 색인으로 넘기는 경로 [x] I-4와 동일 건으로 통합해 닫는다 (2026-09-05 정원석). 새 메커니즘을 만들지 않고 semantic_tags denormalize 컬럼 패턴을 그대로 따른다. 구체적 컬럼 설계는 I-4 구현 작업 → MOR-1935
B-23 꾸밈구조 용어 확정 [x] 그대로 확정 (2026-09-05 정원석). 내용구조/structure, 꾸밈구조/decoration, 꾸밈 축/decoration facet, 선호값/preference. '선호 축'과 '장식 축'은 안 쓴다 — 이미 문서 전체에서 이 표기로 일관되게 쓰이고 있어 제안 상태를 유지할 이유가 없었다
B-24 component.type 을 41종으로 좁힐지 v7 단어장 49종 전량으로 열지 [x] 45종으로 확정 (2026-08-06 정원석이 49종으로 정했다가 2026-08-29 에 정정). 페이지 역할 4종은 어휘에 넣고 지도 전용 4종은 지원하지 않는다 — 지도 데이터가 있어야 성립해 structure 가 표현할 대상이 아니다(2026-08-17 스쿼드 확인, 2026-08-29 확정). 결정 기록과 실행은 MOR-1909
B-25 대분류(major)를 제거하고 중분류(minor)만 남겨도 되는지 [x] 기각 — major 유지로 확정 (2026-09-05 정원석). {major}_{minor}가 이미 여러 시스템(design_object_semantic_tags.tags v2.2, iui.comp_signs_v3_4.semantic_tags, comp-sign 매칭 코드)의 실제 저장·질의 SSoT 포맷이다. minor-only(v2.0) 어휘는 이미 폐기됐고, comp-sign 코드 주석이 "두 형식을 섞으면 조회가 조용히 0건이 된다"고 직접 경고한다 — major를 없애면 그 사고를 재현할 위험만 있고 뚜렷한 이득이 없다
B-26 fallback 을 경로 목록으로 읽을지 그래프로 읽을지 [x] 그래프로 확정 (2026-08-04). 각 행이 그 카테고리의 후속 우선순위 목록이고 검색 모듈은 자기 행만 따라간다 (capability-table.md 3.0.1절)
B-27 도착의 개수 상한을 넘을 때 P8(접기)인지 규칙 1(후보 탈락)인지 [x] 규칙 1(탈락)로 확정 (2026-09-05 정원석). 아래 결정 참고 → MOR-1934
B-28 처음 고른 카테고리가 못 읽는 relations 와 필드를 어디로 보낼지 [x] fallback 여부와 무관하게 항상 적용으로 확정 (2026-09-05 정원석). 아래 결정 참고 → MOR-1934
B-29 축이 갈라지는 독립적 rowspan(주축과 무관하게 특정 컬럼 하나만 여러 행에 걸쳐 값이 반복되는 병합)을 어떻게 담을지 [x] 셀 병합 기능 자체를 안 만드는 것으로 확정 (2026-09-05 정원석). 생성 시(4·5단계 모두) 병합을 표현하지 않는다 — 아래 결정 참고
B-37 matrix 셀 값 타입을 컬럼 role 에 맞게 강제할지 (신설, 2026-09-05) [x] 강제하는 것으로 확정 (2026-09-05 정원석). 아래 결정 참고
B-30 measure↔matrix projection 규칙(P9, P10) 정의 [x] 규칙을 만들지 않는다로 확정 (2026-09-05 정원석). 단일 값과 복합 데이터는 형태가 근본적으로 달라 변환하면 원래 의도를 왜곡한다. Gauge↔PictogramChart/BarChart fallback 엣지를 뺐다 (capability-table.md 3.4·3.5절)
B-31 부모의 대분류가 Hierarchy 일 때 Root-Peeling(N3)을 건너뛰고 Content Flattening(N5)으로 갈지 [x] 건너뛰는 것으로 확정 (2026-09-05 정원석, 실측 없이 구조적 논증으로 결정). 아래 결정 참고
B-32 Root-Peeling(N3)과 Re-Peeling(N4)을 반복해 최상위 컴포넌트 수가 늘어날 때 상한을 둘지 [x] 상한을 두지 않는 것으로 확정 (2026-09-05 정원석) — 배치 엔진이 분리된 컴포넌트를 페이지마다 나눠 배치하는 정책이라 개수 증가가 문제되지 않는다. 아래 결정 참고
B-33 그 카테고리가 읽지 않는 내용 필드(List 의 avatar 등)를 info 로 셀지 [x] info로 세지 않는다로 확정 (2026-09-05 정원석). 사후 등급 매기기 대신 Summary 생성기 쪽에서 원천 차단한다 — 아래 결정 참고. 정본 반영 2026-09-11 — capability-table.md 1절에 생성 시점 쓰기 의무로 명문화했다(규약 0.5.6)
B-34 NestedScope 성립 조건의 계수 기준 — 최상위 entities 만 셀지(현행 코드), children 사슬을 포함해 셀지. HierarchicalTree 처럼 min 1 완화 여부와 표준 인코딩(평평한 나열 vs 사슬) 확정도 함께 [x] 2026-09-05 확정 (정원석, 타인 확인 대기 없이 결정). 개수 2 이상, children 안 쓰고 배열 순서 그대로 + direction 속성 (Staircase 11행과 같은 패턴). 후속 5건은 아래 결정 문단 참고
B-35 구 표기 version: "0.5" 산출물의 소급 처리 — JSON Schema 가 구 표기를 0.5.0 으로 수용하도록 패턴을 넓힐지, 기존 산출물을 일괄 마이그레이션할지 [x] 패턴을 넓히는 쪽으로 확정 (2026-09-05 정원석). 0.1절이 이미 "구표기는 0.5.0으로 읽는다"고 선언했으므로 스키마 패턴을 그에 맞춘다. 골든셋 135행은 안 건드린다. 코드 쪽(schema.ts 의 z.literal('0.5'))과 JSON Schema 패턴 갱신은 구현 작업(I-14)으로 남는다
B-36 검색·대치에서 Native Complex Element/non-Native Complex Element 를 구분해야 하는지 — 실측값을 연속 기하(길이·각도·좌표·면적)로 표현해야 하는 카테고리는 non-Native Complex Element(손그림) 후보로 신뢰 가능한 대치가 안 될 수 있다 [x] 구분이 필요하다는 방향, 그리고 "대치 못 하는 카테고리는 생성기 어휘에서 뺀다"는 규칙은 정원석 확정(2026-09-04). 필터링 메커니즘(스키마에 generatable flag, 생성기만 참조)도 정원석 확정(2026-09-05) — 설계는 전량 닫혔고 남은 건 코드 구현(I-16)뿐이다. 45종 전량 분류표를 capability-table.md 10절에 신설 — 판정 기준이 두 번 좁혀졌다. 처음엔 RLSC LayoutElementKeyType 이름만 봤는데 부족했다(Graph/Gantt/VennDiagram/LayeredDiagram/DisjointEulerDiagram/Quadrant 는 스키마에 값→기하 재계산 데이터가 없는 패턴 태그였다) — 원본 미리캔버스 NodeType(pageJson.ts)에 실제로 존재하는 ChartItem/TableItem/GaugeItem 만 RLSC 쪽 native. 그다음 RLSC 태그 유무 자체가 기준이 아니라는 지적이 나와 다시 정정했다 — I.I.S. 는 RLSC 어휘와 무관하게 구조를 독립 추론하므로, 진짜 기준은 "그 카테고리를 뒷받침할 엔진이 RLSC 든 미리캔버스 Smart Diagram vizType 이든 어딘가에 있는가"다. "Native 필요, 영구 없음" 여섯(Quadrant, GanttChart, Calendar, TreeMap, PictogramChart, BubbleChart — 대응 엔진이 RLSC 에도 미리캔버스 Smart Diagram 에도 아예 없음)과 "Native 필요, 지금은 없음" 하나(HierarchicalTree — 미리캔버스 Smart Diagram vizType: hierarchical 은 있음, B-13 합성 규약만 아직 없어서 그 어댑터가 나오면 재검토 없이 복귀)를 구분한다. Funnel 은 재검토 결과 제외 목록에서 뺐다 — List 처럼 순서만 맞으면 되는 카드형이라 Native Complex Element 없이도 대치된다(정원석, 2026-09-04). 구현은 I-16. 2026-09-11 정원석 추가: Visual Summary 첫 출시에서 KPIKeyMetric_Gauge·AttributeComparison_RadarChart 도 생성기 어휘에서 뺀다(상태는 「Native 필요, 있음」 유지 — 엔진 remap 은 있다). 스마트 다이어그램 대응이 가능해지면 복귀. 생성기 불허는 아홉, I.I.S. 는 45종

B-20 결정 (2026-09-02 닫힘, 허재영) 스키마 칸은 v0.4 부터 열려 있었고 8.8절은 이미 「PoC 는 L3 / 실서비스는 L2」라고 적혀 있었는데, 1절·2.1절·8.7절은 여전히 「생성기는 채우지 않는다」로 남아 있었다. PoC 에서 L3 를 테스트하려면 그 문장을 갈라야 한다. PoC 생성기(MOR-1910)는 entity.components[] 를 채운다. 실서비스 Summary 생성기는 아직 안 채운다. 검색 오케스트레이션(I-11), 이종중첩 풀(D-7), 골든셋 「포함」행은 별건이다. 칸이 원래 optional 이라 기존 산출물을 무효로 만들지 않으므로 버전은 올리지 않는다. 규약 schema-v0.5.md 8.7절.

B-34 근거 (2026-08-31 열림) capability-table.md 2.3절 19행은 NestedScope 를 "개수 2 이상, 내용 조건 children 필수, 선택 내용 children (안쪽이 좁은 범위)"로 적는다. 그런데 구현(src/lib/visual-summary/capability.ts 의 count: { min: 2, basis: 'entities' } 와 establish.ts 의 countFor)은 최상위 entities 만 센다. 그래서 children 을 문서 정의대로 '안쪽 범위'로 읽어 사슬 하나로 적은 산출물은 최상위가 1개라 개수 미달로 떨어진다. Summary 골든셋 v1 검증에서 실측됐다 — 범위를 최상위에 평평하게 나열한 행들은 성립하고(이때 children 은 범위가 아니라 부연 설명을 담아, 문서의 선택 내용 정의와 어긋난 채 통과한다), 사슬형 두 행은 탈락한다. 같은 단일 뿌리 구조인 HierarchicalTree 는 "1 이상 실측"으로 이미 완화된 전례가 있다. 정할 것은 둘이다 — 계수에 children 을 포함할지(또는 min 을 1 로 내릴지), 그리고 NestedScope 의 표준 인코딩을 평평한 나열과 사슬 중 무엇으로 못 박을지. 결정 전까지 골든셋의 NestedScope 행 6건은 동결한다. (초안 작성: Claude, 허재영 검토 후 게시)

B-34 결정 — 첫 초안 (2026-09-02, 허재영, 이후 뒤집힘) 뿌리 1개짜리 NestedScope 를 성립으로 보고 entities.min 을 1 로 내리며 표준 인코딩을 사슬(바깥 범위 label → 안쪽 범위 children)로 못박았었다. 리뷰에서 이 인코딩이 semantic-taxonomy-v7.md 자신의 정의와 부딪힌다는 지적이 나와 뒤집었다 — 아래 결정 참고.

B-34 결정 (2026-09-04 제안, 2026-09-05 정원석 확정 — 타인 확인 대기 없이 결정) NestedScope 는 children 을 쓰지 않는다. 개수는 2 이상(원래 기준으로 되돌림), entities 배열 순서 그 자체가 안쪽→바깥쪽 순서를 나타내고(별도 순서 필드 없음), 방향을 뒤집을 수 있는 direction(속성, inward/outward, 기본 inward — 배열 첫 항목이 안쪽) 만 둔다. Steps·Timeline·Staircase·Cycle 같은 다른 순서형 카테고리가 전부 이 방식이라 — Staircase(11행)와 같은 모양이다.

되돌리자는 이유. semantic-taxonomy-v7.md 145행이 스스로 이렇게 적는다 — "NestedScope 와 HierarchicalTree 를 가르는 것은 의미가 아니라 동심원으로 그리는지다. 단일 사슬 포함 관계는 가지가 하나뿐인 트리와 구조적으로 같은 것." 즉 children 사슬 인코딩을 표준으로 못박으면 NestedScope 가 데이터 구조상 HierarchicalTree 와 동일해진다 — 두 종을 가르는 유일한 근거였던 "표현 방식 차이"를 데이터에서 지워 버리는 셈이다. NestedScope 의 전형인 TAM/SAM/SOM 은 애초에 가변 깊이 트리가 아니라 정해진 개수의 동심원에 라벨 하나씩, 안쪽→바깥쪽 순서만 있으면 되는 구조다. 처음엔 Pyramid(18행)의 weight/apex 를 그대로 본떠 순서를 담는 order 필드를 새로 만들었는데, 다시 보니 불필요했다 — 순서는 배열 위치가 이미 담고 있고(weight는 순서가 아니라 층 크기라 Pyramid엔 별도 필드가 필요했던 것), order라는 이름은 이미 SWOT(5행)이 사분면 배치라는 다른 뜻의 속성으로 쓰고 있어 이름도 겹쳤다. 그래서 order 필드는 빼고 direction 속성만 남긴다. 3.3절 의 Pyramid↔NestedScope 짝은 그대로 둔다. 확정에 따라 남는 후속 작업 다섯 (미착수): ① aippt-prisonbreak capability.ts — entities.min 을 2 로 되돌리고 children 필수 조건을 빼고 direction reads 를 추가한다. direction 타입은 'inward' | 'outward' 문자열 리터럴 유니온(enum)으로 선언한다 — number 나 자유 문자열로 두지 않는다(코드 PR 동봉), ② Pyramid↔NestedScope fallback 규칙(3.6절)의 children 언급을 배열 그대로 전달로 정정, ③ Summary 골든셋의 NestedScope 행 6건을 평평한 나열로 다시 쓴다(동결 해제, 사슬로 쓴 것은 되돌린다), ④ Summary 생성기 프롬프트가 direction 을 기본값에 맡기지 않고 입력 글의 서술 순서를 읽어 명시적으로 판단해 적도록 지시문을 추가한다(schema-v0.5.md 8.2절에 근거와 예시를 이미 적어 뒀다) — apex 같은 진짜 표현 선택과 달리 잘못 판단하면 그림의 안·바깥이 뒤바뀐다, ⑤ I.I.S. 속성 추출기(T-7, extractProperty.ts)가 NestedScope 의 direction 을 실제 컴포넌트에서 실측해 채우도록 어휘를 추가한다(plan/node-tag-and-property-poc-blueprint.md 2.1절에 반영해 뒀다, reads.params 제네릭 추출기가 자동으로 태우므로 ①의 capability.ts 반영이 선행 조건이다). (초안 작성: Claude, 정원석 리뷰 중 제기)

B-34 부작용으로 새로 열린 것 (2026-09-04) report/sections/06-examples.md 의 갱신 전 예시는 시장 둘(국내·해외)을 각각 TAM/SAM/SOM 사슬로 나란히 비교하는 모양이었다 — 최상위 entities 2개, 각각 children 3단. 지금 설계(사슬 하나만 담는 flat 배열)는 이 "여러 사슬을 나란히 비교"를 표현할 방법이 없다. 실사용에서 이 모양이 자주 나오면 별도로 다시 열어야 한다 — 후보는 ① 그룹 라벨을 얹은 2차원 구조를 새로 열거나, ② NestedScope 인스턴스를 여러 개 나란히 두는 상위 구조(예: 비교형 컴포넌트)로 해결. 담당 미정.

B-35 근거 (2026-09-01 열림) 0.1절은 버전 규칙을 세우면서 "종전 표기 0.5 는 0.5.0 으로 읽는다"와 "검증기는 MAJOR·MINOR 만 대조하고 PATCH 는 보지 않는다"를 함께 적었다(2026-08-29 갱신 (2)). 그런데 같은 날 내보낸 spec/schema/visual-summary-0.5.schema.json 의 version 은 "pattern": "^0\\.5\\.(0|[1-9][0-9]?)$" 로 세 자리 표기만 통과시킨다 — 구 표기 0.5 를 0.5.0 으로 읽어 주는 자리가 스키마에는 없다. Summary 골든셋 v1.1 초안 전수 검증(2026-09-01)에서 실측됐다 — 135행 전량이 version: "0.5" 로 이 패턴에 걸려 스키마 검사에서 떨어지는데, 같은 행들이 capability·relations 검사는 대부분 통과한다. 개정 이전에 적힌 산출물이라 표기가 낡은 것이지 내용이 무효인 것이 아니어서, 떨어뜨리는 것이 개정 의도인지가 판정거리다. 코드 쪽(schema.ts 의 z.literal('0.5'))은 반대 방향(두 자리만 수용)으로 낡아 있고 I-14 가 이미 추적한다. 정할 것은 하나였다 — 스키마가 구 표기를 0.5.0 으로 수용하도록 패턴을 넓힐지, 아니면 0.1절에서 "0.5.0 으로 읽는다"를 빼고 기존 산출물의 일괄 치환(마이그레이션) 지침을 두는지.

B-35 결정 (2026-09-05 정원석) 패턴을 넓힌다. 0.1절이 이미 "구표기 0.5는 0.5.0으로 읽는다"고 선언해 둔 규칙이 맞고, 스키마 쪽이 그 규칙을 못 따라간 게 버그였다. 골든셋 135행을 치환하는 마이그레이션은 근본 해결이 아니라 앞으로 낡은 표기가 또 나올 때마다 반복해야 하는 대증치료다. 구현 작업(미착수, I-14 와 같은 건): JSON Schema(spec/schema/visual-summary-0.5.schema.json)의 version 패턴을 ^0\.5(\.(0|[1-9][0-9]?))?$ 로 넓혀 두 자리 표기 "0.5" 도 통과시킨다. 코드 쪽 schema.ts 의 z.literal('0.5') 도 같은 방향으로 맞춘다. (초안 작성: Claude, 허재영 지시로 게시)

B-29 근거 (2026-08-06 열림) 강창석님이 Slack에서 표 검색 매칭 탈락 사례를 공유했다. 병합 셀이 있는 실사용 표 둘이 matrix 성립 조건에 걸려 통째로 탈락했다. 조사해 보니 병합이 두 종류였다. 계층형은 닫혔다. 상위 범주 하나가 하위 행 여럿을 거느리는 경우(예: "PROJECT 01" 이 "성장률"·"매출" 두 행을 거느림)는 entity.children[] 로 그대로 담긴다. ListingTable·ComparisonTable 성립 조건에 entities+columns 대안을 열었다(schema-v0.5.md 2.5.1절, capability-table.md 2.1절, 2026-08-06). 독립축 rowspan 은 못 담는다. 강의 4개 행(주축)이 그대로인데 "주말반" 컬럼 하나만 값이 2행씩 걸쳐 반복되는 사례(와인클래스 캡처)는, 표 전체를 다른 축으로 다시 묶지 않는 한 트리 하나로 표현이 안 된다. 셀 단위 span 메타데이터를 두면 되지만, 그건 matrix(차트와 공유하는 평평한 격자)를 다시 건드리는 길이라 2.5.1절이 피하려던 것과 같은 문제로 되돌아간다.

B-29 결정 (2026-09-05 정원석) 셀 병합(span) 기능 자체를 만들지 않는다 — 실측 없이 확정. 세 근거가 전부 같은 방향을 가리켜서 빈도 데이터 없이도 판단 가능했다. (1) 이건 자연어 설명에서 잘 안 나오는 패턴이다 — Summary는 내용을 의미 구조로 옮기는 것이지 기존 스프레드시트의 셀 레이아웃을 픽셀 단위로 베끼는 게 아니다. (2) 못 담아도 정보가 안 사라진다 — 병합된 값을 각 행에 반복해서 적으면 그만이고, 셀 병합은 내용이 아니라 표현(꾸밈)이다(4.6절 "내용 대치는 꾸밈에 관여하지 않는다"와 같은 성격). (3) 고치는 비용이 구조적으로 비싸다 — matrix는 표뿐 아니라 차트와 공유하는 격자라, 병합 메타데이터를 넣으면 드물게 쓰이는 표 병합 하나 때문에 모든 matrix 소비자(차트 어댑터 포함)가 복잡해진다. 적용 범위: 4단계(comp-sign 생성기)·5단계(I.I.S.) 모두 — 생성 시 병합을 표현하지 않는다.

B-37 근거·결정 (2026-09-05 신설 및 확정, 정원석) schema-v0.5.md의 Matrix.Column은 이미 role?: 'category' | 'value' | 'x' | 'y' | 'size' | 'start' | 'end' | 'date'를 갖고 있는데, rows: Array<string | number | null>[]는 role과 무관하게 전 셀을 느슨하게 허용한다. 그래서 role: 'value'인 컬럼에 문자열이 들어가도(예: 막대 높이가 "많음") 스키마도 capability 성립 조건도 안 걸린다 — 차트는 문자열로 못 그리는데 말이다. 결정: 컬럼의 role이 셀 값 타입을 강제한다.

role 허용 타입 비고
미지정 또는 'category' string row header. 값이 아니라 이름이라 문자열이 맞다
'value', 'x', 'y', 'size' number 또는 null (string 금지) 막대 높이·좌표·크기 — 문자열로는 렌더링 불가
'start', 'end', 'date' 날짜로 파싱 가능한 값(ISO 문자열 또는 timestamp 숫자)만 (임의 문자열 금지) Gantt/Calendar

컬럼 헤더(Column.label)는 스키마 타입이 이미 string 고정이라 모호함이 없다. 적용 기준은 카테고리 이름이 아니라 컬럼의 role이다 — BarChart든 ListingTable이든 상관없이 같은 규칙이 적용되고, 테이블 카테고리는 대부분 value/x/y/size role을 안 써서 자연히 문자열이 허용되며, 차트 카테고리는 이 role들을 선언하니 자동으로 숫자가 강제된다. 카테고리별 예외 규칙을 따로 안 만들어도 된다. 구현 (미착수): establish.ts(또는 zod)에 컬럼 role별 셀 타입 검사를 추가한다.

B-30 근거 (2026-08-17 열림) PictogramChart(matrix 필수)와 Gauge(measure 필수)가 서로의 fallback 후보로 근접도(원칙 2)상 타당한데, capability-table.md 3.4절 P1~P8 어디에도 measure↔matrix 변환 규칙이 없다. 지금은 후보만 순서에 넣어 두고(capability-table.md 3.2절, KPIKeyMetric_Gauge·ProportionComposition_PictogramChart 행) 3.4절에 P9(measure→matrix, value를 1행짜리 matrix로 접는다)와 P10(matrix→measure, 대표값 1행을 value로 쓴다)을 "제안, 미확정"으로만 올려 뒀다. P10 의 대표값 선택 기준(첫 행, 합계, 최대값 등)이 없다 — 확정해야 실제 fallback 이 동작한다.

B-30 결정 (2026-09-05 정원석) P9·P10 을 규칙으로 만들지 않는다. 단일 값(measure)과 복합 데이터(matrix)는 형태가 근본적으로 달라서, 서로 변환하는 규칙을 두면 원래 의도를 왜곡한다 — Gauge(measure)를 BarChart/PictogramChart(matrix)로 접으면 target/min/max/thresholds 가 전부 사라진 막대·아이콘 하나만 남고, 반대로 PictogramChart 의 여러 행 중 하나를 뽑아 Gauge 의 대표값으로 쓰면 원본에 없던 target/max/min 을 채울 근거도 없이 나머지 행을 버리게 된다. 2026-08-17에 plan/fallback-map-review-2026-08-13.md 가 이미 같은 결론(Gauge↔PictogramChart/BarChart 제외)을 냈었는데, 이후 이 문서·report/* 가 옛 체인으로 되돌아가 있었다 — 이번이 그 유실 복원이다. KPIKeyMetric_Gauge 체인을 BigNumber → HighlightedMessage로, ProportionComposition_PictogramChart 체인을 BarChart → List로 되돌렸다(capability-table.md 3.2·3.4·3.5절). PictogramChart → BigNumber(P4)도 재검토 끝에 뺐다 — 처음엔 P4(행마다 entity로 편다)로 행을 보존하니 예외로 봤는데, P4는 label+items만 채우고 BigNumber의 필수 조건인 entity당 value는 안 채운다. BigNumber도 entities가 여럿일 뿐 entity 하나당 값 하나인 단일값 모델이라 matrix를 우겨넣을 수 없다는 점에서 Gauge와 같은 범주다. 엣지 수는 131→127.

B-31 근거 (2026-08-28 열림) capability-table.md 3.6절이 N3(부모를 벗겨 자식을 최상위로 올린다)을 N5(평탄화)보다 먼저 둔다. 둘 다 텍스트를 안 버리는데 N3 이 카테고리 표현을 더 남기기 때문이다. 계층에서는 이 근거가 약해진다. N-R1 이 벗긴 부모의 label 과 items 를 승격된 조각으로 옮기지만 남는 흔적은 부모 한 층뿐이고, 깊이 3 이상이면 소속 관계를 되살릴 수 없다. 평탄화는 들여쓴 items 로 층을 남긴다. 그래서 대분류가 Hierarchy 인 부모는 N3 을 건너뛰자는 제안을 N-R5 로 올려 뒀다.

B-31 결정 (2026-09-05 정원석) 건너뛴다 — 실측 없이 구조적 논증만으로 확정. Root-Peeling(N3)은 한 겹만 벗기므로, 깊이 3단 이상 계층(예: 회사→부서→직원)에 반복 적용(Re-Peeling)하면 직원들이 전부 최상위로 흩어져 "회사-부서-직원" 소속 관계 자체가 사라진다 — 남는 건 형제 인접성뿐이다. Content Flattening(N5)은 들여쓰기로 임의 깊이를 그대로 보존한다. Hierarchy가 대분류라는 건 그 콘텐츠의 핵심이 계층 관계 자체라는 뜻이므로, "카테고리 표현을 더 남긴다"는 Root-Peeling의 일반적 이점보다 "계층 관계를 안 깨뜨린다"는 Content Flattening의 정확성이 우선한다. 이건 실사용 빈도와 무관한 구조적 성질이라 실측을 기다릴 필요가 없었다.

B-33 근거 (2026-08-29 열림) structure 와 params 의 검사 비대칭을 어디까지 둘지다. 먼저 오해를 걷어낸다 — structure 를 검사하지 않는 것이 아니다. 필드 이름·타입·필수 조건은 zod 가 .strict() 로 전량 보고(어휘 밖 필드 이름은 error), 카테고리별 최소 요구는 capabilityCheck.ts 의 성립 조건 판정이 본다. params 는 열린 객체(Record<string, unknown>)라 스키마가 아무것도 못 잡으므로 capability 의 키 검사가 스키마 대역으로 들어간 것이다. 진짜 비대칭은 하나뿐이다 — "그 카테고리가 읽지 않는 값이 실려 있는 것"을 params 는 warning 으로 세고 내용 필드는 아무 등급도 안 붙인다. 지금의 근거는 사라지느냐다. 안 읽는 params 는 P2 로 버려져 의도가 조용히 소멸하고, 안 읽는 내용 필드는 P3 으로 items 에 보존돼 소멸하지 않는다. 다만 소멸하지 않는다고 결함이 아닌 것은 아니다 — List 에 avatar 를 적었다면 그 내용은 ProfileCard 였어야 한다는 신호다. 실패로 올릴 일은 아니지만 info 로 세어 생성 품질 지표에 넣을 수는 있다(같은 성격의 info 등급이 이미 있다 — 자손을 합쳐야 성립하는 경우). 정할 것은 셀지 말지와, 센다면 params 쪽도 warning 에서 내릴지다.

B-33 결정 (2026-09-05 정원석) info로 세지 않는다. "정확하게 생성되지 않으면 내용 자체가 이상하게 생성되고, 사후 처리로는 이미 죽은 semantic 정보를 못 살린다"는 게 이유다 — List에 avatar를 적은 순간 원래 ProfileCard가 표현하려던 사람 카드 구조는 사라지고, items 끝에 붙는 텍스트 한 줄로는 되돌릴 수 없다. 사후 등급이 아니라 생성 시점에 원천 차단한다.

B-33 근거 한 줄 정정 (2026-09-11). 위 근거 문단은 "안 읽는 내용 필드는 P3 으로 items 에 보존돼 소멸하지 않는다"고 적었는데, 그것은 대치 경로 한정이다. P3 는 capability-table.md 3.4절이 "fallback 은 카테고리를 바꾸는 일이므로" 출발→도착 엣지에서 옮길 것을 정하는 규칙이라, 생성기가 처음부터 그 카테고리로 쓰면 돌지 않는다. HierarchicalTree 를 Pyramid 로 재지정한 실측에서 하위 9개가 그대로 소멸했다(iui #1023). 결론(생성 시점 원천 차단)은 그대로고, 근거가 오히려 더 강해진 자리다.

이 결정은 "빈 결과보다 낫다"(색인 검색·fallback 철학, 이 문서 위쪽 성립 미충족 논의)와 범위가 다르다 — 그 철학은 이미 존재하는 디자인 중에서 고르는 색인 검색에 맞는 논리다(재생성이 불가능하므로 흡수가 유일한 선택지). Summary 생성기는 그 자리에서 LLM이 새로 만드는 것이라 재생성이 값싸게 가능하므로, 잘못된 카테고리 선택을 흡수할 이유가 없다. B-33의 결정은 생성기 스코프에만 적용되고, 색인 fallback(B-30 등 대치 경로)에는 적용되지 않는다.

구현 방향 (미착수):

  1. B-11 에서 이미 쓴 패턴(생성 시점에 capability 상수에서 뽑은 '생성용 스키마'를 structured output 에 제약)을 내용 필드(entities[].items, params)에도 확장한다 — type 이 선택되면 그 카테고리의 reads.content/reads.params 로 필드가 제한된 스키마를 LLM structured output 에 넘긴다. List 를 고른 순간 avatar 를 애초에 못 쓴다.
  2. Summary 생성 파이프라인의 capabilityCheck.ts/strictCapability 는 생성 검증 단계에서 기본값을 켠다 — 카테고리 불일치(성립 조건 미충족, 안 읽는 필드 존재)를 통과시키지 않고 재생성 루프를 돈다.
  3. 위 둘을 실제로 하면 이 케이스는 대부분 1번에서 막혀 2번까지 갈 일이 드물겠지만, 안전망으로 둘 다 둔다.

B-32 근거 (2026-08-28 열림) N3 과 N4 는 부모를 벗겨 자식을 최상위 components[] 로 올린다. 반복하면 최상위 원소 수가 늘어난다. design/content-binding-contract.md 3.7절 예시는 1개에서 9개가 됐다. 규약 8.6절이 "원소 수 상한은 두지 않는다. 실데이터에 다중 컴포넌트 페이지가 아직 없어 근거 있는 숫자를 정할 수 없다"로 두었으므로 규약 위반은 아니다. 다만 한 페이지에 9개를 배치할 수 있는지는 배치 엔진의 몫이고 확인한 바 없다. 상한을 둘지, 두면 넘칠 때 N5 로 보낼지 정해야 한다.

B-32 결정 (2026-09-05 정원석) 상한을 두지 않는다. 최신 배치 정책은 Root-Peeling/Re-Peeling으로 갈라진 컴포넌트를 한 페이지에 다 욱여넣지 않고 페이지마다 나눠 배치한다 — 그래서 개수가 9개든 20개든 여러 페이지에 걸쳐 배치하면 되고, 렌더링 실패 위험이 없다. 규약 8.6절 현행(상한 없음) 유지로 닫는다.

부수 아이디어 (2026-09-05, I-11 설계 메모로 이관) Root-Peeling이 잃는 것 중 하나가 "형제 사이의 공간적 인접성"이다(N-R1). 그냥 흩어 배치하는 대신, 같은 부모에서 갈라져 나온 조각들을 배치 시점에 출처 태그로 묶어 크기를 고려해 한 화면/페이지에 2~3개씩 클러스터로 배치하면 이 인접성을 부분적으로 복원할 수 있다. 이건 스키마 결정이 아니라 I-11(복합 구조 fallback 오케스트레이션)의 구현 설계 문제라 그쪽에 메모로 남긴다.

B-26 근거 (2026-08-04 닫힘) 3.2절은 행마다 완결된 경로로 읽히는데 3.3절은 "방문한 카테고리를 기록하고 중복을 건너뛴다"로 순회를 전제했고 3.5절은 엣지 집합이었다. 셋이 같은 것을 다르게 말했다. 행을 이어 붙여 읽으면 나가는 자리가 둘 이상인 카테고리 34종 중 21종에서 우선순위가 정해지지 않는다. 예를 들어 Versus 행은 ComparisonTable → List 인데 ComparisonTable 자기 행은 ListingTable → List 다. 그래프로 정했다. 각 행이 그 카테고리의 후속 우선순위 목록이고 자기 행만 따라간다. 이 정의에서 순환이 불가능해져 방문 기록이 필요 없어지고, 4절의 두 칸 상한도 '어느 행도 대체 후보를 셋 이상 두지 않는다'는 표의 성질이 된다. 3.5절 엣지 표를 이 정의로 다시 계산해 55행(고유 51)에서 75행이 됐다.

B-27 근거 (2026-08-04 열림) 규약 5절 1번은 "성립 조건을 못 채우면 그 카테고리가 후보에서 빠진다"이고 성립 조건에는 개수 범위가 들어 있다. 그런데 capability-table.md 3.4절 P8 은 "도착의 개수 상한을 넘으면 초과분을 마지막 항목의 items 로 접는다"이다. 같은 상황에 답이 둘이다. 항목 20개짜리 Steps 가 Timeline(상한 15)으로 물러날 때, 규칙 1대로면 Timeline 이 후보에서 빠지고 P8 대로면 5개를 접어 Timeline 으로 그린다.

B-27 결정 (2026-09-05 정원석) 규칙 1(탈락)로 확정, P8(접기) 폐기. 접으면 정보는 안 사라지지만 마지막 항목 하나에 텍스트가 몰려 overflow가 눈에 띄게 나기 쉽다 — "내용을 안 버렸다"는 게 "보기 좋다"를 보장하지 않는다. 탈락시키면 fallback 사다리를 계속 타고 내려가는데, 모든 사다리 끝에는 개수 상한이 없는 안전망(List 등)이 있으므로 정보도 안 사라지고 렌더링도 안 깨진다. 표에 P8이 붙어 있던 16개 엣지 전부에서 뗐다(capability-table.md 3.4·3.5절).

부수 효과: P8 폐기로 HighlightedMessage("정확히 1")가 더 이상 개수와 무관하게 항상 성립하지 않는다. KPIKeyMetric_BigNumber, KPIKeyMetric_Gauge, Utterance_QuoteTestimonial 세 카테고리의 체인이 HighlightedMessage에서 끝나고 있었는데, 이러면 개수 초과 질의가 그 지점에서 사다리째 막힌다. 셋 다 끝에 List를 안전망으로 추가했다(3.2·3.5절). 엣지 수는 127→130.

부수적으로 확인된 것 — 개수 상한의 적용 범위. 이 논의 중에 "Timeline의 상한 15" 같은 개수 조건이 Summary 생성기·질의 쪽에만 적용되는 실용적 제약이라는 게 명확해졌다 — 검색 풀의 다양성 한계에 맞춘 것이다. I.I.S.가 실제 디자인의 구조를 인식·태깅할 때는 이 상한을 적용하지 않는다 — 실제 디자인이 20단계짜리 Timeline이어도 그건 그냥 Timeline이다. capability-table.md 2절에 이 구분을 명시했다. 지금 표는 두 해석을 섞어 쓰고 있었다. Steps → Timeline, Pyramid → Staircase, GanttChart → Timeline 셋에는 P8 이 붙어 있는데 같은 조건인 QuoteTestimonial → HighlightedMessage(도착 개수 '정확히 1')에는 빠져 있었다. 2026-08-04 에 P8 을 붙여 표 안에서는 일관되게 했으나, 규칙 1 과 P8 중 무엇이 우선인지는 정해야 한다. 이석님과 정할 항목이다.

B-28 근거 (2026-08-04 열림) capability-table.md 3.4절 규칙 여덟은 fallback 엣지 하나하나에 붙어 있다. 그래서 카테고리가 그대로 성립해 fallback 이 일어나지 않으면 적용될 자리가 없다. 그런데 처음 고른 카테고리도 structure 의 전부를 읽지는 않는다. 규약 1절이 "선언되지 않은 것은 무시한다"로 두었으므로 그냥 지나간다.

B-28 결정 (2026-09-05 정원석) P3·P7("못 읽는 건 버리지 않고 옮긴다")을 fallback 여부와 무관하게 항상 적용한다. 새 판단이 아니라 이미 있는 원칙(내용은 안 버린다)의 적용 범위 누락을 메우는 것이다 — "fallback 엣지에만 건다"는 의도된 제한이 아니라 구현 범위를 좁게 잡은 것으로 보인다. 최종 선택된 카테고리가 fallback을 거쳤든 첫 선택이든 상관없이, 그 카테고리가 못 읽는 relations·내용 필드는 P3·P7대로 옮긴다. 실물 사례가 있다. compares 를 성립 조건으로 선언한 카테고리는 AttributeComparison.Versus 하나뿐이고 개수 조건이 '정확히 2' 다. 3항 이상을 비교하는 입력은 ComparisonTable 로 가는데 ComparisonTable 은 관계를 안 읽는다. 그래서 '가격과 리전 수는 반비례한다' 같은 relation.desc 가 fallback 없이도 사라진다 (yaml-examples.md 3.2절). 규약 5절 4번의 문장이 이 경우를 덮는 것처럼 읽힌다. "선택된 카테고리가 못 읽는 것은 버리지 않고 옮긴다"인데, 옮기는 규칙은 엣지에만 있다. 규칙 P3 과 P7 을 '카테고리 하나에 대해서도' 적용하도록 넓힐지 정해야 한다. 넓히면 B-16 의 관계 어휘 확장 논의도 부담이 줄어든다.

B-24 근거 (2026-08-03 열림) 허재영님이 "41종(v7 단어장 그대로)"라는 Slack 공지 표현이 부정확하다고 지적했다. 단어장은 49종이고 8종이 의도적으로 빠졌다 (Slack). 문서는 이미 맞다. capability-table.md 범위 절이 "49종에서 페이지 역할 4종과 지도 전용 4종을 뺀 41종"으로 적어 두었다. 틀린 것은 공지 문구다. 그런데 범위 자체를 다시 볼 이유가 생겼다. 49종 전량으로 여는 편이 나을 근거가 셋이다.

  1. B-11 결정 구조와 맞는다. contract 스키마는 type: string 으로 열고 생성용 enum 은 capability 상수에서 뽑기로 했다. 즉 enum 은 capability 의 파생물이다. 그러면 단어장 49종을 capability 에 다 넣고 8종은 '질의 대상 아님'으로 표시하는 것이 층이 맞다. 지금은 capability 자체가 41종이라 파생 이전에 범위가 잘려 있다.
  2. 페이지 역할 4종은 색인에 실재한다. 유효 컴포넌트 v3.5 가 page role 확대(Opening, SectionDivider, Ending 허용)를 반영했고, 페이지 역할 태그는 Page Role 을 승계해 logic 으로 태깅한다(C-4). 색인에 있는 값을 질의로 못 적으면 대조가 비대칭이 된다.
  3. 지도 전용 4종을 뺀 근거는 성립 조건 판단이다. '지도 데이터가 있어야 성립해 structure 가 표현할 대상이 아니다'는 capability 의 성립 조건 이야기이고, 어휘에서 빼는 것과는 층이 다르다. capability 에서 '성립 불가'로 두면 같은 효과가 나면서 단어장과 어휘가 어긋나지 않는다. 반대 근거도 있다. 페이지 역할은 의미 구조가 아니라 페이지에서의 자리다. Content Archetype 과 semantic tag 는 서로 다른 것을 분류한다(README.md 2절 '헷갈리기 쉬운 셋' 둘째). 축이 다른 값을 한 enum 에 넣으면 생성기가 둘을 섞을 위험이 있다. 판단이 필요하다. 지금 문서는 41종 유지이고, 이 항목이 닫히기 전에는 41종 표기를 바꾸지 않는다.

B-10 근거 tree 는 답이 나왔다. hierarchical 이 web-2 저장 스키마의 vizType 6종에 들어 있고 jari 에 hierarchy_tree 머신이 있다. TreeItem 이 아니라 스마트 다이어그램이다 (smart-diagram-data-model.md 11절). nested_scope 는 concentric_rectangles 가 후보였다.

B-10 결정 (2026-09-05 정원석). nested_scope는 SmartObject가 아니라 TreeItem(일반 컨테이너)으로 본다. 지금 엔진에 없는 것을 곧 생길 거라고 가정하고 판정을 미뤄두면 안 된다(B-36과 같은 원칙). #프레젠테이션트라이브_스마트다이어그램 채널의 실제 vizType 목록(2026-06-23, 한유진: hub and spoke·hierarchy tree·cycle·staircase·venn diagram·timeline 6종)에 concentric_rectangles는 없고, "동심원" 관련 언급 자체가 그 채널에 없어 계획도 확인되지 않는다. 다행히 생성 쪽엔 이미 안전장치가 있다 — ScopeCoverage_NestedScope는 capability-table.md 10절(B-36)에서 이미 "둘 다 가능"(항목 수가 적고 direction/shape뿐이라 Native Complex Element 없이 손그림으로도 대치 가능)으로 분류돼 있어, 없는 엔진에 기대는 fallback 경로 자체가 없다. 엔진이 실제로 생기면 그때 SmartObject로 재분류한다.

B-11 결정 전 검토 기록 미매핑 라벨이 질의의 절반 이상이라 좁은 enum 을 유지하면 tag 병기가 사실상 필수였다(격차표는 semantic-taxonomy-v7.md 4절). 이 방식은 스키마 고정 원칙과 충돌한다. enum 이 스키마 안에 있으면 단어장이 늘 때마다 스키마가 바뀐다. 셋째 선택지가 결정을 갈랐다. contract 스키마는 type: string 으로 열어 두고, 생성 시점에는 capability 상수에서 enum 을 뽑아 낀 '생성용 스키마'를 따로 조립해 structured output 에 넘긴다. 어휘가 늘어도 스키마 버전이 안 바뀌고 enum 제약도 유지된다. 관련 사실로, 현행 예시 3종이 이미 enum 밖 값을 썼다(H절).

B-13 근거 A-4 와 같은 건이다. 저장 contract 가 version: 2 로 바뀌었으므로 yaml-examples.md Appendix A 기준 설계를 다시 봐야 한다 (smart-diagram-data-model.md 8절, 12절).

2026-09-04 갱신 (정원석) 이 어댑터가 언제 필요한지부터 다시 봤다. poc-module-map.md 2.5절의 대치 경로 셋(PoC 규약 RLSC binding.ts, 4단계 comp-sign, 5단계 property T-8)은 전부 이미 존재하는 실제 컴포넌트(디자이너가 도형·선·텍스트로 만든, native Smart Diagram/Chart 요소가 아니어도 되는 진짜 Design Object)를 검색으로 찾아 그 구조에 내용만 채우는 방식이다. I.I.S. 가 구조만 제대로 뽑으면 native 요소가 아닌 "가짜"(합성 아님) 컴포넌트도 이 세 경로로 대치 가능하고, 이 셋은 dataVizProps/chartProps 어댑터에 의존하지 않는다. 이 어댑터가 필요한 경우는 검색이 실패했을 때 처음부터 새로 합성(synthesis)하는, 아직 착수도 안 한 네 번째 경로뿐이다. 모듈 지도 2.2절 구현 상태 표 어디에도 이 합성 경로가 없다 — 지금 로드맵의 주력이 아니다. 실측 결과도 함께 남긴다. 다이어그램 계열(dataVizProps)은 smart-object-ax 32종 중 화면에 그려지고 에디터가 저장까지 하는 것이 tree(hierarchical)와 venn 둘뿐이다(10절 대응표). 나머지(nested_scope, pyramid, quote 등)는 jari 렌더 머신이 없어 지금은 검증 불가다. 차트 계열(chartProps)은 리스크가 낮다 — ChartBuilder(miricanvas-web-2 packages/engine/miricanvas-engine-util)가 type·dataSource(2차원 배열)·options 세 값만 받아 축·범례·그리드·계열까지 전부 합성해준다. dataSource: Cell[][] 가 dataset.matrix.rows((string|number|null)[][])와 구조적으로 거의 1:1이라, rootComponent 같은 무거운 재귀 트리를 손으로 조립할 필요가 없다. 결론: 지금 당장 급한 항목이 아니다(주력 대치 경로 셋과 무관). 나중에 합성 경로가 실제로 필요해지면, 다이어그램은 tree/venn 만 실측하고 나머지는 jari 확장을 기다리고, 차트는 ChartBuilder 입력 세 값만 만들면 된다.

B-14 근거 semanticTaxonomy.gen.ts 와 같은 방식이다. 내용 41종은 capability-table.md, 상수 모양은 그 문서 5절에 있다.

B-15 근거 page 수준 relations 를 optional 로 열고 생성기는 채우지 않는다. 블록을 가리킬 수단이 필요하므로 컴포넌트 블록에 optional id 도 함께 연다. 근거와 실데이터는 G절에 있다.

B-17 근거 (2026-08-03 닫힘) 규약 5절 4번이 "못 그리는 relations 나 슬롯은 삭제하지 않고 텍스트로 흘려보낸다"까지만 정한다. 어디로 흘려보내는지가 없다. capability-table.md 3절의 '한 번 따라갈 때 잃는 것' 열도 서술이지 규칙이 아니다. Gauge → BigNumber 행은 "목표 대비 위치"를 잃는다고만 적고 measure.target 과 thresholds 가 어디로 가는지는 안 정한다. 지금은 fallback 할 때마다 구현이 알아서 버린다. A 로 정했다 (2026-08-03 정원석). 엣지마다 정의한다. 다만 손으로 쓰지 않고, 규칙 여덟(P1~P8)을 세운 뒤 각 엣지에 어느 규칙이 걸리는지를 2절 capability 에서 계산했다. 그래서 표가 capability 와 어긋날 수 없다. 엣지 수는 결정 당시 55였고 2026-08-04 에 75로 다시 계산했다 (B-26). 계산하다가 2절의 결함 하나가 드러났다. '선택 속성' 열이 내용 필드(period, speaker, avatar)와 표현 선택(orientation, apex)을 섞어 담고 있어서, 그대로 계산하면 Timeline → Steps 에서 시각(period)을 버리는 것으로 나왔다. 둘을 갈라 내용은 옮기고 표현만 버리도록 고쳤다.

B-16 근거 (2026-08-02 닫힘) compares 로 확정했고 규약 스키마에 들어갔다. 아래는 결정 전 기록이다. 지금 enum 이 intersects 하나뿐이라 yaml-examples.md 2.3 이 versus 대비쌍을 intersects 로 적는다. venn 후보가 이 relation 을 교집합으로 소비하면 대비 입력이 벤으로 잘못 그려진다. 판정 기준과 구성은 2026-08-02 에 확정했다(G절). intersects 유지, 비교 관계 1종 추가. 남은 것은 비교 관계의 이름 확정(compares 제안)과 예시 2.3 수정이다 (projection-model.md 7절).


C. 2단계 배포#

사실상 스킵, 8/18 폐기 (2026-08-07 확정, MOR-1774, 근거는 MOR-1939 코멘트). 2단계를 안 하고 4단계로 직행한다. 새 배포 날짜는 없다. 아래 항목 중 태깅·적재·BE 전달 인프라(C-1, C-2, C-6, C-7)는 4단계에서도 그대로 쓰이므로 남겨 두고, '2단계 배포 전' 이라는 마감 표현만 걷어낸다.

# 할 일 상태 담당
C-1 모듈 버전 2.0(semantic) BE 정책 협의 [x] 정원석 ↔ 박지우
C-2 semantic tag 데이터 흐름 다시 확인하고 Slack 공유 [x] 정원석
C-3 YAML 생성기 교체를 어느 시점, 어느 티켓으로 태울지 [x] 정원석
C-4 페이지 역할 태그를 검색 풀에서 어떻게 다룰지 [x] 정원석
C-5 맞는 컴포넌트가 없거나 대치가 실패하면 뭘 보여줄지 [x] 정원석
C-6 태깅 증량분 재태깅과 재전달 (9만 → 16만, +79%) [~] 김수현, 박지우
C-7 증량분 반영 후 README.md 3절 컴포넌트 분포 재측정 [ ] 정원석
C-8 규약을 Confluence 로 발행해 김수현님께 공유 [x] 정원석. 발행과 알림 완료 (2026-08-06) → 발행본 · 알림

C-2 근거 누가 무엇을 언제 어떻게 하는지를 공유한다. 경로는 류민 → 박지우 → 최운식이다. pipeline-flow.md 4절로 1차 그림을 확보했다. C-1·C-2 닫힘 (2026-09-05 정원석) — 낡은 전제로 무효화. 둘 다 "데이터 버전 2.0 시절의 page 단위 semantic tagging을 BE에 전달"하는 구조를 전제로 한 항목인데, 지금은 data version 2.5 추출까지 논의가 진행 중이고, page 단위 tagging은 이미 I.I.S.(component 단위 구조 인식)가 대체하는 방향으로 계획돼 있다. 옛 파이프라인 단계에 대한 협의·공유 항목이라 지금 붙잡을 이유가 없다.

C-3 결론 (2026-08-02) 4단계(MOR-1776)에 넣는다. MOR-1775는 실행하지 않음 상태를 유지하고 티켓을 새로 만들지 않는다. 감수하는 것 4단계에서 생성기 교체와 node 태그와 검색 변경이 한 번에 일어나므로, 품질이 떨어졌을 때 원인이 셋 중 무엇인지 가리기 어렵다(README.md 4절이 지적한 문제). 4단계 착수 때 단계별 계측 지점을 설계로 보완한다.

C-4 근거 방향은 2026-07-27 구두논의에서 정해졌다 (Slack). 페이지 역할 태그 넷(Opening, Agenda, SectionDivider, Ending)은 페이지의 Page Role 을 그대로 승계하고, 다른 semantic tag 와 달리 AI 가 아니라 logic 으로 태깅한다. 남은 판단이었던 것 이 태그를 가진 컴포넌트에 유효컴포넌트 조건을 추가할지, 같은 페이지에서 최상위 node 만 valid 로 볼지였다. 걱정의 실체는 "페이지 단위 라벨을 그대로 승계"하면 한 페이지 안 여러 노드가 일반 유효조건을 각각 통과해 같은 슬라이드의 조각들이 중복 후보로 검색 풀에 여러 개 들어갈 수 있다는 것이었다. 닫힘 (2026-09-05 정원석) — 전제 자체가 무효화. 오늘 Opening 판정을 "페이지 위치 승계"가 아니라 "제목+발표자/날짜/소속 같은 내용 조건"으로 바꾸면서(spec/capability-table.md 2.6절), PageNavigation 4종도 다른 41종과 똑같이 I.I.S.가 구조·내용을 보고 경계를 판단하는 방식으로 넘어갔다. "페이지 라벨을 그냥 승계"하는 로직 기반 태깅이 아니라 다른 카테고리처럼 성립 조건을 만족하는 노드를 정상적으로 하나씩 골라내므로, C-4가 걱정하던 "페이지당 여러 개 중복 후보" 문제는 애초에 로직 승계 방식에서만 생기던 것이고 지금은 발생 조건 자체가 사라졌다. 별도 유효컴포넌트 조건을 추가하지 않는다.

C-5 결론 (2026-08-02) 별도 정책을 두지 않는다. fallback 순서가 처리한다. 마지막 fallback 이 셋(List, LongFormTextLayout, HighlightedMessage)이고 셋 다 개수 조건만 보므로 언제나 하나는 성립한다. 빈 결과를 사용자에게 보여줄 일이 없다 (capability-table.md 3.0절). 저품질 결과에 대한 임계값 정책은 두지 않는다. 기술적 대치 실패는 에러 처리이지 규약 사안이 아니다.

C-6 근거 7/31 에 크게 진척됐다. MOR-1814 "유효컴포넌트 v3.5 증량분과 semantic tag 확장 전량 재전달(data version 2.2)" 이 킥오프 없이 계획대로 진행됐고, design_object_semantic_tags 에 version='2.2' 가 적재됐다. 남은 것은 semantic tag 수령분 검사와 S3 업로드다(7/31 기준 차주 월요일 목표).

C-7 근거 현재 README.md 3절은 2026-07-22 적재분(83,771건) 스냅샷이다. C-6 완료가 선행 조건이다.

C-8 근거 김수현님은 GitHub 계정이 없어 저장소 문서를 못 본다. 기술 논의가 정리되면 Confluence 로 발행해 알리기로 스레드에서 약속했다 (2026-08-02).


D. 스쿼드 차원에서 정할 것#

내 결정이 아닌 것들이다.

# 정할 것 상태 누가 언제
D-1 v7 태그를 후보 필터로 쓸지 순위 가산점으로 쓸지 [x] 이석 답이 이미 구현에 있다
D-2 List 편중을 그대로 두고 2단계를 낼 것인가 [x] 정원석, 이석, 김수현 2026-08-07 닫힘. 2단계를 안 하고 4단계로 직행하므로 결정할 것이 없다
D-3 3단계 스킵 여부를 대치 로직 관점에서 최종 확인 [x] 정원석 MOR-1775 를 2026-08-06 Cancelled 로 닫아 스킵이 확정됐다. 대치 로직 관점 확인은 MOR-1926 PoC 가 승계한다
D-4 structure → 검색 질의 변환기를 누가 만들지 [x] 이석 2026-09-05 닫힘 (정원석 배정). 이석님이 이미 소유한 "구조 검색"(comp-sign buildCompSearchData/rankCompCandidates, markdown 기반)의 연장선이라 이석님으로 배정
D-5 I.I.S. 계열을 RAT pod 재개까지 기다릴지 [x] 정원석 2026-08-22 닫힘. 기다리지 않는다. Visual Summary PoC 가 같은 출력을 대행 생산하며 4단계를 진행한다
D-6 용어를 semantic tag 에서 Component Visual Category 로 바꿀지 [x] 정원석 2026-09-05 닫힘. semantic tag → Semantic Component Category 로 확정 (제안된 Component Visual Category 가 아니라 이 이름)
D-7 이종중첩 컴포넌트(heterogeneous nested component)를 4단계 검색 풀에서 제외할지 [x] 이석, 강창석, 류민, 허재영, 정원석 2026-08-11 체크인에서 닫힘. 제외하기로 결정
D-8 이종중첩 템플릿을 검색 풀에 되돌릴지 (D-7 부분 철회) [ ] 이석, 김수현, 정원석 아래 근거. 복합 구조 순서의 N1 과 N2 가 이 결정에 걸려 있다
D-9 4단계를 단독 단계로 완주할지, 5단계로 직행할지 [x] 정원석, 허재영, 이석, 홍지혜 2026-09-08 닫힘. 직행한다. 4단계 실행 티켓은 cancel

D-1 근거 둘 다 아니고 태그 군(tier)이다. comp-sign v4.3.8 comp-match/semanticTags.ts 가 0군(사용자 지정), 1군(LLM 추정), 2군(그 외)으로 후보 풀을 채운다. 군 순서가 점수보다 절대 우선하며 태그 일치 가점은 없다. 남은 것은 이 방식을 규약에 반영하는 것이고, I-5 다.

D-2 근거 7/24 회의에서 "리스트 먼저 제품화"로 방향은 잡혔다. 질의 쪽 분포로 근거가 보강됐다. 색인은 List 81.1% 인데 질의는 31.96% 이고 ListingTable 과 ComparisonTable 수요가 두텁다 (semantic-taxonomy-v7.md 3절). 닫힘 (2026-08-07) MOR-1939 코멘트로 김수현님이 답했다. 2단계 배포 자체가 스킵되므로 List 편중을 안고 2단계를 낼지는 더 이상 결정할 문제가 아니다. List 편중 대응은 4단계 fallback map(MOR-1909)으로 넘어간다.

D-3 범위 리스트를 Q3 지원 4종에 포함할지도 함께 정한다.

D-4 근거 컴포넌트 색인(Search Signature v2)은 이미 분리돼 있어 대상이 아니다.

D-5 근거 MOR-1425, MOR-1448, MOR-1426, MOR-1427 은 RAT pod 쪽에서 2026년 6월 중순부터 정지 상태다. PoC 는 plan/node-tag-and-property-poc-blueprint.md D-2 대로 같은 출력을 src/lib/visual-summary/iis/ 에서 대행 생산한다. 닫힘 (2026-08-22) RAT pod 재개 시점을 4단계 착수 조건으로 두지 않는다. 출력 계약(동 blueprint 9절)을 우리가 먼저 정하고 PoC 에서 경계·속성·T-5/T-6/T-7 을 계속 진행한다. RAT pod 와의 정합은 교체 가능한 계약 제안으로만 맞춘다. 모듈 지도는 plan/poc-module-map.md 4절.

D-6 근거 README.md 2절 제안 상태다.

D-7 근거 8.7절이 정의하는 "중첩 컴포넌트"(entity.components[]) 자체는 자식 type 이 부모와 같든 다르든 이미 표현 가능하다. 이종중첩은 그중에서도 자식들의 type 이 서로 다른 경우(예: 리스트 안에 리스트와 차트가 동시에 포함)를 가리키며, 4단계 boundary 태깅/node 태그 PoC 준비 중 Slack 공유로 처음 논의됐다. 이런 케이스에 내용대치를 하려면 기존 조합 컴포넌트와는 별개의 대치 로직이 필요한데, 실제로는 대부분 조합 Fallback 만으로 해결될 것으로 예상돼 WSJF 가 낮다. 검색 풀에서 애초에 제외하는 편이 4단계 실행을 더 빠르게 만든다는 것이 2026-08-11 체크인 결론이다. 수량 파악은 별도로 허재영, 황현진이 맡았고 2026-08-13 에 끝났다 — 1,000개 표본에서 1.10%, 전체 약 2,014개(reference/2026-08-13-이종중첩-수량-산출/, MOR-2048). 제외로 잃는 양이 상한에서도 전체의 2% 미만이므로 이 결정을 되돌릴 근거는 나오지 않았다.

D-9 근거 (2026-09-08 닫힘) 결정은 #mordor스쿼드 한 스레드에서 났고, 같은 날 세 대화가 이어졌다. 15:21 허재영이 "4단계 바운더리까지만 깎아 두고 있었는데 5단계 프로퍼티 깎는 체계를 구축하겠다"고 밝혔고 (Slack), 16:54 정원석이 이석·홍지혜와의 구두 논의를 공지하며 컴포넌트 풀을 I.I.S. (Semantic Component - Structure and Property) 로 10만 건 추출한다고 적었다 (Slack). 17:46 허재영이 MOR-1776 하위 티켓 정리를 제안하자 17:51 정원석이 **"4단계 관련해서는 cancel 하고, 문서상으로 결정된 것들 최신화 하겠다"**고 답해 닫혔다 (Slack).

방식 노드 태그만 다는 단계를 따로 완주하지 않고, 경계와 속성을 한 추출 묶음으로 뽑아 노드 단위 검색·대치를 바로 붙인다. 실행은 이미 그 방향이었다 — K-10(경계 4,764건·속성 3,955건, 2026-09-06), K-9(노드 검색 엔진 배선, 2026-09-07). 후속은 MOR-2381(속성 추출 평가)과 MOR-2415(경계·속성 10만 건 추출)이다.

Jira (2026-09-14 조회) MOR-1924·MOR-1925·MOR-1926·MOR-1927·MOR-1929·MOR-1936 이 Cancelled 다. D-3 이 "대치 로직 관점의 최종 확인은 MOR-1926 PoC 가 승계한다"고 적어 뒀는데, 그 티켓이 실행 없이 Cancelled 됐다 — 이 결정은 해당 PoC 실측 없이 났다. 마일스톤 MOR-1776 은 아직 In Progress 이고 due 2026-09-30 이 그대로 남아 있다.

D-8 근거 (2026-08-28 열림) D-7 은 색인 쪽 정책이고, 2026-08-28 에 정한 복합 구조 순서는 질의 쪽 정책이다(spec/capability-table.md 3.6절). 둘이 만나는 지점에서 문제가 하나 생긴다. D-7 이 유지되는 동안 N1(질의 트리 전체를 담은 후보 찾기)과 N2(모양을 고정한 채 카테고리만 내리기)는 이종 구조 질의에서 언제나 후보가 0이다. 둘 다 이종중첩인 모양 그대로 후보를 찾는데, 그런 템플릿이 풀에서 빠져 있기 때문이다. 즉 이종 질의는 실질적으로 N3 에서 시작한다(design/content-binding-contract.md 3.7.5절).

D-7 의 결정 근거가 "대부분 조합 Fallback 만으로 해결될 것으로 예상"이었으므로, N3 부터 N5 의 결과 품질이 그 예상의 검증 자료다. 검증 전에 되돌리지 않는다. 되돌릴 후보 수는 실측돼 있다(전체 약 2,014개, 1,126~3,586). 판정 자료는 N3 이후 경로로 대치한 결과의 judge 점수이고, 그것을 재려면 I-11(오케스트레이션 코드)이 먼저다.


E. 7/24 회의에서 새로 받은 것#

# 할 일 상태 담당
E-1 시멘틱 컴포넌트 정의(규약)와 '문제 → 인과 분해 → 해결 방법 → 목표'의 링크를 문서로 정리해 공유 [ ] 정원석
E-2 폴백(대치 가능) 카테고리 테이블 정리, 유형 간 관계도로 스코어링 가능성 검토 [~] 정원석
E-3 유효 컴포넌트 응집성 게이트와 node 건을 별도 트랙으로 잡기 [ ] 정원석

E-2 근거 41종 fallback 순서 초안은 완성했다 (capability-table.md 3절). 남은 것은 스쿼드 검토이고 MOR-1909 (49종 fallback map 확정)의 할 일에 들어 있다. fallback 순서의 카테고리 선택 41건이 설계 판단이라 근거가 실측이 아니다(그 문서 8절).

E-2 갱신 (2026-08-13) 정원석이 v1 초안(김수현, 허재영 작성 시트)을 검토한 v2 를 plan/fallback-map-review-2026-08-13.md 로 올렸다 (PR #69). 49종 전량으로 확장했고 두 칸 상한 폐지, 종단 대표값 규칙, ComboChart 조건부 규칙을 담았다. 허재영, 김수현, 류민 확인 대기. E-2 갱신 (2026-08-17) 허재영 1차 피드백(Slack) 중 PictogramChart, BarChart, StackedBarChart normalize 를 plan 문서와 spec/capability-table.md(45종, 엣지 113개)에 반영했다. schema-v0.5.md 규약 5절 2번(두 칸 상한)도 동기화했다. 남은 것: List, SWOT, HighlightedMessage, LongFormTextLayout, ProfileCard, Funnel, TableOfContents 7행 원저자 확인, v7 우선순위(P1~P4) 문서 동기화, PageNavigation 4종 성립 조건(C-4 대기). 김수현, 류민 확인은 여전히 대기. E-2 갱신 (2026-08-27) PageNavigation 4종 성립 조건을 spec/capability-table.md 2.6절에 채웠다 — 정본 시트 값을 옮긴 plan/page-navigation-capability-draft.md 기준. C-4(검색 풀 정책)는 템플릿 공급 문제라 성립 조건의 선행 조건이 아니라고 판단해 대기를 풀었다(허재영 질의, Slack). Opening 맨 앞장 전용 여부도 같은 날 닫혔다 — 최신 정본 시트가 Opening/Ending 을 분리한다(허재영 확인, 초안 4.1절). 검증 코드 반영은 PR #2264 (measure 파생·평탄화 셈법 정합 포함).

E-2 는 B-1(등급 3단계), B-4(포기 가능 여부)와 사실상 한 덩어리다. 함께 푸는 편이 낫다.


E2. Placeholder Text Slot 효과 분석의 후속 (2026-09-20 신설)#

근거와 수치는 evaluation/placeholder-effect.md 가 SSoT 다. 여기에는 항목만 둔다(WRITING.md 5.3절).

# 할 일 상태 담당 왜
E2-1 새 경로에서 Placeholder Text Slot과 judge BAD 의 관계를 처치군 다섯 배로 다시 잰다 [ ] 정원석 지금 odds ratio 1.222 (95% CI 0.849~1.760) 로 방향만 보이고 확증이 안 된다 (보고서 7.4절)
E2-2 회차 27 의 rerank.json 에 채움 하한 같은 다른 문이 함께 걸렸는지 확인한다 [ ] 정원석 규칙의 몫 7%p 를 그 규칙 하나에 귀속해도 되는지가 여기 달렸다 (7.5절)
E2-3 노출 표본을 2주 전수로 늘려 컴포넌트 잔존까지 다시 잰다 [ ] 정원석 채택 이후 효과의 95% CI 가 0.572~1.440 이라 있다고도 없다고도 못 한다 (5.2절)
E2-4 대치 재현 실패 37% 의 원인을 가른다 [ ] 정원석 절 텍스트, 시그니처 재적재, MAX_MERGE_STEP = 1 셋 중 하나다. 남은 선택 편향의 크기가 여기서 정해진다 (6.1절)
E2-5 사용자 디자인 콘텐츠를 조회할 길이 있는지 디자인 서비스에 묻는다 [ ] 정원석 있으면 대치 재현 없이 "사용자가 임시 문구를 지웠는가"를 직접 본다 (부록 A.6)
E2-6 MOR-2504 가 닫힌 뒤 새 경로를 다시 잰다 [ ] 정원석 보고서의 새 경로 측정이 전부 "자리째 비는" 상태에서 나왔다. 임시 문구로 덮이면 7절의 judge 개선 7%p 가 사라질 수 있다 (보고서 8절 6번)
E2-7 5.4절 성향점수를 5.1절과 같은 기간(2026-09-01 ~ 09-14)으로 다시 뽑는다 [ ] 정원석 지금은 2026-08-01 ~ 09-01 이라 odds ratio 0.688 과 나란히 놓을 수 없다 (5.4절)

E2 결정 사항 (2026-09-20) judge 판정문에서 파생한 지표(증상 태그, residue)를 결론 근거로 인용하지 않는다. 검출률이 판정문 길이에 10배 끌리고, 처치가 결과와 같은 문장에서 나온다 (보고서 2.3절).


F. 마감 후로 미룬 것 (사후 검증)#

YAML 초안 확정(2026-08-01)을 뒤집을 근거가 있는지 나중에 확인하는 용도다. 다른 마감이 걸려 있지 않을 때 돌린다.

# 할 일 상태
F-1 YAML 과 KDL 과 JSON 의 생성 안정성 실측 [x]
F-2 생성기에 silent corruption 지표를 처음부터 내장 [ ]

F-1 닫힘 (2026-09-05 정원석). 포맷 비교실험은 불필요하다. YAML로 확정된 상태를 재검토할 근거가 없다. 전제 확인 완료 (2026-08-02, 정원석) LLM 이 YAML 을 직접 생성한다. YAML 채택 이유가 추론 속도(생성 토큰 절감)라서 JSON 생성 후 직렬화는 가치가 없다.

F-2 설계 YAML 을 골랐으므로 조건부로 생긴 부채다. "파싱은 됐는데 타입이 뒤바뀐" 건을 별도로 세야 한다. YAML 생성기 교체 작업(C-3)과 묶여야 한다.


G. 최근 해소된 것#

되살아나지 않게 기록해 둔다.

항목 결론 어디서
type 표기 문자열 하나로 적는다. 대분류.중분류 (2026-08-02 정원석 지시로 정정) 규약 2.3절
entity.id 생성물에서 뺀다. 파싱 뒤 배열 순서로 매긴다 (2026-08-02 정원석 지시로 정정) 규약 3.2절
생성 메커니즘 LLM 이 YAML 을 직접 생성한다 (2026-08-02 정원석) 규약 3절
최상위 형태 항상 components[] 배열이다 (2026-08-02) B-5
component.type 어휘와 표기 v7 단어장 41종으로 확장한다 (2026-08-02) B-11
계층과 장식 선언 필드를 두지 않는다 (2026-08-02, traits 제안 기각) → 2026-08-03 에 뒤집혔다. 이 줄은 해소된 것이 아니다. B-2, B-3 이 다시 열려 있다 B-2, B-3, design/decoration-model.md
dataset 모양 measure 와 matrix 2모양으로 재편한다 (2026-08-02) 규약 2절
relation 판정 기준 구조로 적을 수 없는 제3의 내용을 가진 관계만 어휘로 둔다 (2026-08-02) B-16
한 페이지 원소 수 상한 두지 않는다 (2026-08-02) B-15
원소 간 관계 자리만 예약한다 (2026-08-02) B-15
fallback 소유 생성이 카테고리를 정하고, 못 그리면 검색이 fallback 순서를 따라간다 (2026-08-02) capability-table.md 3절
layouts(page) 태깅을 BE 에 전달할지 전달하지 않는다. 앞으로도 전달 계획이 없다 (2026-08-01 정원석 확인) MOR-1755
판별자 키 이름과 다중 래퍼 이름 판별자를 type 하나로 통일한다 (2026-08-02) 규약 2절
중첩 컴포넌트 entity.components[] 로 스키마에 넣는다. 2026-08-02 에는 생성기가 당분간 안 채웠다. 2026-09-02 B-20: PoC 는 채운다 / 실서비스는 아직 안 채운다 규약 1절·8.7절
포맷 YAML 과 KDL YAML 로 초안 확정 (2026-08-01) README.md 2.1절
태그 적재 기능 7/21 프로덕션 배포 완료 (v1.28.104) MOR-1744
태그 전달 형식 태그가 하나든 여럿이든 문자열 배열 7/24 기준 합의 완료
S3 object path uuid overwrite 개발하지 않고 version bump 로 감 MOR-1717 Cancelled
배열 직렬화 깨짐(약 10,410건) 7/23 S3 재업로드로 정리 (박지우) 7/23
D-5 I.I.S. 를 RAT pod 재개까지 기다릴지 기다리지 않는다. PoC iis/ 가 대행 생산하며 4단계 진행 2026-08-22 plan/poc-module-map.md
D-9 4단계를 단독으로 완주할지 완주하지 않는다. 5단계로 직행한다 (정원석 확정, 4단계 실행 티켓 cancel) 2026-09-08 Slack

G.1 결론의 근거#

type 표기 처음에는 { major, minor } 두 필드였다. 근거 셋 중 둘이 실측과 어긋나 같은 날 정정했다. 결합 문자열 62값(대분류 21 + 결합 41) enum 은 두 필드 방식과 거부하는 입력 집합이 완전히 같고(어긋난 조합, 없는 대분류, 없는 중분류, 대분류만 적기 전부 동일), 토큰은 18종 기준 4.6% 적다. 그리고 저장 키가 이미 결합 문자열이라 두 필드로 두면 표기가 셋이 된다. semantic-taxonomy-v7.md 0절이 "두 형식을 섞으면 조회가 조용히 0건이 된다"는 사고 기록이 있다. 구분자는 점이다. 저장 키는 밑줄이고, 검색 모듈이 받는 즉시 점을 밑줄로 바꾼다.

entity.id LLM 이 식별자를 지을 이유가 없다. 읽는 곳은 relations[].of 하나인데 relations 는 어디서도 필수가 아니다. 비용은 둘이다. 토큰이 18종 기준 5.9% 늘고, 같은 내용에 다른 이름이 나와 F-1 재현성 지표가 같은 결과를 불일치로 센다. id 패턴이 ASCII 만 받는 것도 한국어 입력에서 실패 자리를 하나 늘린다. 그래서 생성물에서 빼고 파싱 직후 배열 순서로 매긴다. relations[].of 는 label 로 적고 후처리가 id 로 바꾼다.

생성 메커니즘 YAML 채택 이유가 추론 속도(생성 토큰 절감)라서, JSON 으로 생성한 뒤 YAML 로 직렬화하는 방식은 쓰지 않는다. 검증은 YAML 을 JSON 으로 변환해 수행한다. v0.2 원문의 structured output 서술을 대체한다.

최상위 형태 단일 형태는 제거하고, 컴포넌트가 하나면 길이 1 배열이다. 소비자 분기가 하나로 줄고 중첩 깊이 어긋남이 없어진다.

component.type 어휘와 표기 페이지 역할 4종과 지도 전용 4종은 제외한다. 표기는 { major, minor } 중첩 구조로 major 별 허용 minor 를 스키마가 강제하고, minor 는 optional 이다. 저장과 조회 키는 결합값 {major}_{minor} 다. semantic-taxonomy-v7.md 4절 격차표는 이 결정으로 소멸 예정이다.

계층과 장식 계층 존재는 children 과 entity.components[] 가, 계층 보존 요구는 component.type 이, 평탄화 허용 비용은 카테고리 fallback 순서가, 타이틀과 디스크립션 수준은 구조 파생이, 그림 장식은 색인 쪽 변형이 처리한다 (projection-model.md 6절). 이 진술은 2026-08-03 에 뒤집혔다. 계층은 nested 구조로 풀고, 장식은 Summary 가 축별 선호를 적는다. 위 문단에서 "타이틀과 디스크립션 수준은 구조 파생"과 "그림 장식은 색인 쪽 변형" 두 대목이 무효다. 나머지(계층 존재를 children 과 entity.components[] 가 담는다)는 그대로 유효하고, 오히려 B-3 뒤집힘의 근거가 됐다. 대체 문서는 design/decoration-model.md 다.

dataset 모양 component 별 허용 조건은 capability 테이블이 선언한다. 표 계열(ListingTable, ComparisonTable)도 matrix 로 담긴다. 현행 3종(gauge, stacked_bar, combo)은 컴포넌트명이라 '컴포넌트 결정을 데이터에 박지 않는다' 원칙 위반이었다.

relation 판정 기준 intersects 유지, 비교 관계는 compares 로 확정해 스키마에 넣었다(B-16). 전후 변화는 실사용 관측 후, 문제해결과 원인과 결합은 기각 (projection-model.md 7절).

한 페이지 원소 수 상한 실데이터에 다중 컴포넌트 페이지가 아직 없다. 질의 쪽은 block 하나에 중분류 라벨 하나를 붙이고(검증 시트 taxonomy_minor 단수), 색인 쪽 복수 태그는 83,771건 중 260건(0.3%)이며, yaml-examples.md 2절의 다중 예시 3종은 손으로 만든 것이다. 근거 없이 숫자를 박지 않는다. 과분할이 실제로 관측되면 그때 생성기 정책으로 건다.

원소 간 관계 page 수준 relations 를 optional 로 열고 생성기는 채우지 않는다. 블록을 가리킬 수단으로 컴포넌트 블록에 optional id 를 함께 연다. entity.components[] 와 같은 결정 구조다.

fallback 소유 생성기가 type 으로 카테고리를 정하고, 검색이 capability 로 그릴 수 있는지 확인한다. 못 그리면 fallback 순서를 따라간다(예: Gauge → BigNumber → List). alternatives 를 적어 두면 fallback 순서보다 먼저 본다. 처음 형태는 v0.3 4절의 "gauge → big_number → list" 다.

layouts 태깅 레이아웃 태깅은 존재하지만 RAT pod 내부용이고, 검색에는 컴포넌트 태깅만 쓴다. MOR-1755 본문 '확인 필요' 절이 근거다.

레이아웃 태깅도 있나? → 있긴한데 rat pod 에서 사용하는 데이터이며, 검색에는 쓰이지 않는다

원 Slack 근거도 달려 있다.

판별자 키 이름과 다중 래퍼 이름 hint → component, 그 안의 component → type, relation.kind → relation.type, dataset.kind → dataset.type, blocks[] → components[] 로 바꿨다. v0.4 는 component: { type, params, confidence } 와 structure 이고, 다중은 components[] 로 원소마다 { type, params, structure } 를 편다. 바꾼 이유는 셋이다. hint 는 내용이 아니라 세기를 가리켰다. component.params.apex 는 capability 테이블 pyramid 행의 성립 조건이라 참고값이 아닌데도 hint 안에 있었다. 그리고 type 은 RLSC LayoutContainer.type(형태)과 짝을 이루는 이름이라, 의미 축인 component.type 이 같은 키 이름을 쓰면 두 어휘의 대응이 드러난다. kind 와 type 이 섞여 있던 것은 type 으로 몰아 없앴다. 코드 비용은 0 이다. Visual Summary 는 packages/comp-sign 에 구현이 없다.

중첩 컴포넌트 표현 가능성만 열어 두고 켜는 시점은 생성기 정책과 capability 선언이 정한다. 단계가 늘어도 version 은 안 바뀐다. 원소끼리 중첩시키면 부모의 어느 슬롯인지 못 적어서 내용 대치가 위치를 잃는다. entity 가 곧 슬롯이라 여기 매단다. 소화 못 하는 컴포넌트는 규약 1절 '중첩은 평탄화된다'대로 부모 items 에 편다 (schema-v0.4.md 1절, 8-1절).

포맷 YAML 남은 것은 A-2 정렬과 F-1 사후 검증이다 (README.md 2.1절, schema-v0.4.md 6절).

3.5절 규칙 열 6행을 계산과 맞췄다 (2026-09-10, F5 전수 대조로 발견). NestedScope 에서 나가는 세 행(→List, →Pyramid, →Staircase)은 P3 이 남아 있었다 — B-34 로 children 요구가 빠지면서 옮길 내용이 없어졌는데 규칙 열이 안 따라왔다. PictogramChart→List, Calendar→Timeline, ComparisonTable→Versus 세 행은 P3 이 빠져 있었다 — P4(matrix 를 entities 로 편다)가 붙으면 열 내용이 items 로 가므로 P3 이 함께 붙고, 같은 문서의 다른 P4 엣지들은 "P1 P2 P3 P4" 로 적는다

P8 폐기(B-27)를 코드가 따라왔다 (2026-09-10). 3.4절이 2026-09-05 에 P8(개수 상한 초과분을 마지막 항목 items 로 접는다)을 폐기하고 "접지 않고 성립 조건 미충족으로 후보에서 탈락시킨다" 로 정했는데(정원석, MOR-1934), aippt projection.ts 는 그 뒤로도 P8 을 계산하고 projectStructure 가 실제로 접고 있었다. fallback.ts 가 projection 을 성립 판정보다 먼저 돌려 접힌 구조가 개수 조건을 통과했으므로 폐기 전과 같은 동작이었다. aippt PR #2399 에서 규칙 계산과 적용, acceptedWithP8 플래그, query.ts 필터, PoC 화면 칩을 전부 걷어냈다. 동작 확인: Steps 17개가 상한 15인 Timeline 에서 탈락하고 상한 없는 List 까지 내려가며 17개가 그대로 남는다 — 3.2절이 B-27 과 함께 List 안전망 세 행을 더한 이유가 이것이다

표 도착지 projection 규칙을 3.4절에 명문화했다 (2026-09-10 허재영 판단, 정원석 확인 대기). 3.4절은 BarChart→ComparisonTable·ListingTable 엣지를 "P1 P2, 옮길 것이 없다" 로 두어 matrix 가 Column.role 째 그대로 넘어간다. B-37 이 role 별 셀 타입을 카테고리와 무관하게 검사하므로, 값 열에 문자열이 섞인 차트는 표 도착지에서도 같은 사유로 탈락해 List 까지 내려갔다. 3.4절에 P2 보충을 넣어 표 도착지에서는 Column.role·axis·type 을 버리게 했다 — 2.5.1절이 이 셋을 순수 차트 어휘로 둔 것이 근거다. 코드가 먼저 그렇게 돌고 있었고 규약에 명문이 없던 것을 이번에 채웠다. 규칙 라벨은 안 바뀌고 손실 목록에 한 줄이 붙는다

3.5절 두 행의 규칙 열에 P5 가 빠져 있었다 (2026-09-10 발견). BigNumber→List 와 Gauge→List 가 "P1 P2 P3" 으로 적혀 있었는데, 출발지가 measure 를 읽고 List 는 못 읽으므로 P5(value 와 unit 을 entity 로 옮긴다)가 붙어야 한다. 같은 문서의 →HighlightedMessage 두 행은 P5 를 적고 있다. B-27 로 List 안전망 행을 더할 때 옆 행의 규칙 열을 덜 옮긴 것으로 보인다. 코드가 맞고 문서를 고쳤다

2.6절 내용 조건을 배치 가능성으로 좁혔다 (2026-09-10 허재영 판단, 정원석 확인 대기). 1절이 성립 조건을 "이게 없으면 그 종류로 배치가 불가능한 것" 으로 정의한다. 그 기준으로 42행의 "제목 + 발표자·날짜·소속 중 하나 이상" 은 조건이 아니다 — 제목만 있는 표지도 표지 레이아웃에 배치된다. 42행 내용 조건을 없애고 발표자·날짜·소속을 선택 내용으로 내렸다. 43행에서도 "번호와 제목을 나열한다" 를 빼고 "흐름·단계가 없다" 만 남겼다. 44·45행의 설명 문장도 성립 판정에 넣지 않는다(기계가 보는 것은 "본문 콘텐츠가 없다" 뿐이다). 그 결과 Opening·Ending·SectionDivider 는 구조로 구분되지 않는다 — 셋 다 entity 하나에 본문이 없다. 어느 종인지는 질의가 적은 type 과 I.I.S. 태깅이 정한다. 2026-09-05 정원석 확정("위치가 아니라 내용으로 판정한다")의 위치 조항은 그대로다

B-37 의 날짜 조항을 뺐다 — start·end·date 열은 형식을 강제하지 않는다 (2026-09-10 허재영 판단, 정원석 확인 대기). 종전 문구는 "ISO 문자열 또는 timestamp 숫자만, 임의 문자열 금지" 였다. 그 규칙으로는 달력과 간트가 실사용 입력에서 거의 성립하지 않는다 — 생성기 골든셋 v1 의 Calendar 10행과 GanttChart 10행이 전량 탈락했고, 그중 여덟은 입력에 달이 없어("3일 소아과 당직") ISO 로 옮기려면 없는 값을 지어내야 해서 골든셋을 고쳐도 풀리지 않았다. 날짜 셀은 계산 대상이 아니라 표시와 대치 대상이다 — "3월 4일" 을 그대로 담고, 내용 대치가 그 문자열을 검색해 바꾼다. 숫자 계약이 남는 곳은 value·x·y·size 뿐이다. 고친 곳은 spec/schema-v0.5.md 2.1절 주석이다

2.6절 Opening 산문을 개수에 맞췄다 (2026-09-10 허재영 판단, 정원석 확인 대기). 산문 괄호가 "제목·발표자·날짜가 각각 entity" 라고 적어 42행 개수 "정확히 1" 과 어긋났다. 셋을 각각 entity 로 두면 개수가 셋이 된다. 제목이 label 이고 발표자·날짜·소속은 그 entity 의 items 로 정정했다. 개수 열과 시트의 "단일 슬라이드 구조" 정의를 살린 표기이고, 구현과 시트본 골든셋 v1.5 의 Opening 세 행이 이미 이 모양이다. 42행의 내용 조건("제목이 있고 발표자·날짜·소속 중 하나 이상")은 2026-09-05 정원석 확정이라 그대로 둔다

2.5절 column.role 배정은 읽기 규칙이지 성립 조건이 아니다 (2026-09-09 허재영 확인). "x·y·size 는 산점도와 버블, start·end 는 간트, date 는 달력" 문장을 보고 선차트에 x 를 쓴 산출물을 위반으로 볼 뻔했으나, capability-table 1절 "여기 적히지 않은 것은 전부 무시한다" 와 2절 표(성립 조건에 role 없음)가 이미 답을 준다. 실제 위반은 B-37(셀 타입)이 잡는다 — x 열에 "2023년" 문자열이 들어간 것이 잘못이지 x 를 쓴 것이 잘못이 아니다. 골든셋 검수도 role 어휘 불일치는 인코딩 선택으로 다룬다


H. 문서 정합 백로그#

이 문서를 만들면서 드러난 repo 내부 불일치다.

  • README.md 4절과 6절의 'layouts 태깅을 BE 에 전달할지 확정'이 4절 '확정된 것'과 모순이었다 → G 표로 정정 (2026-08-01)

  • README.md 3절 컴포넌트 분포와 semantic-taxonomy-v7.md 3절 질의 분포가 서로 다른 것을 센다는 것을 3절 본문에도 명시할지 → 3절 머리의 '축 구분에 주의한다' 안내 상자가 이미 이 역할을 한다. 추가 명시 없이 닫음 (2026-08-02)

  • schema-v0.4.md 11절 체크리스트를 이 문서로 완전히 이관 → 이관 완료. 11절은 포인터와 맥락 위치 안내만 남김 (2026-08-02)

  • content-binding-contract.md 초판이 comp-sign 구현을 확인하지 않고 구간 ②③을 "빈칸"으로 적었다 → 실물 코드 확인 후 정정 (2026-08-01)

  • schema-v0.4.md 10절이 2026-07-21 이전 상태를 근거로 쓰여 실물과 어긋난다 → 10절을 smart-diagram-data-model.md 포인터와 유효한 대응표만 남기고 줄였다. 낡은 진술 셋(TreeItem 분리, placeholder, v1 저장 contract)은 본문에서 제거 (2026-08-02)

  • yaml-examples.md 예시가 component.type enum 밖 값을 써서 스키마 검증을 통과하지 못하던 문제 → 해소 (2026-08-02). 어휘가 41종으로 늘었고, 예시를 v0.5 표기로 다시 썼다. 본문 예시 18종 전량이 규약 스키마 검증을 통과하는 것을 확인했다

  • 문체 통일 (WRITING.md 2.8) 2026-08-02 에 이 폴더와 root 문서를 해라체로 통일했다. 저장소의 기존 문서가 이미 해라체라 그쪽에 맞췄다. 인용 원문은 코드 블록으로 감싸 보존한다

  • em dash 와 본문 가운뎃점 정리 (3.2, 3.3) 2026-08-02 에 이 폴더를 정리했다. 인용 원문(모듈 출력, 외부 문서 제목, Slack 과 코드 주석 인용)과 보존 문서(archive/), 허용 예외(표 셀, 메타 줄, 링크 나열의 가운뎃점)는 그대로 두었다. 저장소 나머지는 미정리다

  • [~] markdown-summary-samples.md 의 표 20건 줄바꿈 (2026-08-02). 표를 담은 20건이 한 줄로 눌려 있었고 2건은 ** 표기가 잘려 있었다. 규칙 복원으로 20건 전량을 되돌렸고 열 수 검사를 통과했다(그 문서 0.1절). 남은 것은 원본 대조다. 시트(Drive 1wRBwx5...)에서 20건을 다시 뽑아 글자까지 일치하는지 확인해야 한다

  • 이 저장소 나머지를 WRITING.md 기준으로 훑어 비유와 과장 표현 정리 (visual-summary/ 는 2026-08-02 에 전면 정리 완료)

  • 계약 → contract 표기 통일 (3.4) visual-summary/ 는 2026-08-02 에 맞췄다. 저장소 나머지에 '저장 계약', '전달 계약', '입출력 계약' 등이 남아 있다

  • SSOT → SSoT 표기 통일 (5.1) 이 폴더와 root 문서는 2026-08-02 에 맞췄다. 저장소 나머지에 옛 표기가 남아 있다

  • B-14 의 자리 진술이 실물과 다르다 (2026-09-09 허재영 진술, 정원석 확인 대기). B-14(2026-08-02)는 "TS 상수를 packages/comp-sign 에 둔다" 고 적었는데, miricanvas-iui 의 packages/comp-sign 에는 capability 코드가 없고 상수(capability.ts)와 검증기(schema.ts, establish.ts, capabilityCheck.ts, validate.ts, vocabValidate.ts)는 전부 aippt-prisonbreak src/lib/visual-summary/ 에 있다. 상수와 검증기가 있는 곳은 aippt-prisonbreak 이고, iui 는 그것을 보고 따로 구현한다 로 B-14 문구를 정정한다. I-17(json 필드 확장)과 B-37 구현의 대상 repo 도 aippt-prisonbreak 다

  • SWOT order 의 뜻이 표와 I.I.S. 속성 어휘에서 다르다 (2026-09-09). 2절 표는 order 를 "강점, 약점, 기회, 위협 순"(사분면 배치 순서, B-34 결정문도 같은 뜻)으로 두는데 aippt PROPERTY_PARAM_SPEC 은 boolean(그 순서인가)으로 본다. I.I.S. 의 enum 도 표에 없는 값(direction: backward, descending, rotation: counterclockwise)을 갖는다. 표가 값 어휘를 열거하고 I.I.S. 가 따라가야 한다 (I-17 ③)

  • archive/projection-model.md 135행의 ./pipeline-flow.md 링크가 깨져 있다. 폴더를 장르로 재구성할 때 design/ 으로 옮겨진 파일인데 경로를 안 고쳤다. 보존 전용 문서라 본문은 안 건드리고 링크만 고칠지 판단이 필요하다 (2026-08-03 발견)


I. 내용 대치 contract#

고른 다음에 무엇을 어디에 어떻게 넣는가. 맥락과 근거는 content-binding-contract.md 에 있다. 규약이 '어떤 컴포넌트를 고를까'까지만 정의하고 그 다음 구간이 비어 있어서 생긴 묶음이다.

왜 이 묶음이 중요한가 2026-07-26 BAD 원인 분석에서 BAD 의 85% 가 렌더링이 아니라 컴포넌트와 내용이 안 맞는 문제로 나왔다. judge 채점 16,819행 집계에 BAD 200건 직접 분류를 더한 결과다 (Slack). 세부 원인은 텍스트 용량 초과, 매핑과 위계 오류, 슬롯 수 불일치, 대치 실패다. 넷 다 이 절이 다루는 구간이다.

전제 구현은 이미 상당 부분 있다. 이석님의 miridih/miricanvas-iui packages/comp-sign v4.3.8 가 시그니처 추출, 검색, 랭킹, 내용 대치 네 단계와 semantic tag 소비를 모두 구현하고 있다. 비어 있는 것은 규약이다.

PoC 대치 경로 우선순위 (2026-08-22) 아래 표 순서가 실행 우선순위다. I-2(contract 정렬)는 이석·류민 협의가 필요해 병행하되, 공정한 3-way 비교는 I-8( pool bake )이 먼저다.

순 # 한 줄
1 I-8 평가 pool 고정 + comp-sign / I.I.S. 경계·속성 사전 적재
2 I-7 T-8 cross-type property projection 규칙 초안
3 I-9 T-8 property 대치 PoC (5단계 경로, service.ts / UI 연동)
4 I-2 노드 분류·장식·comp-sign 변형 정렬
5 I-5, I-6 comp-sign 규약 반영, 랭킹 A/B
6 I-1, I-3, I-4 속성 목록, 폴백 층, comp_signs 경로
# 작업 단위 상태 누구와
I-1 semantic category 별 필요 속성 목록 만들기 → MOR-1932 [~] 정원석(초안) → 류민, 신영빈
I-2 세 가지를 하나로 정렬 → MOR-1933 [ ] 류민, 신영빈, 이석
I-3 폴백 ①카테고리, ②등급 층을 규약에 정의 (잔여는 MOR-1935 와 한 덩어리) [~] 정원석 → 이석, BE
I-4 구간 ①의 속성을 comp_signs 로 넘기는 컬럼과 경로 정의 → MOR-1935 [ ] 정원석, 이석, 이강윤
I-5 comp-sign 실물을 capability-table.md 에 반영 (MOR-1925 와 순서 조율) [ ] 정원석
I-6 rankCompCandidates 위계 가중치 A/B → MOR-1936 [ ] 정원석, 이석
I-7 cross-type property projection (T-8) 규칙 초안. structure P1~P8과 구분. StackedBarChart 쓰기 규칙(I-15)을 여기 포함한다 [ ] 정원석, blueprint T-8
I-8 PoC 평가 pool 고정 및 사전 적재: 같은 DO subset에 comp-sign signature(T-2) + I.I.S. 경계(T-4) + 속성(T-7). pool 정의·스크립트·적재 상태 문서화 [ ] 정원석
I-9 T-8 property 기반 대치 PoC: I-8·I-7 선행. 5단계 경로를 service.ts / 대치 비교 UI에 3번째 열로 연동 [ ] 정원석
I-10 속성 골든셋 사람 확정 UI: 경계 골든셋과 같은 모양으로 properties/preannotation_properties 컬럼을 더하고 편집기에 패널을 얹는다 → plan/iis-property-annotation-ui-blueprint.md [ ] 허재영, 황현진 (2026-09-05 정원석 배정)
I-11 복합 구조 fallback 오케스트레이션: 질의가 중첩·병치인 것을 인식하고 N1N5(ExactMatch: Structure&Semantic / ExactStructure-SemanticFallback / Root-Peeling / Re-Peeling / Content Flattening) 를 돌리며 조각마다 검색을 호출하고 결과를 다시 조립하는 코드. 설계 메모(2026-09-05, B-32 부수): Re-Peeling으로 갈라진 조각 중 원래 부모가 같은 것들은 출처를 유지해뒀다가, 배치 시점에 크기를 고려해 화면/페이지당 23개씩 클러스터로 묶어 형제 인접성을 부분 복원하는 안 [ ] 정원석
I-12 질의 쪽 중첩을 단일 type 으로 평탄화한 뒤 fallback 할지, 구조를 유지한 채 노드 태그로 대치 범위를 자를지 [x] 2026-08-28 닫힘. 유지한다
I-14 type 필수 + 버전 규칙 코드 반영(2026-08-29): zod componentSchema.type 을 필수로, version 을 z.literal('0.5') 에서 MAJOR.MINOR 만 보는 패턴으로(0.1절), SCHEMA_VERSION 을 '0.5.1' 로, 생성기 프롬프트에 type 필수와 "못 고르면 종단 셋 중 하나" 지시, query.ts 의 "type 없을 때 확정 카테고리를 1군에" 구제 분기 정리, fallback.ts 의 type 없음 경로. 대분류 21값을 뺀 어휘 좁힘(taxonomy.ts 의 SEMANTIC_CATEGORIES 62 → 41, fallback.ts 의 major-expansion 경로와 isMajor 분기, query.ts 의 major-expansion 필터)도 함께. 문서 쪽 JSON Schema 파일은 이미 반영했다(required 에 type, enum 41종, version 패턴) [ ] 정원석
I-13 복잡도 레벨 토글: 생성기 프롬프트의 위상 지시를 레벨 단위 블록으로 쪼개고(병치=L2, 포함=L3), 프롬프트 조립이 복잡도 레벨을 인자로 받게 하고, 산출물의 실제 레벨(entity.components[] 사용 여부)이 선언한 레벨과 어긋나지 않는지 검사한다. K절(week 27 PoC 목표)의 K-4·K-5가 이 항목을 직접 필요로 한다 [ ] 정원석
I-15 StackedBarChart 대치 시 엔진 isStacked 세팅 (B-12, 2026-09-04 확정). RLSC 추출도 질의 스키마(params)도 안 건드린다 — type: ProportionComposition.StackedBarChart 선택 자체가 이미 stacked 신호이므로 별도 필드가 불필요하다. 구현할 것: 대치 로직이 최종 선택된 type 이 StackedBarChart 일 때, 대상 컴포넌트(BarChart 계열 후보에서 옮겨온 것 포함)의 엔진 노드 속성에 isStacked: true 를 쓴다. 엔진 쪽 필드는 packages/engine/node-props-schema/src/nodes/ChartPropsSchema.ts 의 ChartOptions.isStacked(XML 원본은 ChartItemXmlSchema.ts 의 showStacked)다. type 이 BarChart/LineAreaChart 면 isStacked 를 켜지 않는다(또는 명시적으로 false). T-8 cross-type property projection(I-7) 규칙에 이 쓰기 하나를 포함시키면 된다 — 새 규칙 카테고리를 만들 필요 없이 기존 P-계열 규칙 옆에 추가 [ ] 정원석
I-16 대치 불가 카테고리를 Visual Summary 생성기 어휘에서 제외 (B-36, 2026-09-04 확정 · 2026-09-11 목록 9종). 근거는 capability-table.md 10절. 대응 엔진이 아예 없는 여섯(RelationalComparison_Quadrant, TimelineSchedule_GanttChart, TimelineSchedule_Calendar, ProportionComposition_TreeMap, ProportionComposition_PictogramChart, RelationalComparison_BubbleChart)은 계속 뺀다. 엔진은 있지만 연결 코드가 아직 없는 하나(Hierarchy_HierarchicalTree)는 B-13 완료 시 다시 넣는다. 첫 출시 공급 제외 둘(KPIKeyMetric_Gauge, AttributeComparison_RadarChart, 2026-09-11 정원석)은 상태는 「Native 필요, 있음」이고 스마트 다이어그램 대응이 가능해지면 복귀한다. Conversion_Funnel 은 제외 목록에 없다. capability 스키마에 generatable: boolean 을 심고 이 아홉은 false — spec/schema/capability.json 에 반영. 생성기 프롬프트·검증은 이 flag 를 보고 type 어휘에서 제외한다(iui #1023 V2, 36종). I.I.S. 쪽은 이 flag 를 참조하지 않고 45종 전량. 런타임 상수(capability.ts 등)에 같은 필드를 심는 일은 코드 쪽 잔여 [~] 정원석, 허재영
I-17 capability.json 에 표(2절)의 성립 조건을 전부 담을 필드를 만든다 (2026-09-09 허재영 제안, agent 작성 초안). 설계와 기각한 대안은 plans/capability-table-code-sync.md 에 있다. 진행(2026-09-09): ①②③⑤ 를 json 에 넣었고 ④ 는 답을 정해 닫았다. aippt PR #2399 가 같은 값을 상수와 검사(B-37 셀 타입, 내용 조건 13종, 개수 단위, params 열거값)로 옮긴다. 종전 json 은 개수 min/max·dataset·reads·fallback 만 담아, 표에 산문으로 있던 다섯 가지가 json 에 없었고 코드가 표를 손으로 옮겨 적고 있었다 — ① 개수 단위(항목·행·조각·축·계열·열). 정의 초안(json countBasisVocabulary, 정원석 확인 대기): 항목 = entities 수 · 행/조각/축 = matrix 행 수 · 계열 = 첫 열(범주 축)을 뺀 나머지 열 수 · 열 = 첫 열을 뺀 열 수(ComparisonTable). 계열을 role 로 거르지 않는다 — 초안은 role: value 인 열만 셌는데, 그러면 선차트가 연도 열에 x, 계열에 y 를 적었을 때 계열 0 으로 성립에 실패한다. role 배정을 성립 조건으로 만드는 것이라 G절이 닫은 방향과 어긋나고, 생성기 프롬프트 예시(role: x, role: y)와 저장소 골든셋 Trend 계열 20행이 전부 탈락한다. 실제 위반은 B-37 이 잡는다. 표가 단위를 안 적고 "n 이상" 으로만 적은 행은 2절 머리말("n 은 entities 개수다")대로 entities 로 센다 — 첫 판이 Quadrant·BubbleChart·Calendar·TreeMap·PictogramChart 다섯을 행으로 적었던 것을 2026-09-09 에 되돌렸다. 31행 "축 2 이상" 은 count 가 단위를 하나만 담아 contentConditions.rowsMin2 로 갔다 ② 내용 조건 12종(Quadrant x·y, Bubble x·y·크기, Gantt 시작·끝, Calendar 날짜·일정, HierarchicalTree children, BigNumber value, Gauge measure.value, ComboChart type/axis, ListingTable·ComparisonTable entities+columns 대안 경로, PageNavigation 3종 — Opening 은 2026-09-10 에 빠졌다). 어휘와 뜻은 contentConditionsVocabulary 에 두고, aippt PR #2399 가 13종 전량을 검사한다 ③ params 허용 값과 기본값 26건. 표가 괄호 안에 백틱으로 적은 값만 기본값으로 옮긴다 — 설명 문장인 header(첫 행이 머리글)와 SWOT order(강점, 약점, 기회, 위협 순)는 빈 선언으로 뒀다. 남은 것: 열거값(values)을 가진 키가 NestedScope direction 하나뿐이라 rotation: 원형 같은 값이 아직 통과한다. StackedBar normalize 는 표가 백틱으로 적은 것이 기본값 false 하나뿐이라 열거를 빼고 기본값만 뒀다 — "true 는 렌더러 미지원" 을 검사로 만들려면 표가 열거로 적어야 하고, 그 검사를 I.I.S. 인식 쪽에도 적용할지는 개수 상한 선례(2절 머리말, B-27 병행 확정: 상한은 생성·질의 쪽 제약이지 인식 쪽 제약이 아니다)를 따라 정해야 한다. 나머지 키의 값 어휘는 표가 먼저 열거해야 한다(H절 SWOT order 항목과 한 건이다) ④ 선택 내용 선언 원칙 — 닫았다 (2026-09-09 허재영 제안, 정원석 확인 대기): label 과 items 는 공통 필드로 보고 reads.content 에 선언하지 않는다. 근거는 둘이다 — 45종 어느 행도 이 둘을 reads.content 에 적지 않았고, 3.5절이 PageNavigation 엣지 여덟을 전부 "옮길 것이 없다"(P1 P2)로 둔다. 선언하면 projection 이 P3(내용을 옮긴다)로 계산돼 3.5절과 어긋난다. aippt PR #2393 이 "범위 밖 불일치 2번" 으로 남긴 PageNavigation 선택 내용 공백이 이 답으로 닫힌다 ⑤ role 규칙(B-37 셀 타입. 2.5절의 role 배정 자체는 성립 조건이 아니다 — 1절 "적히지 않은 것은 무시", G절 참고). 표와 json 과 코드가 같은지는 aippt capability.test.ts 의 문서 사본 대조가 45종 전 필드로 본다 (2026-09-09 확장. 개수·단위·내용 조건·속성 선언·dataset·reads·fallback 과 entities 호환 필드까지). B-14 는 그대로다 — TS 상수가 SSoT 이고 이 json 은 검증용 사본이다. 남은 것: 그 대조가 CI 에서도 돌려면 aippt CI 가 private 서브모듈(docs/)을 체크아웃할 토큰이 필요하다. 지금은 로컬에서 pnpm check:docs-sync 로 돌고 CI 에서는 건너뛴다 — 인프라 결정으로 남긴다. B-35(version 패턴)와 B-36(generatable)은 이 작업과 한 묶음이다. 커널 41종과 사전 45종의 분리는 capability.test.ts 가 "문서 45 / 커널 41" 로 잠근 의도적 설계라 이 항목의 범위가 아니다 [~] 정원석(스키마) → aippt 담당

2026-08-22 fallback 이후 대치 계층 SSOT를 content-binding-contract.md 3.4~3.6절에 추가했다. PoC 대치 경로 비교 탭(/visual-summary)으로 RLSC vs comp-sign을 컴포넌트별로 나란히 볼 수 있다 (poc-guide.md 4.1절). 규약 RLSC는 실서비스·내용시각화 BE 경로가 아님을 3.4·3.6절에 명시했다.

I-1 Steps 의 sequence, rank, attach, span(MOR-1856)을 템플릿 삼아 P0 카테고리부터 채운다. 지금 열거하지 않으면 카테고리마다 다르게 설계된다. 이미 착수했다. 2026-07-27 standup 에 "semantic category 별 속성 정의 schema 파일을 repo schema 폴더에 만들어 초안 공유를 준비. Visual Summary YAML 생성 예시(v0.4)를 기준으로 category 마다 속성과 example 을 하나씩 붙이는 형태"로 적혀 있다 (Slack).

I-2 정렬할 셋은 노드 분류(CORE/DESC/DECO/OUT), 장식 등급(N, N+α, N+β), comp-sign 변형 3종(원본, Subtitle strip, Hl strip)이다. 세 곳이 같은 문제를 서로 다른 기준으로 풀고 있다. 맞추지 않으면 컴포넌트당 변형이 최대 9벌이 된다. B-1~B-4 가 이것에 종속된다. 2026-08-03 에 절반이 풀렸다. design/decoration-model.md 가 등급과 노드 분류를 한 축 집합으로 합쳤다. 장식 등급은 독립 개념이 아니라 노드 분류의 파생물이라는 것이 근거다(DESC 가 설명 축, DECO 가 장식 축). 나머지 절반인 B-19(comp-sign 변형 3종 처리 시점)도 2026-09-05에 닫혔다 — comp-sign은 색인 시점에 3벌을 미리 계산해 저장하는 현행 방식을 그대로 유지한다(대치 시점 처리로 옮기지 않는다). 장식 축은 애초에 색인을 곱하지 않으므로(4.5절, 검색 순위 신호일 뿐 노드를 지우거나 넣지 않는다) 9벌 곱셈 우려는 실재하지 않았다 — 컴포넌트당 색인은 comp-sign 변형 3벌에서 그친다.

I-3 ① 카테고리 층은 2026-08-02 에 정의됐다 (capability-table.md 3절 fallback 순서). 남은 것은 ② 등급 층이고 B-1, B-4, E-2 와 한 덩어리다. ② 등급 층의 설계는 2026-08-03 에 정해졌다 (design/decoration-model.md 4절). DECO 계열 축 둘에 값 셋이고 선언 층이 셋이다. 남은 것은 41종에 카테고리 기본값을 채우는 것과 색인이 role 표식을 받는 경로다. capability 테이블에 장식 축 열이 아직 없다(B-18, B-21, B-22).

I-4 text_map(용량), text_node_roles(role), semantic_tags(태그)는 있는데 sequence, rank, 노드 분류를 담는 자리가 없다. semantic_tags denormalize 방식이 선례다.

I-5 반영할 것은 태그 군 3단계, 채점 항목 20여 종, 대치 box 보정 옵션 4종이다. 규약이 구현보다 뒤에 있다. D-1 의 답도 여기서 확정된다.

I-6 BAD 원인 분석이 지목한 '가장 싸게 검증할 수 있는 다음 수'다 (2026-07-26). B-3 계층 결정의 실측 근거가 된다.

I-7 structure projection(P1~P8)과 구분한다. 이종 type 간 sequence, orientation 등을 슬롯에 맞추는 규칙이 없으면 5단계 대치 PoC(I-9)를 구현할 수 없다.

I-8 평가 pool은 design/content-binding-contract.md 3.6절과 plan/poc-module-map.md 3절이 SSOT다. indexPool 술어와 동일한 DO 집합에서 고정 subset을 정하고, 그 subset에 comp-sign(comp_signatures 또는 PoC 메모리 빌드), I.I.S. 경계, I.I.S. 속성을 bake 한다. 전량 17만 건은 dev/stg 판정(T-2 full) 때 별도로 다룬다.

I-9 5단계 property 대치는 T-7 추출만으로는 부족하다. 이종 type 사이에서 sequence, orientation 같은 속성을 서로 다른 슬롯에 맞추는 규칙인 **T-8(cross-type property projection, plan/node-tag-and-property-poc-blueprint.md 6절)**을 I-7이 정하고, pool bake(I-8) 후 service.ts와 대치 비교 UI에 세 번째 경로를 연동한다. 지금 UI는 PoC RLSC vs comp-sign 2-way만 가능하다.

I-11 (2026-08-28 신설) 순서는 정해졌고 코드가 없다(spec/capability-table.md 3.6절). 지금 있는 것은 조각 하나를 찾는 검색(T-5 노드 태그 검색)뿐이고, 질의가 복합인 것을 인식해 N-R2 로 형제를 묶고 조각마다 검색을 호출하고 N-R1 로 벗긴 부모의 내용을 옮기고 결과를 최상위 components[] 로 다시 조립하는 층이 없다. D-8 판정 자료가 이 코드에 걸려 있다. N3 이후 경로의 결과 품질을 재야 D-7 을 되돌릴지 정할 수 있다.

I-13 (2026-08-29 신설) 복잡도 레벨(L1/L2/L3, spec/schema-v0.5.md 8.8절)을 정책으로 통제하려면 레벨을 올리는 지시가 프롬프트에서 통째로 떼어지고 붙어야 한다. 지금은 절반만 되어 있다 — 위상 지시가 generator/prompts/system.md 의 ## 이종 컴포넌트 한 절에 뭉쳐 있어 물리적으로는 한 덩어리지만, ## 컴포넌트 블록 절에 "entity.components 는 포함 관계를 적을 때만 쓴다" 한 줄이 밖에 남아 있고, 포함(L3)과 병치(L2)가 한 절에 섞여 있어 레벨 경계로 갈리지 않으며, fillPrompt 가 {{TOKEN}} 치환만 해서 블록을 넣고 뺄 수단이 없다. 검사 재료는 이미 있다 — 산출물의 실제 레벨은 entity.components[] 사용 여부 하나로 판정되므로 선언 레벨과의 대조를 validate.ts 계측에 붙일 수 있다. 이것이 없으면 프롬프트 판본마다 실제 레벨이 달라지고, L2 로 내린 줄 알았는데 중첩이 나와 I-11 이 없는 상태에서 조용히 실패한다. 요구사항은 규약 8.9절.

I-12 (2026-08-28 닫힘) 유지하는 쪽으로 정했다. 구조를 그대로 두고 노드마다 카테고리를 내려 보고(N2), 실패하면 최상위 부모부터 벗겨 조각마다 찾고(N3, N4), 평탄화(N5)는 마지막에만 한다. 근거는 부모를 벗기면 질의가 여럿으로 갈라져 후보 풀이 커지는데 자식부터 떼면 내용을 지불하고도 부모가 여전히 복합으로 남는다는 것이다(spec/capability-table.md 3.6.3절). 종전에 이 항목은 design/content-binding-contract.md 3.4절과 report/sections/06-examples.md 7.4절에 "아직 한 문서에 못 박지 않았다"로 적혀 있었다.

I-10 (2026-08-23 신설) T-7(속성 추출)이 137건 전량에 값을 채웠지만(plan/node-tag-and-property-poc-blueprint.md 6절, 2026-08-22), 사람이 그 값을 고쳐 확정하고 재현 가능한 정답으로 남기는 화면과 저장소가 아직 없다. 경계 쪽은 iis_boundary_annotation/iis_boundary_annotation_node 골든셋과 BoundaryAnnotationEditor.tsx 편집기를 갖췄는데 속성 쪽만 비어 있다. 같은 모양(LLM 초안 테이블 iis_semantic_component가 이미 category와 properties를 한 행에 담는 방식)으로 컬럼을 더하고 편집기에 속성 패널을 얹는 설계를 plan/iis-property-annotation-ui-blueprint.md에 정리했다. D-1~D-4 모두 제안 상태이고, 오너 확인 전에는 마이그레이션도 화면 작업도 시작하지 않는다.


J. 생성 모델 파인튜닝#

착수 전이다. 설계는 finetuning-plan.md 에 있다. 대부분 규약 확정에 걸려 있고 J-1 만 지금 독립적으로 진행할 수 있다.

# 할 일 상태 선행 조건
J-1 평가셋을 38 중분류로 확장 → MOR-1928 [ ] 없음. 분할 찌꺼기를 먼저 걷어내야 한다 (아래)
J-2 few-shot 기준선 측정 (튜닝 없이 어디까지 되나) [ ] J-1, 스키마 확정
J-3 Steps 역생성 파일럿 [ ] 스키마 확정, CORE/DESC/DECO/OUT 정의 확인 (I절)
J-4 round-trip 검증 파이프라인 [ ] J-3
J-5 카테고리 확대 (ListingTable, ComparisonTable) [ ] RLSC 대응 확인
J-6 라벨링 항목과 지침 확정 [ ] B-1, B-2 등급 논의, importance 필드 결정
J-7 SFT [ ] J-3, J-4
J-8 선호 학습 [ ] J-6, J-7

J-1 골든셋 90건이 17 중분류만 덮는데 실사용 관측은 38종이다. 데이터셋에 분할 찌꺼기가 있다. 샘플 58건 중 11건이 빈 heading 으로 시작하고 전부 --- 분할로 쪼개진 조각이다 (markdown-summary-samples.md 0.2절). 평가셋을 넓히기 전에 걷어낸다.

J-4 comp-sign v4.3.8 이 이미 있어 구현은 작다.

J-5 표현 수단은 2026-08-02 에 생겼다. v0.5 matrix 가 표 계열을 담는다 (capability-table.md 2.1절). 남은 것은 RLSC 대응 확인이다(그 문서 7절).

데이터가 스키마에 묶인다. 규약이 흔들리는 동안 대량 생성에 들어가면 그만큼 버린다. J-3 이후는 A절 규약 확정 뒤에 연다.


K. Week 27 PoC — 10,000 규모 인덱스 위 자유 입력 전체 파이프라인 (신설, 2026-09-05 정원석)#

목표. component index 약 10,000건 위에서, 자유 텍스트 input을 넣으면 → Visual Summary 생성(complexity L1/L2/L3 선택 또는 동시 생성) → 검색 질의 변환 → 검색 실행 → 대치(content binding) → 결과물을 화면에서 렌더링까지, 전체 파이프라인이 한 번에 도는 것을 확인하는 PoC. 이번 주(week 27)에서 가장 급한 것.

1차 완료 (2026-09-05, K-1~K-8). 파이프라인 실동작 확인, 인덱스 커버리지 실측(178,095건), 기존 스크립트로 로컬 인덱스 2,494→12,348건 확보, 복잡도 레벨 토글과 동시 생성, L1 위상 지시 누락 버그 수정, 브라우저 실험 결과를 Claude 가 다시 읽는 로컬 로그, "여정 뷰" 화면까지 끝났다.

2차 진행 중 (K-9~K-11). 검색을 지금의 컴포넌트 단위(RLSC 시뮬레이션)에서 I.I.S. 5단계(노드 단위) default로 바꾸는 작업. 조사 결과 코드는 이미 있고 스키마도 안 바꿔도 되는데, iis_semantic_component 데이터가 로컬 176건·프로덕션 60건뿐이라 지금 배선하면 후보가 거의 안 잡힌다. 두 경로는 절대 폴백으로 섞지 않는다 — 평가가 안 되기 때문이다(정원석 확정). 로컬 파일럿 추출은 K-10에서 5,000건 규모로 끝났고(boundary 4,764건·property 3,955건), 그 위에 K-9 배선도 끝났다 — 플레이그라운드에서 검색 엔진을 골라 자유 입력부터 대치까지 노드 경로로 돌려볼 수 있다. 남은 것은 정확도 판정(K-9-1)과 프로덕션 추출(K-11, AI 에이전트가 못 건드리는 영역이라 사람이 별도로 처리한다)이다.

# 할 일 상태 담당 비고
K-1 파이프라인 실동작 확인 — 생성기(generator/generate.ts)·질의 변환(query.ts::buildSearchQuery)·검색(search.ts::rankComponents)·대치(binding.ts::bindComponentToRlsc)·렌더(McInlineCanvas.tsx) 전부 코드로 존재하고 /visual-summary 플레이그라운드에서 자유 텍스트 입력으로 전체가 한 번에 돈다 [x] 정원석 2026-09-05 조사 완료. 새로 만들 것 없음
K-2 검색 인덱스 커버리지 실측 — sheet_json과 structure_json을 둘 다 가진 design_objects 수를 프로덕션 readonly DB에서 직접 센다 [x] 정원석 실측 완료 (2026-09-05). SUPABASE_PROD_READONLY_DB_URL로 직접 조회: design_object_semantic_tags(version 2.2 전량) 178,095건 전부가 sheet_json·structure_json 둘 다 갖췄다. 문서에 남아 있던 "2,468건"은 로컬 개발 DB의 작은 seed 규모였지, 실제 데이터 부족이 아니었다 — 프로덕션은 이미 목표(10,000)의 17배가 넘는다
K-3 인덱스를 10,000건 규모로 확보 [x] 정원석 완료 (2026-09-05). 기존 스크립트 scripts/visual-summary-expand-index.ts --per-tag 60(태그별 균형 샘플링, plan/poc-guide.md 8절에 이미 문서화돼 있던 것)로 프로덕션에서 로컬 DB로 직접 복사 — 로컬 인덱스 2,494→12,348건. 주의: 처음엔 이 기존 스크립트를 확인 안 하고 비슷한 역할의 경량 seed 스크립트를 새로 만들었다가 중복이라 삭제하고 이걸로 대체했다. supabase/config.toml의 [db.settings].statement_timeout이 정수(120000)라 CLI 2.113.0에서 파싱 에러가 나던 것도 문자열("120000ms")로 함께 고쳤다
K-4 Complexity 레벨(L1/L2/L3) UI 토글 구현 — 화면에 레벨 선택 추가, 생성기 프롬프트를 레벨 단위 블록으로 쪼개 조립 [x] 정원석 구현 완료 (2026-09-05). 프롬프트 레벨 분리(buildSystemPromptForLevel, topology-containment.md/topology-juxtaposition.md)는 이미 있었다 — generate.ts/service.ts/run/route.ts/PlaygroundTab 배선만 추가했다. 실측: 같은 입력에 L1은 Fishbone, L2/L3은 List를 골라 레벨이 실제 생성 결과에 영향을 줌을 확인
K-5 레벨 동시 생성 — 같은 입력을 L1/L2/L3 세 레벨로 나란히 생성해 화면에서 비교 [x] 정원석 구현 완료 (2026-09-05). PlaygroundTab에 "⧉ L1/L2/L3 동시 생성" 버튼. 처음엔 3열 그리드로 나란히 폈는데 "가로로 좁아 보기 힘들다"는 지적으로, 위쪽 요약 표(카테고리 뱃지 비교) + 행 클릭 시 아래 전체 폭 상세로 구조를 바꿨다
K-6 L1(Single) 위상 지시 누락 버그 수정 [x] 정원석 버그 실측·수정 완료 (2026-09-05). K-4 배선 직후 실사용 테스트(고객 문의 프리셋)에서 L1을 선택해도 컴포넌트 5개로 병치되는 걸 발견 — prompt.ts::topologyBlocksFor('L1')가 빈 문자열만 반환해 "1개로 제한하라"는 지시 자체가 없었다. nestingHint에 명시 제약을 추가하고, generate.ts에 "산출물이 선언한 레벨과 어긋나면 재생성" 검증도 추가했다(schema-v0.5.md 8.9절이 "아직 구현되지 않았다"로 남겨 뒀던 부분)
K-7 로컬 실험 로그 — 브라우저에서 돌린 결과를 Claude 가 다시 읽을 수 있게 [x] 정원석 구현 완료 (2026-09-05). src/app/api/visual-summary/runLog.ts 신설, service.ts::runVisualSummaryPoc에서 호출. 매 실행마다 콘솔 한 줄 + logs/visual-summary-runs.jsonl(gitignore) 에 레벨·카테고리·검색 후보(1순위 design object·점수)·슬롯 채움률·재시도 횟수를 구조화해 남긴다. K-6 버그를 이 로그로 바로 잡았다. 사용법은 plan/poc-guide.md 8절
K-8 결과 화면을 "여정 뷰"로 새로 만듦 — 컴포넌트마다 구조→확정→검색→매핑→결과 5단계를 표로 한눈에 보고, 행을 고르면 카드로 펼침. 기존 상세 trace 는 토글로 유지 [x] 정원석 구현 완료 (2026-09-05). RunFlowView.tsx 신설 — "가로로 3개 펼쳐서 보기 힘들다"는 지적(K-5) 이후 결과 자체의 가독성 문제로 확장됐다. index.tsx에 흐름 보기/상세 trace 토글 추가
K-9 I.I.S. 5단계(노드 단위 검색)를 플레이그라운드 검색 엔진으로 배선 — 폴백 없는 완전 분리 경로 [x] 정원석 구현 완료 (2026-09-07). K-10 으로 데이터가 생겨(경계 4,764건 → 검색 단위 7,696 노드) 배선했다. /visual-summary 플레이그라운드에 검색 엔진 토글을 두고, 고르면 자유 입력 → 생성 → 검증 → 카테고리 확정까지는 같은 코드를 쓰고 검색·대치만 iis/nodeSearch.ts·iis/nodeBinding.ts 로 갈아 끼운다(service.ts 의 SearchLane — 규칙을 두 벌 만들지 않으려고 인터페이스 하나만 바꿔 받는다). 두 엔진은 서로 폴백하지 않는다(정원석 확정) — 노드 쪽이 못 찾으면 그대로 "후보 없음" 이고 컴포넌트 태그 풀은 읽지도 않는다. 이 불변식은 service.test.ts 가 못박는다(검사에 실제로 폴백을 넣어 깨지는 것까지 확인). 노드 엔진은 comp-sign 비교·결함 채점을 돌리지 않는다 — 용량 근거가 컴포넌트 전체의 자라 서브트리 대치에 못 댄다(화면이 "이 엔진은 미채점"으로 적어 결함 없음과 구별한다). 실측: 같은 입력·같은 design object 에서 컴포넌트 태그는 5/17 슬롯, 노드는 5/9 슬롯을 채운다(후보 단위가 서브트리라서) — 한 컴포넌트(DO 6099766)가 Group_5·Group_6 두 노드로 각각 후보에 오르는 것도 확인했다
K-9-1 노드 검색 정확도 실측 — 두 엔진을 같은 입력에 돌려 나란히 비교 [ ] 정원석 K-9 로 화면은 생겼고, 아직 판정을 안 했다. 지금은 실행마다 엔진을 손으로 바꿔 눈으로 본다. 어느 쪽이 얼마나 맞히는지는 실사용 입력 묶음(fixtures/realInputs.ts 10건 등)으로 두 엔진을 돌려 사람이 대치 결과를 보고 판정해야 나온다(precision 우선). 자동 채점은 노드 경로에 용량 자가 없어 지금 구조로는 안 된다 — 무엇으로 잴지부터 정해야 한다
K-10 I.I.S. 로컬 파일럿 추출 (경계 T-4 + 속성 T-7) [x] 정원석 완료 (2026-09-06), 1,000건 계획을 5,000건으로 키워 실행. scripts/iis-extract-boundary.ts --limit 5000 → scripts/iis-extract-property.ts --limit 5000 순서로 실행(속성은 경계에 의존하는 설계라 한 호출로 못 합친다). 실행 전 속성 배치가 순차 루프였음을 실측으로 발견(20건 122초 ≈ 건당 6.1초 — 1,000건이면 그대로 102분) — 경계 배치가 이미 쓰던 동시성 워커풀을 속성에도 옮겨 20건 12.7초로 줄였다(PR #2361). 이어서 동시성 상한을 20→200으로 올리고 CLI에 --concurrency 옵션을 추가해 100으로 돌렸다. 결과: 경계 4,764건·속성 3,955건 적재(목표 5,000 대비 경계 실패 83건·속성 실패 234건, 대부분 동시성 100에서의 "Request timed out" — 재시도 시 50 정도로 낮추는 편이 나아 보인다). gpt-5.6-luna 단가를 웹 검색으로 확인해 models.ts에 채웠다(입력 $0.2/출력 $1.2/cached $0.02 per 1M, 2026-07-30 개정) — 실측 토큰 기준 이번 5,000건 배치 비용은 경계·속성 합쳐 **약 $89**. reasoning_effort는 둘 다 low(DEFAULT_BOUNDARY_REASONING_EFFORT/DEFAULT_PROPERTY_REASONING_EFFORT, extractBoundary.ts/extractProperty.ts에 명시적 기본값) — 위 시간·비용·실패율은 전부 이 값 기준이다. 정확도 비교 없이 effort를 올리면 이 실측치가 그대로 안 맞게 된다
K-11 프로덕션 I.I.S. 추출 — AI 에이전트가 못 하는 작업 [ ] 정원석 (또는 권한 있는 사람) .claude/hooks/block-prod-db.sh가 프로덕션 DB 쓰기를 원천 차단한다(SUPABASE_PROD_READONLY_DB_URL만 허용). 로컬 파일럿(K-10)이 검증됐으니, 같은 스크립트를 프로덕션 쓰기 권한을 가진 사람이 직접 돌리거나 실서비스 배치 파이프라인에 태워야 한다 — Claude 세션에서는 시도하지 않는다

K-2/K-3 근거. indexPool.ts의 WHERE_STRICT가 sheet_json과 structure_json을 둘 다 요구해 검색 후보를 좁힌다. README.md 3절이 인용하는 83,771건은 2026-07-22 적재분 스냅샷이라 낡았다 — 지금 프로덕션 실측(2026-09-05)은 178,095건이고 전량이 strict 조건을 만족한다. 병목은 데이터가 아니라 로컬 개발 환경에 이 데이터가 안 들어와 있다는 것뿐이다.

K-4/K-5 근거. I-13이 이미 같은 내용을 미착수로 적어 뒀다. K절은 I-13을 "이번 주 PoC의 필요조건"으로 승격시키는 것이지 새 설계가 아니다.


참고#

README.md 허브 · schema-v0.5.md 규약 · capability-table.md 41종 capability · projection-model.md 설계 의도 · yaml-examples.md 예시 · semantic-taxonomy-v7.md 단어장 · markdown-summary-samples.md 실사용 샘플 · pipeline-flow.md 전체 흐름 · content-binding-contract.md 내용 대치 contract · smart-diagram-data-model.md 스마트 다이어그램 종류와 스키마

↑ 목차로 · 1부 머리로

2부

규약 — spec/

이 책의 핵심이다. 스키마가 무엇을 정하고, 45종이 각각 무엇을 받고, 빈 결과가 왜 사용자에게 가지 않는지.

질의 component.type예: Gauge capability 대조성립 조건을 만족하나 후보가 있다 후보가 비었다 그 컴포넌트로 간다 fallback 순서의 다음 후보같은 카테고리 안 → 이웃 카테고리 다시 대조한다 끝까지 비어도 빈 화면은 보내지 않는다규약이 보장하는 것은 이 한 줄이다
fallback 은 「검색이 실패해도 화면이 비지 않게」 하는 장치다. 사슬을 몇 번 따라가는지의 상한은 2026-08-17 에 폐지됐다. 전체 순서는 2부 5장 3절, 엣지 목록은 5부 27장에 있다.
4장스키마 규약 v0.5 정본규약 SSoT · core schema·생성·검증·fallback

원문 spec/schema-v0.5.md GitHub 에서 보기 52 KB 원문 제목 「Summary 와 RLSC 공용 스키마 규약 v0.5」

이 문서가 SSoT이다. schema-v0.4.md 를 대체한다. v0.4 는 기록으로 보존하고 갱신하지 않는다.

오너 정원석 (MORDOR) · 상태 확정 · 최종 갱신 2026-09-05 (B-21 — ornament 축이 Element.Separator/LayoutContainer.Separator까지 흡수, 엣지 130개 동기화) · 티켓 MOR-1773

이 문서의 역할 사용자 입력을 검색과 대치에 쓸 수 있게 구조화한 산출물의 규약이다. 무엇을 어떤 필드에 적는가만 정한다. 어떤 컴포넌트가 그 구조를 받을 수 있는지는 capability-table.md, 왜 이런 모양인지는 1절에 있다.

관련 capability-table.md 45종 성립 조건과 fallback 순서, 복합 구조 순서 3.6절 · semantic-taxonomy-v7.md 단어장 · yaml-examples.md 예시 · TODO.md

이 문서 훑기

절 무엇에 답하나
0 v0.4 에서 무엇이 바뀌었나
1 어떤 원칙과 규칙 위에 서 있나
2 필드가 무엇이고 어떤 모양인가
3 누가 어떻게 생성하고 검증하나
4 성립 조건은 어디에 있나
5 원하는 컴포넌트가 없으면 어떻게 되나. 6번이 노드가 여럿일 때의 순서다
6 type 이 무슨 일을 하나
7 RLSC 를 안 고치고 얹을 수 있나
8 실제로 어떻게 생겼나. 8.8절이 복잡도 레벨, 8.9절이 그것을 켜고 끄는 방법이다
9 아직 안 정해진 것은 무엇인가

0. v0.4 에서 무엇이 바뀌었나#

2026-08-02 에 정한 열두 가지다. 각 결정의 근거는 TODO.md G절에 있다.

# v0.4 v0.5
1 LLM 이 JSON 을 생성하고 YAML 로 직렬화 LLM 이 YAML 을 직접 생성한다. 이유는 추론 속도다
2 최상위가 structure(단일) 또는 components[](다중) 항상 components[] 배열이다. 하나면 길이 1이다
3 component.type enum 11종 type 문자열 하나, 대분류.중분류 결합값 45종. 종전에는 대분류 21값을 더해 62값이었으나 2026-08-29 에 뺐다 (6절 상자). v7 단어장 49종에서 지도 전용 4종을 뺀 45종이다 — 지도는 지원하지 않는다(capability-table.md 범위 절)
4 component.confidence optional 제거했다
5 없음 alternatives[] 를 열었다. 못 쓸 때 대신 쓸 카테고리를 선호 순서로 적는다
6 4-1절이 traits(계층, 장식) 도입을 제안 도입하지 않는다 → 2026-08-03 에 뒤집혔다 (아래 상자). traits 묶음은 두지 않고 꾸밈은 decoration, 계층은 entity.components[] 로 나눠 갔다. 2026-08-04 에 2절에 반영했다
7 dataset.type 3종(gauge, stacked_bar, combo) dataset.shape 2종(measure, matrix)
8 relation.type 1종(intersects) 2종(intersects, compares)
9 entity.id 필수 생성물에서 뺐다. 파싱 뒤 기계가 매긴다 (3.2절)
10 없음 page 수준 relations 를 예약했다. Summary 생성기는 채우지 않는다
11 3절에 capability 테이블 11행 별도 문서 41행으로 분리했다
12 11절에 열린 결정 15건 TODO.md B절이 SSoT 다

6번은 2026-08-03 에 뒤집혔다. 장식과 계층을 질의 쪽에도 적는 방향으로 바꿨다.

  • 계층은 새 필드를 두지 않는다. 이미 있는 entity.children[](같은 컴포넌트 안)과 entity.components[](컴포넌트 경계를 넘는 중첩)를 실제로 쓴다. 어느 생성기가 entity.components[] 를 채우느냐는 B-20 이다 (2026-09-02: PoC 는 채운다 / 실서비스 Summary 생성기는 아직 안 채운다. 8.7절·8.8절).
  • 장식은 Summary 가 축별 선호를 적는다. traits.decoration 같은 누적 등급 하나로 두지 않는다. 축은 ornament(Element.Decoration, Element.Separator)와 background(Element.Background) 둘이고 각각 RLSC role 군 하나에 대응한다(2026-09-05 정원석, B-21 — Separator는 별도 축을 새로 만들지 않고 ornament에 흡수했다). 값은 셋(prefer, indifferent, avoid)이다.
  • 전용 속성은 DECO 계열에만 둔다. 타이틀과 디스크립션(Element.Title, Subtitle, Description)과 강조(Element.Highlight)는 대치가 내용으로 채우는 노드이므로 선호 선언이 필요 없다. 앞은 structure 와 entity.components[] 가 담고 뒤는 entity.emphasis 가 담는다. 그래서 회의 용어 N+α 는 개념 자체가 사라졌고, N 과 N+β 만 프리셋 이름(minimal, rich)으로 남는다.

설계 의도와 근거는 ../design/decoration-model.md 가 SSoT다. 표기 반영은 2026-08-04 에 끝냈다 (../TODO.md B-18). 아래 본문의 2절이 decoration 을 포함하고, 자리와 해석 순서는 2.7절에 있다. 1단계에서 생성기는 이 필드를 채우지 않는다.

'생성기'는 Summary 생성기를 가리킨다. 사용자 입력을 받아 이 규약대로 산출물을 써 내는 LLM 모듈이다. 현행은 Markdown 을 쓰고 4단계에서 YAML 로 바뀐다 (README.md 2절 용어, 4절 마일스톤).

entity 의 필드 구성은 대체로 유지하고 셋(period, avatar, organization)을 더했다. id 는 남아 있지만 optional 이고 생성기가 쓰지 않는다. entity.components[](중첩)는 v0.4 부터 스키마에 열려 있다. PoC 생성기는 채운다. 실서비스 Summary 생성기는 아직 채우지 않는다 (B-20, 8.7절).

0.1 버전 규칙 (2026-08-29 신설)#

규약을 고치면 버전을 올린다. 예외는 없다. 종전에는 v0.5 를 자리에서 여러 번 고치면서 버전을 그대로 두었다(2026-08-06 entities+columns 추가, 2026-08-17 fallback 상한 폐지, 2026-08-28 fallback 규칙 6번 신설, 2026-08-29 type 필수). 그래서 "지금 규약이 무엇인가"를 버전으로 가리킬 수 없었고, 생성물이 어느 판본을 따랐는지도 알 수 없었다.

형식은 MAJOR.MINOR.PATCH 이고 각 자리는 0 부터 99 까지다. 세 자리 모두 두 자리까지 쓸 수 있고, 앞자리를 0 으로 채우지 않는다(0.5.1 은 되고 0.05.01 은 안 된다).

^(0|[1-9][0-9]?)\.(0|[1-9][0-9]?)\.(0|[1-9][0-9]?)$

어느 자리를 올릴지는 하나로 판정한다 — 이 변경으로 무효가 되는 기존 산출물이 실제로 있는가.

자리 언제 올리나 예
MAJOR 기존 산출물이 실제로 무효가 된다. 필드를 없애거나 뜻을 바꾸거나 최상위 모양을 바꾼다 v0.4 → v0.5 의 "최상위가 항상 components[]"
MINOR 기존 산출물은 그대로 유효하고 새로 쓸 수 있는 것이 늘어난다. 필드·값·카테고리 추가, 규칙 신설 entities+columns 대안 경로 추가, fallback 규칙 6번 신설
PATCH 기존 산출물의 유효성이 바뀌지 않는다. 문구 정정, 근거 보강, 실제로 지켜지던 것의 명문화, 오탈자 type 필수화(2026-08-29)

type 필수화가 PATCH 인 이유를 적어 둔다. 원리적으로는 required 를 좁히는 변경이라 무효가 되는 산출물이 있을 수 있다. 그런데 실제로는 0건이었다 — 골든셋 v1 의 컴포넌트 470개가 전부 이미 type 을 갖고 있었고 생성기 프롬프트도 적으라고 지시하고 있었다. 판정은 원리가 아니라 실측으로 한다. 그래서 PATCH 다. 실측 없이 좁히는 변경은 MINOR 이상으로 본다.

검증기는 MAJOR 와 MINOR 만 대조하고 PATCH 는 보지 않는다. PATCH 는 정의상 유효성을 바꾸지 않으므로, patch 가 다르다고 옛 산출물을 떨어뜨리면 규칙이 자기모순이 된다. version 값은 0.5.x 대역 전체를 받아들이고, 정확한 patch 값은 계측에만 쓴다.

파일 이름은 MAJOR.MINOR 만 따라간다. 이 문서가 schema-v0.5.md 이고 JSON Schema 가 visual-summary-0.5.schema.json 인 것은 그대로 둔다. patch 를 올릴 때마다 파일 이름과 문서 30여 곳의 링크가 바뀌면 얻는 것보다 잃는 것이 크다. MINOR 이상을 올릴 때만 파일을 새로 판다.

현행 값은 0.5.6 이다. 종전 표기 0.5 는 0.5.0 으로 읽는다. 코드 반영은 아직이다(../TODO.md I-14).

값 무엇이 바뀌었나 자리 판정 근거
0.5.0 종전 표기 0.5 —
0.5.1 component.type 필수화 (6절) 골든셋 470/470 이 이미 type 을 갖고 있어 무효 0건
0.5.2 type 어휘에서 대분류 21값 제거 (6절) 골든셋의 컴포넌트 type 454개가 전부 결합값이라 무효 0건
0.5.3 enum 에 PageNavigation 4종 반영 (41 → 45) B-24 가 2026-08-06 에 이미 정했고 capability 테이블·코드·프롬프트는 전부 45종으로 돌고 있었다. enum 만 안 따라온 미반영 결함의 수정이라 PATCH 다
0.5.4 ScopeCoverage.NestedScope 가 children 필수 조건을 빼고 direction(inward/outward) 속성을 신설 (B-34, 2.3절) 필드 하나가 빠지고 하나가 선택 속성으로 늘었을 뿐이라 기존 산출물을 무효로 만들지 않는다(선택 속성은 안 적어도 되고, children 을 쓰던 산출물도 스키마가 여전히 그 필드 자체를 막지는 않는다) — PATCH
0.5.5 5절의 type "optional" 잔존 문구를 0.5.1 필수화에 맞게 정정, 2.5.1 의 어휘 수 41 을 45 로 갱신 규범 내용 변경 없는 문구 정정이라 무효 산출물 0건 (PATCH)
0.5.6 Visual Summary 생성기 type 어휘에서 9종 제외(첫 출시). I.I.S.·검색 인식 어휘는 45종 유지. capability.json 에 generatable 과 재지정 예외 한 줄(generatorRedirect) 반영. 함께 capability-table.md 1절에 생성 시점 쓰기 의무를 명문화 — 그 카테고리가 읽는다고 선언하지 않은 내용 필드에 값을 담지 않는다(B-33, 2026-09-05 정원석 확정분의 정본 반영. 3.4절 P3 가 fallback 엣지 한정이라는 범위도 함께 적었다) enum 을 줄이지 않는다. 생성기만 서브셋을 쓴다. 쓰기 의무도 생성기 스코프에만 적용하고 이미 만들어진 산출물을 소급해 무효로 보지 않는다고 문장 자체가 한정한다. 기존 산출물의 유효성이 바뀌지 않으므로 PATCH. 생성 단계 검증이 이 의무를 재시도 사유로 볼지는 구현(I-16·I-17 계열)에서 정한다

1. 설계 원칙#

생성 단계에서 최적 semantic category 를 정한다. 생성기가 입력을 읽고 어느 카테고리로 그릴지 골라 type 에 적는다. 검색은 그 카테고리를 capability 와 대조해 실제로 그릴 수 있는지 확인하고, 못 그리면 fallback 순서를 따라간다(capability-table.md 3절).

생성기가 고를 수 있는 type 은 규약 enum 45종의 부분집합이다 (2026-09-11). capability-table.md 10.1절과 schema/capability.json 의 generatable 이 그 부분집합을 정한다. 첫 출시 기준 36종 — generatable: false 아홉(엔진 없음 여섯, HierarchicalTree, 공급 제외 Gauge·RadarChart)은 프롬프트 어휘와 검증에서 뺀다. I.I.S. 추출·검색 인식 어휘는 45종 전량이다. 스키마 enum 을 줄이지 않는다. RadarChart 를 빼는 자리의 생성 재지정은 ComparisonTable 이다(다축 보존, capability.json 의 generatorRedirect). BarChart 가 3.2절 첫 후속이지만 생성기가 그리로 가면 축이 붕괴한다. Gauge 는 3.2절과 같이 BigNumber 다.

structure 의 필드 이름을 카테고리에 묶지 않는다. venn.sets 가 아니라 entities 이고, profile.avatar 가 아니라 entity.avatar 다. 필드 중에는 사실상 한 카테고리만 읽는 것도 있다. avatar 와 organization 은 ProfileCard 가, speaker 는 DialogueChat 이, author 와 source 는 QuoteTestimonial 이 읽는다. 그래도 이름이 중립이면 못 읽는 카테고리가 그냥 지나갈 수 있고, fallback 은 그래서 돈다 (1절 "선언되지 않은 것은 무시한다").

카테고리마다 다른 것은 params 가 담는다. structure 는 내용, params 는 표현 선택이다. params 의 허용 키와 기본값은 capability-table.md 2절이 카테고리마다 정한다.

v0.2 와 v0.3 은 반대였다. 그때는 생성물이 표현형에 커밋하지 않고 검색이 골랐다. v0.5 가 이를 뒤집은 경위는 archive/projection-model.md 에 기록으로 남아 있다.

아래 규칙 셋은 어떤 structure 에도 예외 없이 성립한다. 이 셋이 성립하기 때문에 fallback 순서가 반드시 끝나고, 컴포넌트가 무엇을 못 그리든 결과가 비지 않는다.

어떤 구조든 리스트로 읽힌다 모든 structure 는 리스트로 읽히는 entities 를 가진다. dataset 만 있으면 거기서 파생한다. 그래서 마지막 fallback 이 언제나 성립한다.

선언되지 않은 것은 무시한다 relations 와 슬롯과 dataset 은 더해지는 정보다. 못 읽는 컴포넌트는 그냥 안 읽는다. 규약이 따로 정하지 않은 것의 기본 동작이 무시다.

중첩은 평탄화된다 entity.components[] 를 못 읽는 컴포넌트는 자식의 entities 를 부모 entity 의 items 로 편다. 여기에도 첫 규칙이 걸리므로, 깊이가 얼마든 결국 리스트로 읽힌다. 평탄화는 마지막 수단이다. 그 전에 모양을 지키며 카테고리를 내리고(N2), 최상위 부모를 벗겨 조각마다 찾는다(N3). 다섯 단계 전량은 capability-table.md 3.6절이다.

RLSC 로는 컴포넌트가 정해지지 않는다. RLSC 는 컴포넌트의 구조를 기술하는 어휘다. 어느 컴포넌트를 쓸지는 검색 모듈이 capability 와 type 을 대조해 정한다.


2. Core Schema v0.5#

규약을 두 벌로 적는다. 앞의 TypeScript 타입은 사람이 필드 구성을 읽는 용도이고, 뒤의 JSON Schema 는 기계가 생성물을 판정하는 용도다. 둘은 같은 것을 정의한다.

벌 형식 언제 보나 절
정의용 TypeScript 어떤 필드가 있고 무엇이 필수인지 볼 때 2.1
검증용 JSON Schema 생성물이 규약을 지켰는지 판정할 때 2.2

SSoT 는 JSON Schema 다. 정의용 TypeScript 타입과 검증기 zod 스키마는 둘 다 JSON Schema 에서 나온 파생물이고, 셋이 어긋나면 JSON Schema 를 따른다.

2.1 정의용 TypeScript 타입#

RLSC 를 TypeScript 로 정의한 것과 같은 방식이다 (src/app/pipeline/common/type/layoutStructure.ts, packages/structured-layout/src/role-v2.ts).

ts
// 한 입력에서 나온 산출물 전체. 최상위다.
export interface VisualSummary {
  version: '0.5.6';           // 0.1절 버전 규칙. 검증은 MAJOR.MINOR 만 대조한다
  title: string;               // 빈 문자열을 허용하지 않는다
  components: Component[];     // 1개 이상
  relations?: Relation[];      // page 수준 관계. 자리만 예약했고 생성기는 채우지 않는다
  decoration?: Decoration;     // page 수준 꾸밈 선호. 자리만 예약했고 생성기는 채우지 않는다 (2.7절)
}

// 컴포넌트 하나. structure 만 필수다.
export interface Component {
  id?: string;                       // ^[a-zA-Z0-9_-]+$. 생성기는 채우지 않는다
  type: SemanticCategory;            // 필수 (2026-08-29). 생성기가 고른 카테고리. 검색이 그대로 따를 의무는 없다 (6절)
  alternatives?: SemanticCategory[]; // 못 쓸 때 대신 쓸 카테고리를 선호 순서로
  params?: Record<string, unknown>;  // 표현 선택 속성. 허용 키는 capability-table.md 2절
  structure: Structure;
  decoration?: Decoration;           // 이 컴포넌트에만 적용된다 (2.7절)
}

// 꾸밈 축에 담기는 값. 이 셋이 전부다
export type Preference = 'prefer' | 'indifferent' | 'avoid';

// 꾸밈구조. 축 하나가 RLSC role 군 하나에 대응한다.
// minimal 이나 rich 같은 프리셋 문자열은 받지 않는다
export interface Decoration {
  ornament?: Preference;    // Element.Decoration, LayoutContainer.Decoration, Element.Separator, LayoutContainer.Separator
  background?: Preference;  // Element.Background, LayoutContainer.Background
}

// 대분류 21종. 각 대분류는 자기 이름과 그 아래 중분류를 값으로 갖는다.
export type Enumeration = 'Enumeration' | 'Enumeration.List' | 'Enumeration.ListingTable';
export type AttributeComparison = 'AttributeComparison' | 'AttributeComparison.Versus' | 'AttributeComparison.ComparisonTable' | 'AttributeComparison.RadarChart';
export type RelationalComparison = 'RelationalComparison' | 'RelationalComparison.SWOT' | 'RelationalComparison.VennDiagram' | 'RelationalComparison.Quadrant' | 'RelationalComparison.BubbleChart';
export type Transformation = 'Transformation' | 'Transformation.Transformation';
export type ProcessFlow = 'ProcessFlow' | 'ProcessFlow.Steps';
export type LogicFlow = 'LogicFlow' | 'LogicFlow.FlowChart';
export type Progression = 'Progression' | 'Progression.Staircase';
export type TimelineSchedule = 'TimelineSchedule' | 'TimelineSchedule.Timeline' | 'TimelineSchedule.GanttChart' | 'TimelineSchedule.Calendar';
export type Hierarchy = 'Hierarchy' | 'Hierarchy.HierarchicalTree' | 'Hierarchy.Pyramid';
export type ScopeCoverage = 'ScopeCoverage' | 'ScopeCoverage.NestedScope';
export type Cycle = 'Cycle' | 'Cycle.Cycle';
export type ReasoningStructure = 'ReasoningStructure' | 'ReasoningStructure.Fishbone' | 'ReasoningStructure.CombinedResult' | 'ReasoningStructure.ProblemSolution';
export type KPIKeyMetric = 'KPIKeyMetric' | 'KPIKeyMetric.BigNumber' | 'KPIKeyMetric.Gauge';
export type TextBasedCommunication = 'TextBasedCommunication' | 'TextBasedCommunication.HighlightedMessage' | 'TextBasedCommunication.LongFormTextLayout';
export type QnACard = 'QnACard' | 'QnACard.QnACard';
export type Utterance = 'Utterance' | 'Utterance.DialogueChat' | 'Utterance.QuoteTestimonial';
export type PortraitProfile = 'PortraitProfile' | 'PortraitProfile.ProfileCard' | 'PortraitProfile.ResumeCV';
export type Conversion = 'Conversion' | 'Conversion.Funnel';
export type ProportionComposition = 'ProportionComposition' | 'ProportionComposition.TreeMap' | 'ProportionComposition.PieChart' | 'ProportionComposition.DonutChart' | 'ProportionComposition.StackedBarChart' | 'ProportionComposition.PictogramChart';
export type Trend = 'Trend' | 'Trend.LineAreaChart' | 'Trend.ComboChart';
export type MagnitudeRanking = 'MagnitudeRanking' | 'MagnitudeRanking.BarChart';


// 결합값(대분류.중분류) 45종만이다. 대분류 단독은 값이 아니다 (2026-08-29, 6절 상자).
export type SemanticCategory =
  | Enumeration
  | AttributeComparison
  | RelationalComparison
  | Transformation
  | ProcessFlow
  | LogicFlow
  | Progression
  | TimelineSchedule
  | Hierarchy
  | ScopeCoverage
  | Cycle
  | ReasoningStructure
  | KPIKeyMetric
  | TextBasedCommunication
  | QnACard
  | Utterance
  | PortraitProfile
  | Conversion
  | ProportionComposition
  | Trend
  | MagnitudeRanking;


// entities 와 dataset 중 적어도 하나가 있어야 한다
export type Structure =
  | (StructureSlots & { entities: Entity[] })
  | (StructureSlots & { dataset: Dataset });

interface StructureSlots {
  entities?: Entity[];      // 1개 이상
  relations?: Relation[];   // 블록 안 관계
  dataset?: Dataset;
  columns?: Column[];       // entities 로 표를 적을 때 헤더. dataset 없이도 쓴다 (2.5.1절)
}

// label 만 필수다. 나머지는 카테고리가 읽을 수 있으면 읽는다.
export interface Entity {
  id?: string;              // ^[a-zA-Z0-9_-]+$. 생성기가 아니라 후처리가 채운다 (3.2절)
  label: string;            // 빈 문자열을 허용하지 않는다
  items?: string[];
  value?: number;
  unit?: string;
  period?: string;
  emphasis?: boolean;
  weight?: number;
  speaker?: string;
  author?: string;
  source?: string;
  avatar?: string;
  organization?: string;
  values?: Array<string | number | null>; // columns 와 위치 대응. label 이 범주축(첫 열)을 대신한다 (2.5.1절)
  children?: Entity[];
  components?: Component[]; // 중첩. PoC 생성기는 채운다. 실서비스는 아직 안 채운다 (B-20, 8.7절)
}

// 두 개 이상의 대상을 잇는다.
export interface Relation {
  type: 'intersects' | 'compares';
  of: [string, string, ...string[]]; // entity 의 label 을 적는다. 파싱 뒤 id 로 바뀐다 (3.2절)
  desc?: string;
}

// 수치 데이터. shape 가 어느 모양인지 정한다.
export type Dataset = Measure | Matrix;

// 단일 수치
export interface Measure {
  shape: 'measure';
  value: number;
  unit?: string;
  min?: number;
  max?: number;
  target?: number;
  delta?: number;
  thresholds?: Threshold[];
}

export interface Threshold {
  at: number;
  label?: string;
  state?: 'safe' | 'warn' | 'danger';
}

/** 행과 열. 첫 열이 범주 축이다 (2.5절) */
export interface Matrix {
  shape: 'matrix';
  columns: Column[];                        // 1개 이상. 첫 열이 범주 축이다 (2.5절)
  rows: Array<string | number | null>[];    // 1개 이상. 타입은 넓게 열려 있지만 컬럼 role별 제약이 있다 (B-37, 아래)
}

/**
 * B-37 (2026-09-05 확정). 셀 값 타입은 그 열의 Column.role이 정한다 — 타입 시스템은
 * 열마다 다른 타입을 표현할 수 없어 rows는 string|number|null로 넓게 열어 두고,
 * 실제 제약은 establish 단계에서 role별로 검사한다.
 *   - role 미지정 또는 'category': string (row header, 값이 아니라 이름)
 *   - role 'value'|'x'|'y'|'size': number | null 만 허용, string 금지 (막대 높이·좌표·크기)
 *   - role 'start'|'end'|'date': 형식을 강제하지 않는다 (2026-09-10 정정, 허재영 판단 · 정원석 확인 대기).
 *     "3월 4일" 처럼 사람이 읽는 표기를 그대로 담는다. 이 값은 계산 대상이 아니라 표시와 대치
 *     대상이고, 내용 대치는 이 문자열을 그대로 검색해 바꾼다. ISO 표기와 timestamp 숫자도 받는다.
 *     종전 문구는 "ISO 문자열 또는 timestamp 숫자만, 임의 문자열 금지" 였는데, 그러면 달력과
 *     간트가 실사용 입력에서 거의 성립하지 않는다 — 생성기 골든셋 v1 의 Calendar 10행과
 *     GanttChart 10행이 전량 탈락했고, 그중 여덟은 입력에 달이 없어 ISO 로 옮길 수도 없다
 * 카테고리 이름이 아니라 컬럼의 role이 기준이라 모든 matrix 필수 카테고리에 동일하게 적용된다.
 */

export interface Column {
  label: string;
  role?: 'category' | 'value' | 'x' | 'y' | 'size' | 'start' | 'end' | 'date';
  unit?: string;
  axis?: 'left' | 'right';
  type?: 'bar' | 'line' | 'area';
}

타입이 못 담는 조건이 넷이다. 문자열 최소 길이(title, entity.label), id 의 ASCII 패턴, 객체 리터럴이 아닌 값의 초과 필드 거부, params 의 키 검증이다. 앞의 셋은 2.2절 JSON Schema 가, params 는 capability 가 판정한다.

Threshold 라는 이름은 이 타입 정의에서만 쓴다. JSON Schema 는 같은 객체를 measure.thresholds 안에 이름 없이 적는다.

2.2 검증용 JSON Schema#

생성은 YAML 로 하고(3절), 검증은 YAML 을 JSON 으로 변환한 뒤 이 스키마로 한다.

같은 내용이 파일로도 있다. schema/visual-summary-0.5.schema.json 이고, 검증기가 $ref 로 가리킬 수 있게 내보낸 것이다. 아래 코드펜스가 SSoT 이고 파일은 생성물이다. 이 절을 고치면 그 파일도 함께 맞춘다 (schema/README.md).

json
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://miridih.com/schemas/visual-summary/0.5",
  "title": "VisualSummary",
  "type": "object",
  "required": ["version", "title", "components"],
  "additionalProperties": false,
  "properties": {
    "version":    { "type": "string", "pattern": "^0\\.5\\.(0|[1-9][0-9]?)$" },
    "title":      { "type": "string", "minLength": 1 },
    "components": { "type": "array", "minItems": 1, "items": { "$ref": "#/$defs/componentBlock" } },
    "relations":  { "type": "array", "default": [], "items": { "$ref": "#/$defs/relation" } },
    "decoration": { "$ref": "#/$defs/decoration" }
  },
  "$defs": {
    "componentBlock": {
      "type": "object",
      "required": ["structure", "type"],
      "additionalProperties": false,
      "properties": {
        "id":           { "type": "string", "pattern": "^[a-zA-Z0-9_-]+$" },
        "type":         { "$ref": "#/$defs/semanticCategory" },
        "alternatives": { "type": "array", "items": { "$ref": "#/$defs/semanticCategory" } },
        "params":       { "type": "object" },
        "structure":    { "$ref": "#/$defs/structure" },
        "decoration":   { "$ref": "#/$defs/decoration" }
      }
    },

    "decoration": {
      "type": "object",
      "additionalProperties": false,
      "minProperties": 1,
      "properties": {
        "ornament":   { "$ref": "#/$defs/preference" },
        "background": { "$ref": "#/$defs/preference" }
      }
    },

    "preference": {
      "type": "string",
      "enum": ["prefer", "indifferent", "avoid"],
      "default": "indifferent"
    },

    "semanticCategory": {
      "type": "string",
      "enum": [
        "Enumeration.List", "Enumeration.ListingTable",
        "AttributeComparison.Versus", "AttributeComparison.ComparisonTable", "AttributeComparison.RadarChart",
        "RelationalComparison.SWOT", "RelationalComparison.VennDiagram", "RelationalComparison.Quadrant", "RelationalComparison.BubbleChart",
        "Transformation.Transformation",
        "ProcessFlow.Steps",
        "LogicFlow.FlowChart",
        "Progression.Staircase",
        "TimelineSchedule.Timeline", "TimelineSchedule.GanttChart", "TimelineSchedule.Calendar",
        "Hierarchy.HierarchicalTree", "Hierarchy.Pyramid",
        "ScopeCoverage.NestedScope",
        "Cycle.Cycle",
        "ReasoningStructure.Fishbone", "ReasoningStructure.CombinedResult", "ReasoningStructure.ProblemSolution",
        "KPIKeyMetric.BigNumber", "KPIKeyMetric.Gauge",
        "TextBasedCommunication.HighlightedMessage", "TextBasedCommunication.LongFormTextLayout",
        "QnACard.QnACard",
        "Utterance.DialogueChat", "Utterance.QuoteTestimonial",
        "PortraitProfile.ProfileCard", "PortraitProfile.ResumeCV",
        "Conversion.Funnel",
        "ProportionComposition.TreeMap", "ProportionComposition.PieChart", "ProportionComposition.DonutChart", "ProportionComposition.StackedBarChart", "ProportionComposition.PictogramChart",
        "Trend.LineAreaChart", "Trend.ComboChart",
        "MagnitudeRanking.BarChart",
        "PageNavigation.Opening", "PageNavigation.TableOfContents", "PageNavigation.SectionDivider", "PageNavigation.Ending"
      ]
    },

    "structure": {
      "type": "object",
      "additionalProperties": false,
      "anyOf": [ { "required": ["entities"] }, { "required": ["dataset"] } ],
      "properties": {
        "entities":  { "type": "array", "minItems": 1, "items": { "$ref": "#/$defs/entity" } },
        "relations": { "type": "array", "default": [], "items": { "$ref": "#/$defs/relation" } },
        "dataset":   { "$ref": "#/$defs/dataset" },
        "columns":   { "type": "array", "minItems": 1, "items": { "$ref": "#/$defs/column" } }
      }
    },

    "entity": {
      "type": "object",
      "required": ["label"],
      "additionalProperties": false,
      "properties": {
        "id":           { "type": "string", "pattern": "^[a-zA-Z0-9_-]+$" },
        "label":        { "type": "string", "minLength": 1 },
        "items":        { "type": "array", "items": { "type": "string" } },
        "value":        { "type": "number" },
        "unit":         { "type": "string" },
        "period":       { "type": "string" },
        "emphasis":     { "type": "boolean" },
        "weight":       { "type": "number" },
        "speaker":      { "type": "string" },
        "author":       { "type": "string" },
        "source":       { "type": "string" },
        "avatar":       { "type": "string" },
        "organization": { "type": "string" },
        "values":       { "type": "array", "items": { "type": ["string", "number", "null"] } },
        "children":     { "type": "array", "items": { "$ref": "#/$defs/entity" } },
        "components":   { "type": "array", "items": { "$ref": "#/$defs/componentBlock" } }
      }
    },

    "relation": {
      "type": "object",
      "required": ["type", "of"],
      "additionalProperties": false,
      "properties": {
        "type": { "enum": ["intersects", "compares"] },
        "of":   { "type": "array", "minItems": 2, "items": { "type": "string" } },
        "desc": { "type": "string" }
      }
    },

    "dataset": {
      "oneOf": [ { "$ref": "#/$defs/measure" }, { "$ref": "#/$defs/matrix" } ]
    },

    "measure": {
      "type": "object",
      "required": ["shape", "value"],
      "additionalProperties": false,
      "properties": {
        "shape":  { "const": "measure" },
        "value":  { "type": "number" },
        "unit":   { "type": "string" },
        "min":    { "type": "number" },
        "max":    { "type": "number" },
        "target": { "type": "number" },
        "delta":  { "type": "number" },
        "thresholds": {
          "type": "array",
          "items": {
            "type": "object",
            "required": ["at"],
            "additionalProperties": false,
            "properties": {
              "at":    { "type": "number" },
              "label": { "type": "string" },
              "state": { "enum": ["safe", "warn", "danger"] }
            }
          }
        }
      }
    },

    "matrix": {
      "type": "object",
      "required": ["shape", "columns", "rows"],
      "additionalProperties": false,
      "properties": {
        "shape":   { "const": "matrix" },
        "columns": { "type": "array", "minItems": 1, "items": { "$ref": "#/$defs/column" } },
        "rows": {
          "type": "array",
          "minItems": 1,
          "items": { "type": "array", "items": { "type": ["string", "number", "null"] } }
        }
      }
    },

    "column": {
      "type": "object",
      "required": ["label"],
      "additionalProperties": false,
      "properties": {
        "label": { "type": "string" },
        "role":  { "enum": ["category", "value", "x", "y", "size", "start", "end", "date"] },
        "unit":  { "type": "string" },
        "axis":  { "enum": ["left", "right"] },
        "type":  { "enum": ["bar", "line", "area"] }
      }
    }
  }
}

2.3 type 을 문자열 하나로 적는 이유#

대분류가 중분류의 namespace 다. Pyramid 는 Hierarchy 아래에만 있고 BarChart 는 MagnitudeRanking 아래에만 있다. 그래서 값을 Hierarchy.Pyramid 처럼 대분류를 앞에 붙여 적는다.

한 태그에 표기를 여럿 두지 않는다. 이 저장소는 같은 사고를 이미 한 번 겪었다. minor 이름만 담던 v2.0 어휘와 결합 표기를 섞었다가 조회가 조용히 0건이 났고, 그래서 semantic-taxonomy-v7.md 0절이 표기를 하나로 못 박았다. 필드를 둘로 나누면 세 번째 표기가 생긴다.

스키마는 결합값 45종 목록으로 조합을 강제한다. Hierarchy.BarChart 처럼 어긋난 조합은 목록에 없으므로 검증에서 걸린다.

대분류만 적는 것은 허용하지 않는다 (2026-08-29 개정). 종전에는 생성기가 중분류를 못 정하면 Hierarchy 만 적을 수 있었고, 검색이 그 안 중분류 전체를 후보로 봤다. 그래서 목록이 62값(대분류 21 + 결합 41)이었다. 지금은 41종뿐이다. 대분류는 그릴 수 있는 표현형이 아니라 결합값을 묶어 부르는 분류 축이므로 type 값이 될 수 없다. 근거와 영향은 6절 개정 상자에 있다. 중분류를 못 정하겠으면 종단 셋 중 하나를 고른다 — 그것이 이 규약이 정한 '못 고를 때의 답'이다(5절 3번).

저장 키는 구분자가 다르다. DB 와 comp-sign 이 쓰는 값은 Enumeration_List 처럼 밑줄이다(design_object_semantic_tags.tags, comp_signs_v3_4.semantic_tags). 질의 쪽 표기는 점이다. 검색 모듈이 받는 즉시 점을 밑줄로 바꾼다. 이 변환은 한 곳에만 있어야 한다.

2.4 params 를 열린 객체로 두는 이유#

카테고리마다 표현 선택 속성이 다르다(pyramid 의 apex, timeline 의 orientation, pictogram 의 icon). 41종의 속성을 전부 스키마 조건문으로 쓰면 스키마가 어휘에 묶여 카테고리가 늘 때마다 버전이 오른다.

params 의 허용 키와 기본값은 capability-table.md 2절이 카테고리별로 정한다. 스키마는 객체라는 것까지만 보고, 값 검증은 capability 가 한다. 어휘를 capability 상수로 모으기로 한 결정과 같은 방향이다.

2.5 matrix 의 행과 열#

첫 열이 범주 축이다. 나머지 열이 계열이다. 이 규약을 두지 않으면 생성기가 행과 열을 바꿔 적어도 검증이 통과한다.

column.role 이 각 열의 성격을 밝힌다. category 는 범주 축, value 는 수치, x 와 y 와 size 는 산점도와 버블, start 와 end 는 간트, date 는 달력이다. role 을 생략하면 첫 열은 category, 나머지는 value 로 본다.

표 계열(ListingTable, ComparisonTable)과 차트 계열이 같은 모양을 쓴다. 둘의 차이는 데이터가 아니라 표현이고, 표현은 type 이 정한다.

2.5.1 병합·계층 헤더가 있는 표: entities 경로 (2026-08-06 추가)#

계기. 강창석님 질의로 병합 셀이 있는 실사용 표가 matrix 성립 조건(2.1절)에 걸려 통째로 탈락하는 사례가 나왔다. Column.role(x, y, size, start, end, date)과 type(bar, line, area)이 순수 차트 어휘라, 2.5절의 "표와 차트가 같은 모양을 쓴다"는 전제는 평평한 격자에서만 성립한다. 병합·계층 헤더는 이 전제 밖이고, 지금 45종 어느 차트 카테고리도 그런 구조를 읽지 않는다(capability-table.md 2절). 그래서 matrix 를 확장하는 대신, ListingTable·ComparisonTable 에만 entities 를 대안 구조로 연다.

모양. columns(2.1절 StructureSlots, 헤더 정의)와 entities(행)를 함께 쓴다. entity.label 이 범주축(첫 열), entity.values[] 가 columns 와 위치로 대응하는 나머지 열이다. 부모-자식 행 병합(예: 상위 범주 하나가 하위 행 여럿을 거느리는 것)은 entity.children[] 로 적는다 — 새 필드가 아니라 0절과 6절이 계층에 이미 쓰기로 한 필드를 그대로 쓰는 것이다.

yaml
version: "0.5.6"
title: 분기별 프로젝트 성과
components:
  - type: AttributeComparison.ComparisonTable
    structure:
      columns:
        - { label: 항목 }
        - { label: 1Q }
        - { label: 2Q }
        - { label: 3Q }
      entities:
        - label: PROJECT 01
          children:
            - { label: 성장률, values: ["35%", "67%", "55%"] }
            - { label: 매출, values: [14000, 25000, 18000] }

적용 범위는 계층형 병합으로 한정한다. 위 모양은 상위 범주가 하위 행을 거느리는 경우(부모-자식 트리 하나로 표현되는 경우)를 담는다. 주축과 무관하게 특정 컬럼 하나만 값이 여러 행에 걸쳐 반복되는 경우(예: 강의 4개 행 중 "주말반" 컬럼만 2행씩 묶이는 것 — 축이 둘로 갈라지는 독립적 rowspan)는 트리 하나로 담기지 않는다. 이 경우는 지금 범위 밖이고 TODO.md B-29 에 열어 둔다.

fallback 은 matrix 성립 조건을 요구하는 카테고리로 가지 않는다. entities(+children) 로 표현된 표를 억지로 matrix.rows 로 접으면(다른 fallback 엣지가 하듯, capability-table.md 3.4절 P1/P2) 병합 의미가 조용히 사라진다. 그래서 이 경로로 채워진 ListingTable/ComparisonTable 은 Quadrant, GanttChart, Calendar 같은 matrix 필수 카테고리의 fallback 도착지가 되지 않는다. 대부분은 성립 조건(예: Quadrant 의 "항목마다 x, y")을 애초에 못 채워 5절 1번으로 걸러지지만, fallback 변환 규칙을 넓힐 때 이 방향으로 우회하지 않도록 여기 명문화해 둔다. 성립 조건과 이 경로가 capability 쪽에서 실제로 갈리는 지점은 capability-table.md 2.1절과 6절이다.

2.6 relation 어휘를 늘리는 기준#

구조로 적을 수 없는 제3의 내용을 가진 관계만 어휘로 만든다. 배열 순서, entity 내부 필드, children 으로 적히는 것은 어휘로 만들지 않는다. 이 기준으로 후보를 전수 판정한 결과가 아래다 (2026-08-02 확정, 원 논의는 archive/projection-model.md 7절).

관계 제3의 내용 판정
교집합 (intersects) 있다. 교집합 영역의 텍스트는 어느 entity 에도 속하지 않는다 유지
비교 (compares) 있다. 비교되는 항목들 사이를 설명하는 텍스트(비교 기준, 전환 문구) 추가
전후 변화 경계선. 화살표에 얹을 텍스트가 실사용 입력에서 관측되는지 미확인 보류. 관측되면 추가
문제와 해결, 원인, 결합 없다. entity 하나가 짝을 담거나(label 이 문제, items 가 해결), 허브형이라 type 에서 자명하다 기각

2.7 decoration 은 어디 붙고 어떻게 읽나#

structure 의 형제로 붙고 page 수준에도 같은 키를 연다. params 도 structure 도 아닌 이유는 둘이다. params 는 카테고리별 표현 선택인데(2.4절) 꾸밈구조는 카테고리와 무관하고, structure 는 내용인데 꾸밈구조는 내용이 아니다.

축은 둘이고 각각 RLSC role 군 하나에 대응한다. ornament 가 Element.Decoration·LayoutContainer.Decoration·Element.Separator·LayoutContainer.Separator, background 가 Element.Background 와 LayoutContainer.Background 다(Separator 흡수는 2026-09-05 정원석 확정, B-21). 타이틀과 디스크립션과 강조는 대치가 내용으로 채우는 노드라 축이 아니다. 앞은 structure 와 entity.components[] 가 담고 뒤는 entity.emphasis 가 담는다.

값은 셋이고 require 를 두지 않는다. prefer 는 그 role 노드를 가진 후보를 앞에 두고, avoid 는 뒤로 보내며, indifferent 는 보지 않는다이고 기본값이다. 꾸밈을 필수로 걸면 후보가 0이 되어 전부 List 로 강등되기 때문이다(capability-table.md 성립 조건과 겹치지 않는다).

값의 모양은 enum 이고, 수치 점수는 앞으로도 두지 않는다 (2026-08-29 확정, 정원석). 셋을 갈라 적는다.

모양 판정
enum (prefer·indifferent·avoid) 4단계와 5단계가 쓰는 것이 이것이다. 지금 규약에 있는 형태다
객체 (ornament: { ... } 처럼 구체적인 값을 담는 것) 나중 단계의 자리다. 기각된 것이 아니라 4·5단계 범위가 아닐 뿐이다. 그 단계에서 무엇을 담을지는 정하지 않았다
수치 점수 (ornament: 0.8 같은 가중치) 앞으로도 두지 않는다. 값에 점수를 매기는 것이 의미가 없다고 판단했다. 채점 코드를 건드리지 않기로 한 결정과 어긋나고 entity.weight(number)가 다른 뜻으로 이미 있다는 종전 근거도 그대로다

그래서 "prefer 가 몇 점인가"는 물음 자체가 성립하지 않는다. 남는 것은 점수가 아닌 형태로 검색이 이 선호를 어떻게 반영하는가, 그리고 fallback 보다 먼저 보는가 나중에 보는가다(../design/decoration-model.md 6절, 이석).

해석은 축마다 따로 하고 구체적인 쪽을 우선한다. component.decoration → page.decoration → 카테고리 기본값 → 시스템 전역 기본값 → indifferent 순이다. 층 전체를 갈아치우지 않고 축 단위로 병합하므로, page 가 두 축을 prefer 로 둔 상태에서 컴포넌트 하나만 background: avoid 로 덮을 수 있다.

indifferent 를 명시적 값으로 둔 이유가 이 병합에 있다. 위 층에서 물려받은 값을 취소하려면 '안 적음'과 구별되는 값이 필요하다.

yaml
version: "0.5.6"
title: 분기 실적
decoration:                    # page 수준. 아래 컴포넌트의 기본값이 된다
  ornament: prefer
  background: prefer
components:
  - type: Enumeration.List     # 물려받는다. 둘 다 prefer
    structure:
      entities: [ { label: 신규 계약, items: [12건] } ]
  - type: ProcessFlow.Steps
    decoration:
      background: indifferent  # 이 축만 되돌린다. ornament 는 prefer 그대로다
    structure:
      entities:
        - { label: 계약 검토 }
        - { label: 체결 }

1단계에서 생성기는 이 필드를 채우지 않는다. 스키마에 자리만 열고 값은 시스템 전역 기본값에서 온다. page 수준 relations 가 같은 방식으로 열려 있다. entity.components[] 는 B-20 으로 갈렸다 — PoC 는 채우고 실서비스는 아직 안 채운다. 소비할 주체인 요소 대치가 아직 동작하지 않고, 색인이 role 표식을 받는 경로도 없어서 지금 적어도 읽는 곳이 없다.

page layout 은 규약 범위 밖이다 (2026-08-29 확정, 정원석). page 수준에 있는 것은 relations 와 decoration 둘뿐이고, 둘 다 배치 지시가 아니다 — relations 는 컴포넌트 블록끼리의 의미 관계를 적을 자리이고 decoration 은 꾸밈 선호다. "컴포넌트 여럿을 한 페이지에 어떻게 놓는가"를 정하는 규약은 5단계 범위에도 없다. 5단계가 뽑는 것은 노드 태그와 카테고리 고유 속성(properties)과 구조 대응이지 배치가 아니다.

없는 것이 아니라 미룬 것이다. 배치를 다루자는 결정은 5단계 이후로 잡혀 있고, 그때까지 질의 쪽 스키마에 좌표나 그리드를 넣지 않는다. 지금 넣으면 색인 쪽(검색이 고른 실제 컴포넌트와 그것이 속한 템플릿이 배치를 정한다)과 두 벌로 갈린다.

그 전에 풀려야 할 것 둘이 먼저다. 복합 구조 fallback 에서 N3·N4 를 반복하면 최상위 컴포넌트 수가 늘어나는데 그 상한이 없고(../TODO.md B-32), 여러 조각으로 쪼갠 검색 결과를 한 페이지로 조립하는 오케스트레이션 코드도 없다(같은 문서 I-11). 개수와 조립이 정해지지 않은 상태에서 배치 규약을 먼저 쓸 수 없다.

설계 의도와 축을 둘로 좁힌 근거, 회의 용어 N / N+β 와의 관계는 ../design/decoration-model.md 에 있다. 안 쓰는 표기 다섯은 그 문서 5.5절이다.


3. 생성 메커니즘: LLM 이 YAML 을 직접 쓴다#

YAML 을 쓰는 이유는 추론 속도다. YAML 은 JSON 보다 구두점 토큰이 적어 같은 내용을 더 적은 생성 토큰으로 쓰고, 생성 토큰 수가 추론 시간을 정한다.

그래서 JSON 으로 생성한 뒤 YAML 로 직렬화하지 않는다. 그 방식은 속도 이득이 없다. v0.2 원문의 "structured output 으로 생성하고 직렬화만 YAML" 서술은 이 결정으로 대체됐다.

3.1 검증은 수동 절차다#

structured output 을 쓰지 않으므로 스키마 위반이 생성 단계에서 막히지 않는다. 그 보호를 검증 절차로 대신한다.

LLM 생성 (YAML)  →  js-yaml 파싱  →  JSON  →  id 할당  →  zod 검증  →  실패하면 재생성

entity.id 는 LLM 이 쓰지 않는다. 파싱 직후에 배열 순서대로 기계가 매긴다(3.2절). 그래서 id 는 스키마에서 optional 이고, 검증 시점에는 항상 채워져 있다.

3.2 id 를 어떻게 매기나#

LLM 이 지은 식별자는 두 가지를 해친다. 하나는 토큰이고(18종 실측 -5.9%), 다른 하나는 재현성이다. 내용이 같아도 agenda 와 a1 로 갈리면 ../TODO.md F-1 의 재현성 지표가 같은 결과를 불일치로 센다. id 패턴이 ASCII 만 받는 것도 한국어 입력에서는 매번 라틴 문자를 지어내야 한다는 뜻이라 실패 자리가 하나 늘어난다.

그래서 파싱 직후에 배열 순서대로 매긴다. 규칙은 둘이다.

  1. 한 컴포넌트 블록 안에서 entities 를 훑으며 e1, e2, ... 를 붙인다. children 은 부모 뒤에 이어서 센다.
  2. relations[].of 에 적힌 label 을 그 블록에서 찾아 매긴 id 로 바꾼다. 못 찾으면 검증 실패로 다루고 재생성한다.

배열 순서에서 나오므로 같은 입력이면 같은 id 가 나온다.

검증기는 zod 를 쓴다. 규약은 JSON Schema(2절)로 적고, 소비 코드가 TypeScript 이므로 zod 스키마를 생성물로 내보낸다. capability 상수(semanticTaxonomy.gen.ts)와 같은 방식이다. JSON Schema 와 정의용 타입과 zod 셋의 관계는 2절에 있다.

재생성 정책 검증 실패는 오류 메시지를 붙여 같은 모델에 다시 물린다. 반복 상한을 두고, 상한에 걸리면 그 블록을 생성 실패로 기록한다. 상한값은 실측으로 정한다.

계측이 생성기 요건이다. YAML 파싱이 실경로이므로 파싱 실패율과 스키마 위반율을 나눠 세고, 여기에 파싱은 됐는데 타입이 뒤바뀐 경우를 따로 센다. YAML 의 암묵적 타이핑 때문에 no 가 false 로, 1.0 이 숫자로 바뀌는 사고가 알려져 있다. 이 지표를 처음부터 넣는다 (../TODO.md F-2).

형식은 이 절차와 독립이다. 표면을 JSON 으로 바꾸고 검증기를 돌리는 절차는 어느 형식이든 같다. KDL 로 갈 경우의 표현 설계와 토큰 실측은 format-alternatives.md 에 있다.


4. capability 는 별도 문서다#

어떤 카테고리가 어떤 구조를 받을 수 있는지, 못 받을 때 어디로 물러나는지는 capability-table.md 에 45종 전량(49종 단어장에서 지도 전용 4종 제외, capability-table.md 범위 절)이 있다. 규약 본문에 두지 않는 이유는 둘이다.

첫째, 갱신 주기가 다르다. 스키마는 거의 안 바뀌고 capability 는 단어장과 색인 상태를 따라 바뀐다.

둘째, 소비자가 다르다. 스키마는 생성기와 검증기가 읽고, capability 는 검색 모듈이 읽는다.

capability 는 TypeScript 상수를 SSoT 로 두고 BE 소비용은 JSON 으로 내보낸다.


5. fallback 규칙#

  1. 카테고리의 성립 조건을 못 채우면 그 카테고리가 후보에서 빠진다. 성립 조건은 개수 범위와 내용 존재만 본다.
  2. 후보가 없으면 fallback 순서를 따라 내려간다. 상한을 두지 않는다. 근접도 순서만 지키면 몇 칸이든 허용하고, 그래도 없으면 마지막 fallback 으로 간다 (2026-08-17, capability-table.md 4절 개정 — 종전 "최대 두 번" 규칙 폐지)
  3. 마지막 fallback 은 셋이다. entities 가 2개 이상이면 Enumeration.List, 1개이고 여러 문장이면 TextBasedCommunication.LongFormTextLayout, 1개이고 한 문장이면 TextBasedCommunication.HighlightedMessage 다. 셋 다 개수 조건만 보므로 언제나 하나는 성립한다.
  4. 선택된 카테고리가 못 읽는 것은 버리지 않고 옮긴다. 어디로 옮기는지는 capability-table.md 3.4절이 규칙 일곱으로 정하고(2026-09-05, B-27로 P8 폐기), 3.5절이 엣지 130개 전량에 적용해 둔다. 표현 선택(params)만 버리고 내용은 items 로 간다.
  5. type 이 가리킨 카테고리의 성립 조건을 structure 가 못 채우면 구조가 우선이다. 그 카테고리는 1번대로 후보에서 빠지고 fallback 순서로 넘어간다.
  6. 질의가 중첩(entity.components[])이나 병치(최상위 components[] 여럿)로 노드를 여럿 가지면, 위 1번부터 5번과 별개로 어느 노드를 먼저 포기하는지를 정해야 한다. 순서는 다섯 단계(N1 전체 일치, N2 모양 보존 강등, N3 최상위 벗기기, N4 반복, N5 평탄화)이고 capability-table.md 3.6절이 SSoT 다 (2026-08-28 확정). 1절 "중첩은 평탄화된다"는 폐기되지 않고 N5, 즉 마지막 단계로 자리를 옮겼다.

5번이 발동했다는 것은 대개 생성 결함이다. 프롬프트가 "정의의 개수 조건과 dataset 요구를 채울 수 있는지 먼저 확인하고, 못 채우면 정의의 '못 쓸 때 대안' 순서를 따른다"고 지시하므로, 못 채우는 type 을 적은 것은 지시 위반이다. 그런데도 검증 오류로 두지 않는 근거가 셋이다. type 은 권고이고(6절) 최종 결정은 검색이 하며, 데이터가 entity.components[] 중첩 안에 적혀 있어 자손을 합치면 성립하는 경우가 있고(그때는 결함이 아니라 적힌 자리가 다른 것이다), 하드 실패시키면 재생성 루프를 돌고 그마저 실패하면 사용자에게 빈 결과가 간다. 대신 생성 품질 신호로 센다 — capabilityCheck.ts 가 warning 으로 세고(capabilityFallbackCount) strictCapability 를 켜면 검증 실패로 올린다. 어떤 카테고리로도 못 그리는 경우만 항상 error 다. 발생 빈도는 아직 실측이 없다.

반대로 fallback 도착지에서의 미충족은 결함이 아니다. 예를 들어 Gauge 에서 HighlightedMessage(정확히 1)로 내려갈 때 항목이 여럿이면 P8 로 접어 받는데, 이것은 강등 경로가 원래 그렇게 도는 것이다. type 을 아예 적지 않은 것은 여기(fallback 결함)가 아니라 검증 단계에서 스키마 위반으로 걸린다 — type 은 0.5.1 부터 필수다(0.1절·2절). 이 자리에 남아 있던 "optional" 표기는 0.5.0 시절의 잔존 문구였다.

빈 결과가 사용자에게 가지 않는다. 3번이 이를 보장한다. 별도의 실패 표시 정책을 두지 않는 근거다. 6번의 N5 도 3번으로 끝나므로 복합 구조에서도 같다.


6. type 이 하는 일 셋#

type 은 생성기가 고른 카테고리다. 검색은 이 값을 기본으로 따르고, 그릴 수 없을 때만 fallback 순서로 벗어난다. 하는 일이 셋이다.

2026-08-29 개정: type 은 필수다 (정원석). 종전에는 optional 이었고 근거는 "필수로 걸면 '카테고리를 못 고르겠다'가 곧 생성 실패가 된다" 였다. 뒤집는다.

근거는 이 단계의 존재 이유다. 1절 설계 원칙 첫째가 "생성 단계에서 최적 semantic category 를 정한다"이다. 어떤 시각 표현형으로 그릴지를 먼저 정해야 거기서부터 표현 방식이 따라 나온다. type 을 비우고 내보내는 것은 그 판단을 검색 쪽으로 미루는 것이고, 시각화 책임을 회피하는 것이다. 원칙 1과 optional 은 처음부터 서로 어긋나 있었고 이 개정이 그것을 맞춘다.

'적을 의무'만 바꾸고 '따를 의무'는 안 바꾼다. 필수가 된다고 type 이 지시가 되는 것은 아니다. 검색은 여전히 성립 조건과 색인 공급을 보고 fallback 으로 벗어날 수 있다(5절). 권고라는 성질은 그대로다.

못 고르겠는 경우의 답은 '비우기'가 아니라 '종단 셋 중 하나를 고르기'다. entities 가 2개 이상이면 Enumeration.List, 1개이고 여러 문장이면 TextBasedCommunication.LongFormTextLayout, 1개이고 한 문장이면 TextBasedCommunication.HighlightedMessage 다(5절 3번). 셋 다 개수 조건만 보므로 어떤 내용이든 하나는 성립한다. 고를 수 없는 입력은 없다.

깨지는 산출물이 없다. 골든셋 v1 의 컴포넌트 470개가 전부 이미 type 을 갖고 있고, PoC 프롬프트도 type 을 적으라고 지시하고 있었다. 이 개정은 실제로 지켜지던 것을 규약에 올리는 일이다.

버전은 올리지 않는다. required 를 좁히는 변경이지만 위 이유로 무효가 되는 기존 산출물이 없어서, v0.5 를 자리에서 고친다(2026-08-06 entities+columns 추가, 2026-08-17 fallback 상한 폐지와 같은 방식).

2026-08-29 개정 (2): 대분류 단독은 type 값이 아니다 (정원석). type 과 alternatives 는 결합값(대분류.중분류)만 받는다. 어휘에서 대분류 21값을 뺐고, 그래서 enum 이 62값에서 결합값만 남았다(2.1절 타입, 2.2절 JSON Schema, 2.3절 어휘).

근거는 대분류가 실제로 쓰이는 컴포넌트 유형이 아니라는 것이다. Hierarchy 로는 아무것도 그릴 수 없다 — 그릴 수 있는 것은 Hierarchy.HierarchicalTree 이거나 Hierarchy.Pyramid 다. 대분류는 결합값을 묶어 부르는 분류 축이지 표현형이 아니다. 대분류만 적는 것은 앞 개정이 막은 것과 같은 회피이고, 정도만 약하다.

대분류라는 개념 자체가 사라지는 것은 아니다. fallback 근접도 판정에서 "대분류 일치는 강한 신호"로 쓰고(capability-table.md 3.1절), 6절 셋째의 "대분류가 Hierarchy 인가" 신호도 그대로다. 값으로 못 쓸 뿐 축으로는 계속 쓴다.

깨지는 산출물이 없다. 골든셋 v1 의 컴포넌트 type 454개가 전부 결합값이고, 생성기 프롬프트도 결합값만 목록으로 준다(VOCAB_COMBINED). 2026-09-11 부터 생성기 프롬프트(V2)는 그 45종에서 generatable: false 9종을 뺀 36종만 싣는다 — enum 자체는 45종이다(1절). 대분류를 받아 그 안 중분류 전체로 펼치던 경로(fallback.ts 의 major-expansion)는 규약상 도달할 수 없는 경로가 된다.

버전은 0.5.1 → 0.5.2 다. 어휘를 좁히는 변경이지만 실제로 무효가 되는 산출물이 0건이므로 PATCH 다(0.1절 판정 기준).

첫째, 무엇으로 그릴지 정한다. 검색은 type 이 가리킨 카테고리의 후보를 먼저 찾는다. 성립 조건을 채우고 색인에 후보가 있으면 그대로 간다.

둘째, 구조가 똑같은 두 카테고리를 가른다. Hierarchy.HierarchicalTree(조직도)와 ScopeCoverage.NestedScope(포함 범위)는 성립 조건이 같다. 둘 다 children 만 있으면 성립한다. 구조만 봐서는 어느 쪽으로 그릴지 알 수 없고, type 이 그것을 정한다.

셋째, '계층이 이 내용의 핵심인가'를 type 으로 판정한다. 조직도를 들여쓴 리스트로 바꾸면 소속 관계가 사라지고, 단순 분류 목록을 그렇게 바꾸면 별 손해가 없다. 이 차이를 적을 필드를 따로 두지 않기로 했다 (TODO.md B-3). 대신 type 의 대분류가 Hierarchy 인지가 그 신호가 되고, 평탄화를 어디까지 허용할지는 capability-table.md 의 fallback 순서가 정한다.

2026-08-03 갱신 B-3 이 뒤집혔다. type 으로 판정한다는 위 문장은 유효하지만, 그것만으로 두지 않고 컴포넌트를 nested 구조로 두어 계층을 표현한다. 새 선언 필드를 만드는 것이 아니라 entity.children[] 과 entity.components[] 를 실제로 쓰는 것이다 (0절 상자, ../design/decoration-model.md 1절).

6.1 alternatives 는 왜 두는가#

적어도 되고 안 적어도 된다. 생성기가 안 채우면 없는 것이고, 그때는 검색이 capability-table.md 3절의 fallback 순서만 쓴다. 적혀 있으면 그 순서보다 먼저 본다.

둘을 함께 두는 이유는 보는 것이 다르기 때문이다.

fallback 순서 alternatives
어디 있나 capability 상수. 카테고리마다 미리 박혀 있다 생성물. 입력마다 다르다
무엇을 보나 카테고리 대 카테고리만 본다 그 입력의 내용을 본다
언제 정하나 규약을 쓸 때 한 번 생성할 때마다

fallback 순서는 정적이다. Gauge → BigNumber → List 는 게이지에 무엇이 담기든 같다. 안에 든 내용이 목표 대비 달성률인지 단순 수치인지 구분하지 않는다.

그런데 내용을 보면 더 좁힐 수 있다고 본다. 같은 Hierarchy.Pyramid 라도 층마다 크기 차이가 의미를 지니면 Progression.Staircase 가 낫고, 그냥 3단 분류면 Enumeration.List 로 가도 손해가 적다. 이 판단은 카테고리 이름만 봐서는 안 나오고 내용을 읽어야 나온다. 생성기는 그 내용을 이미 읽었으므로 적어 둘 수 있다.

이건 가정이고 아직 실측이 없다. 생성기가 고른 alternatives 가 정적 순서보다 실제로 나은지는 재 본 적이 없다. 그래서 optional 로 열어 두고 생성기 정책으로 켜고 끌 수 있게 했다.


7. 설계 제약: 기존 RLSC 를 변경 없이 mapping#

structure 는 기존 RLSC 노드를 수정하지 않고 그대로 투영받을 수 있는 형태여야 한다. RLSC 가 이미 가진 것(children, role, chart data)에 structure 의 슬롯이 얹히는 방향이다.

Visual Summary 기존 RLSC 요소 변경 필요
entities[] LayoutContainer children (VStack, HStack, Grid) 없음
entity.label Role.Element.Title 텍스트 노드 없음
entity.items[] Role.Element.Description 텍스트 노드 없음
entity.children[] 중첩 LayoutContainer children 없음
entity.components[] 중첩 LayoutContainer 안의 별도 컴포넌트 없음. 못 읽는 컴포넌트는 1절 "중첩은 평탄화된다"로 편다. PoC 는 이 칸을 채운다 (B-20)
entity.value 와 unit Chart data cell 또는 Highlight 텍스트 없음
relations VennDiagram, DisjointEulerDiagram 없음
dataset.matrix Chart data(2D), seriesAxis 없음
entity.speaker 와 author Role.Element.Marker, Title (슬롯 재사용) 없음

2단계와 3단계에서 RLSC 스키마는 손대지 않는다. node 단위로 RLSC 를 실제 확장하는 것은 4단계(I.I.S.) 소관이다.

RLSC 대응을 41종 전량으로 채우는 것은 남아 있다. v0.4 3절의 11행 표에서 이미 셋(big_number, quote, dialogue)이 전용 컨테이너 없음이었고, 늘어난 30종 중 상당수가 같은 상태로 보인다 (capability-table.md 7절).


8. YAML 예시#

8.1 단일 컴포넌트#

yaml
version: "0.5.6"
title: 효과적인 회의를 위한 3가지 원칙
components:
  - type: Enumeration.List
    structure:
      entities:
        - { label: 명확한 아젠다, items: [회의 전 안건 공유, 목표 한 문장 정의] }
        - { label: 시간 관리, items: [시작과 종료 시간 엄수, 항목별 타임박스] }
        - { label: 액션 아이템, items: [담당자 지정, 마감일 명시] }

컴포넌트가 하나여도 배열이다.

8.2 params 와 alternatives#

yaml
version: "0.5.6"
title: 매슬로 욕구 5단계
components:
  - type: Hierarchy.Pyramid
    params: { apex: last }
    alternatives:
      - Progression.Staircase
      - Enumeration.List
    structure:
      entities:
        - { label: 생리적 욕구 }
        - { label: 안전 욕구 }
        - { label: 사회적 욕구 }
        - { label: 존경 욕구 }
        - { label: 자아실현 }

apex 는 안 적어도 된다. 기본값이 last 다. alternatives 도 선택이고, 적으면 검색 모듈이 fallback 순서보다 먼저 참고한다.

ScopeCoverage.NestedScope 의 direction 은 다르다 — 기본값에 기대지 말고 반드시 판단해서 적는다 (2026-09-04 B-34).

yaml
version: "0.5.6"
title: 목표 시장 규모
components:
  - type: ScopeCoverage.NestedScope
    params: { direction: inward }
    structure:
      entities:
        - { label: 수익 시장, items: ["SOM 연 3,100억원"] }
        - { label: 유효 시장, items: [SAM 연 2.4조원] }
        - { label: 전체 시장, items: [TAM 연 12조원] }

apex 는 안 적으면 last(마지막 항목이 꼭대기)로 정해도 그림이 뒤집힐 뿐 의미가 바뀌지 않는다. direction 은 다르다 — 입력 글이 "가장 좁은 시장부터" 적었는지 "가장 넓은 시장부터" 적었는지에 따라 entities 배열 순서가 반대가 되고, direction 을 잘못 판단하면 안쪽과 바깥쪽이 뒤바뀐 틀린 그림이 나온다(원인이 결과처럼 보이는 것과 같은 종류의 오류다). 그래서 생성기는 direction 을 기본값에 맡기지 않고 입력 글의 서술 순서를 읽어 inward/outward 중 하나를 판단해 항상 명시적으로 적어야 한다. 색인 쪽(I.I.S. 속성 추출, T-7)도 같다 — 실제 컴포넌트를 볼 때 동심원의 어느 쪽이 라벨 순서상 첫 항목인지 실측해서 채운다. 값은 inward/outward 두 가지뿐이다(추측하기 쉬운 값이 아니라 열거형으로 못박는다 — 근거는 아래).

8.3 relations#

yaml
version: "0.5.6"
title: 장수기업 HR의 핵심 과제
components:
  - type: RelationalComparison.VennDiagram
    structure:
      entities:
        - { label: 전통, items: [유산, 정체성, 신뢰] }
        - { label: 변화, items: [혁신, 적응, 미래] }
      relations:
        - type: intersects
          of: [전통, 변화]
          desc: 두 요소를 조화롭게 관리하는 것이 장수기업 HR의 핵심

of 에는 entity 의 label 을 적는다. desc 는 어느 entity 에도 속하지 않는 내용이라 관계로 적는다. 이것이 관계 어휘를 만드는 유일한 기준이다.

8.4 measure#

yaml
version: "0.5.6"
title: 3분기 목표 달성률
components:
  - type: KPIKeyMetric.Gauge
    structure:
      entities:
        - { label: 목표 달성률, value: 65, unit: "%" }
      dataset:
        shape: measure
        value: 65
        unit: "%"
        min: 0
        max: 100
        target: 80
        thresholds:
          - { at: 50, state: warn, label: 주의 }
          - { at: 80, state: safe, label: 목표선 }

entities 의 헤드라인은 마지막 fallback 으로 갈 때 쓴다.

8.5 matrix#

yaml
version: "0.5.6"
title: 분기별 채널 매출 구성
components:
  - type: ProportionComposition.StackedBarChart
    structure:
      dataset:
        shape: matrix
        columns:
          - { label: 분기, role: category }
          - { label: 온라인, role: value, unit: 억원 }
          - { label: 오프라인, role: value, unit: 억원 }
          - { label: 파트너, role: value, unit: 억원 }
        rows:
          - [1분기, 12, 8, 3]
          - [2분기, 15, 7, 4]
          - [3분기, 18, 9, 5]
          - [4분기, 22, 10, 6]

같은 모양으로 표도 적는다. 표와 차트의 차이는 데이터가 아니라 type 이다.

yaml
version: "0.5.6"
title: 요금제 비교
components:
  - type: AttributeComparison.ComparisonTable
    structure:
      dataset:
        shape: matrix
        columns:
          - { label: 항목, role: category }
          - { label: 베이직 }
          - { label: 프로 }
        rows:
          - [월 요금, 9900원, 19900원]
          - [저장 용량, 10GB, 100GB]
          - [지원, 이메일, 전화와 이메일]

8.6 다중 컴포넌트#

yaml
version: "0.5.6"
title: 2025 사업 성과 요약
components:
  - type: KPIKeyMetric.BigNumber
    structure:
      entities:
        - { label: 총매출, value: 152, unit: 억원, emphasis: true }
        - { label: 전년 대비, value: 38, unit: "%" }

  - type: Enumeration.List
    structure:
      entities:
        - { label: 신규 시장 진출, items: [동남아 3개국] }
        - { label: 반복 구매 증가, items: [구독 전환율 상승] }

원소 수 상한은 두지 않는다. 실데이터에 다중 컴포넌트 페이지가 아직 없어 근거 있는 숫자를 정할 수 없다.

컴포넌트 블록의 id 도 생성기는 채우지 않는다. page 수준 relations 가 블록을 가리킬 자리로 예약만 해 두었다.

8.7 중첩 컴포넌트#

항목 하나가 그 안에 또 컴포넌트를 품는 모양이다. entity.components[] 가 그 자리이고, 스키마에는 v0.4 때부터 열려 있다. 이 칸은 title 바로 아래 최상위 components[](L2 병치)와 다른 자리다 — 최상위는 Visual Summary 의 컴포넌트 목록이고, 여기는 structure.entities[] 항목 안에 매달린 중첩이다.

B-20 (2026-09-02, 허재영). 어느 생성기가 이 칸을 채울지를 갈랐다. PoC 생성기(MOR-1910)는 L3 테스트 범위로 entity.components[] 를 채운다. 이미 프롬프트 기본값이 L3 다. 실서비스 Summary 생성기(허재영·황현진)는 아직 채우지 않는다 — 8.8절 L2 정책은 그대로다. 검색 오케스트레이션(I-11)과 이종중첩 풀 정책(D-7/D-8)은 이 결정의 범위가 아니다. 버전 자리는 기존 산출물을 무효로 만들지 않으므로 올리지 않는다(칸은 원래 optional).

yaml
version: "0.5.6"
title: 분기별 사업 현황
components:
  - type: Enumeration.List
    structure:
      entities:
        - label: 1분기
          items: [신규 계약 12건]
          components:
            - type: KPIKeyMetric.BigNumber
              structure:
                entities:
                  - { label: 매출, value: 12, unit: 억원 }
        - label: 2분기
          items: [신규 계약 15건]
          components:
            - type: KPIKeyMetric.BigNumber
              structure:
                entities:
                  - { label: 매출, value: 15, unit: 억원 }

중첩을 못 읽는 컴포넌트는 자식의 entities 를 부모 items 로 편다 (1절 "중첩은 평탄화된다"). 위 예시가 평탄화되면 1분기 항목의 items 가 [신규 계약 12건, 매출 12억원] 이 된다. 깊이가 얼마든 결국 리스트로 읽힌다.

평탄화 앞에 세 단계가 더 있다 (2026-08-28 추가). 위 예시를 그대로 담은 후보가 색인에 없으면, 먼저 모양을 고정한 채 노드마다 카테고리를 내리고(N2), 그다음 Enumeration.List 를 벗겨 KPIKeyMetric.BigNumber 조각을 최상위로 올려 따로 찾는다(N3). 평탄화(N5)는 그 뒤다. 다섯 단계와 규칙 다섯은 capability-table.md 3.6절이 정한다. 부모를 위부터 벗기는 이유는 질의가 작아질수록 후보 풀이 커지는 반면 자식부터 떼면 내용을 지불하고도 부모가 여전히 복합으로 남기 때문이다(그 절 3.6.3).

왜 entity 아래에 매다나. 원소끼리 중첩시키면 그 자식이 부모의 어느 슬롯에 들어가는지 적을 자리가 없다. entity 가 곧 슬롯이므로 거기 매달면 위치가 정해진다.

이종중첩 컴포넌트(heterogeneous nested component) 는 위 중첩 중에서도 자식들의 type 이 서로 다른 경우를 가리킨다(위 예시가 그 경우다: 부모 Enumeration.List, 자식 KPIKeyMetric.BigNumber). 4단계 boundary 태깅 PoC 준비 중 Slack에서 처음 논의됐고, 검색 풀에서는 제외하기로 확정했다(2026-08-11 체크인, ../TODO.md D-7). 이런 케이스에 내용대치를 하려면 기존 조합 컴포넌트와는 별개의 대치 로직이 필요한데, 대부분 조합 Fallback 만으로 해결될 것으로 예상돼 WSJF 가 낮다고 판단했기 때문이다.

Topological Relation (위상 관계) 이종중첩 컴포넌트 안에서 semantic category 가 서로 다른 부분들이 서로에 대해 시각적으로 어떤 공간 관계로 놓이는지로 나눈 분류다(Slack, 2026-08-13). entity.components[] 가 부모 슬롯에 자식을 매다는 스키마 표현 방식과는 다른 층이다 — 이 분류는 스키마가 그 관계를 어떻게 적는지가 아니라, 서로 다른 semantic category 의 시각 영역이 실제로 어떻게 배치돼 있는지를 본다. 또한 ../rlsc/design-topology-and-isomorphism.md가 정의하는 Topology System / Design Isomorphism 체계와도 다르다. 그 문서는 tree invariant 보존 여부로 두 구조를 비교하고, 여기서는 이미 이종으로 판정된 한 컴포넌트 안에서 서로 다른 semantic category 영역이 공간을 어떻게 나눠 쓰는지만 본다.

  • Containment(포함): 한 semantic category 의 시각 영역이 다른 semantic category 의 시각 영역 안에 완전히 들어간다. 위 1분기/2분기 예시(List 전체 영역 안에 분기마다 BigNumber 가 들어가 있는 모양, Enumeration.List 와 KPIKeyMetric.BigNumber)가 이 경우다.
  • Overlap(중첩): 서로 다른 semantic category 의 시각 영역이 부분적으로 겹친다.
  • Juxtaposition(병치): 서로 다른 semantic category 의 시각 영역이 겹치지 않고 나란히 놓인다. 2026-08-13 수량 산출에서 관측된 11건은 전부 이 경우였다(예: 좌측 BigNumber + 우측 List, 상단 List + 하단 VennDiagram).

Overlap 은 현재 RLSC 구조에서는 발생하지 않는다. RLSC 트리에서 한 node 는 하나의 부모 node 아래에만 들어가고, 2개 이상의 node 아래 동시에 들어가는 구조가 없다. 서로 다른 semantic category 영역이 겹치려면 그런 다중 소속이 전제돼야 하므로, 현재 데이터에서 관측 가능한 이종상태는 Containment 아니면 Juxtaposition 뿐이다. Overlap 은 위상 관계 분류 자체의 완결성을 위해 남겨 둔 범주이지, 지금 나오는 실제 사례는 아니다.

D-7의 검색 풀 제외 결정은 위상 관계와 무관하게 이종중첩 전체에 적용된다. 이 분류는 원인을 구분해 부르기 위한 것이지, 제외 범위를 셋 중 일부로 좁히자는 것이 아니다.

8.8 복잡도 레벨 셋 (complexity level)#

8.1 부터 8.7 까지가 보여 준 모양들은 Visual Summary 하나가 가질 수 있는 위상의 범위로 셋으로 묶인다. 그 범위를 가리키는 이름이 복잡도 레벨이다. 레벨은 산출물 한 건의 모양을 분류하는 이름이 아니라, 생성기가 낼 수 있도록 허용된 범위의 이름이다. 그래서 누적이다 — L1 ⊂ L2 ⊂ L3 이고, L3 를 켠 생성기가 컴포넌트 하나짜리 Visual Summary 를 내는 것은 정상이다.

여기서는 '질의'라고 부르지 않는다. 복잡도는 생성기 산출물(Visual Summary) 의 성질이고, 그것이 검색용 질의로 바뀌는 것은 나중이다(질의 변환기, query.ts). 이 문서에서 '질의 쪽'이라고 쓸 때는 색인 쪽과 대비하는 진영 이름이지 질의 변환기 산출물이 아니다.

레벨 이름 최상위 components[] entity.components[] 모양 예시
L1 단일 컴포넌트 (single component) 길이 1 안 쓴다 8.1 ~ 8.5
L2 병치 위상 (juxtaposition topology) 여럿 허용 안 쓴다 8.6
L3 전체 위상 (all topology) 여럿 허용 쓴다 8.7

레벨을 가르는 축은 하나다. 컴포넌트 경계를 넘는 계층(entity.components[])을 쓰느냐다. entity.children[](같은 컴포넌트 안 계층)은 어느 레벨에서나 쓴다 — 그것은 한 컴포넌트 내부의 내용 구조이지 위상이 아니다. L1 과 L2 를 가르는 것은 그다음 축인 최상위 원소 수다.

레벨마다 무엇이 더 필요한가.

레벨 fallback 이 무엇을 쓰나 그 레벨을 켜려면 무엇이 있어야 하나
L1 카테고리 강등 사다리(3.2~3.5절)만. 복합 순서는 쓸 일이 없다 없다. 지금 도는 것이 이것이다
L2 사다리가 최상위 원소마다 독립으로 돈다(3.6절). 벗길 부모가 없어 N1~N5 는 쓰지 않는다 조각마다 검색을 호출하고 결과를 한 페이지로 다시 조립하는 오케스트레이션(../TODO.md I-11). 색인의 후보 단위가 컴포넌트 하나이므로 한 후보가 최상위 원소 둘을 함께 담는 일이 없다
L3 N1~N5 전부와 규칙 여섯(3.6절) 위의 것 전부에 더해, 중첩 후보의 대치 로직(D-7 이 "기존 조합 컴포넌트와는 별개의 대치 로직이 필요하다"고 적은 그것), 그리고 N3·N4 반복으로 최상위 원소가 늘어날 때의 상한(B-32). 어느 생성기가 entity.components[] 를 채울지는 B-20 으로 갈렸다 (8.7절)

생성기가 둘이고 지금 레벨이 서로 다르다. 섞어 읽으면 안 된다.

생성기 지금 레벨 근거
Summary 생성기(실서비스) — 허재영·황현진 소관 L2 까지 B-20 이 이 생성기는 entity.components[] 를 아직 안 채운다고 정했다. 실사용 로그 10건 실행에서 입력 10건이 컴포넌트 15개를 냈으므로 L2 는 관측된다
PoC 생성기 — 정원석 소관(MOR-1910) 이미 L3 프롬프트(generator/prompts/system.md)에 ## 이종 컴포넌트 절이 있어 포함(containment)을 entity.components 로 적으라고 지시하고, 골든셋 v1 에 중첩 산출물이 실제로 들어 있다

그래서 "L3 는 자리만 열려 있다"는 말은 생성 쪽에는 더 이상 맞지 않는다. 비어 있는 것은 그다음 두 칸이다 — 조각마다 검색을 호출하고 결과를 조립하는 오케스트레이션(../TODO.md I-11)과 중첩 후보의 대치 로직(D-7)이다. 4단계 실서비스에서 어느 레벨까지 켤지는 정해진 바 없고, 생성기 정책과 I-11 이 함께 풀려야 정할 수 있다.

8.9 복잡도 레벨을 켜고 끄는 방법 (프롬프트 블록화)#

레벨을 정책으로 통제하려면 레벨을 올리는 지시가 프롬프트에서 통째로 떼어지고 붙어야 한다. 지시가 여기저기 흩어져 있으면 "L2 로 내려라"가 한 군데를 지우는 일이 아니라 문장을 골라내는 일이 되고, 그 순간 프롬프트 판본마다 실제 레벨이 달라진다.

지금 상태는 절반쯤 그렇게 되어 있다. PoC 프롬프트에서 위상 지시는 ## 이종 컴포넌트 한 절(포함·병치·판별)에 뭉쳐 있어 물리적으로는 이미 한 덩어리다. 다만 둘이 모자란다.

무엇 지금 필요한 것
블록 경계 ## 이종 컴포넌트 절에 모여 있으나, ## 컴포넌트 블록 절의 "entity.components 는 ... 포함 관계를 적을 때만 쓴다" 한 줄이 밖에 남아 있다 그 줄까지 블록 안으로 넣거나, 블록이 꺼지면 함께 꺼지도록 묶는다
레벨별 분리 포함(L3)과 병치(L2)가 한 절 안에 있다 레벨 경계로 갈라야 한다. L2 로 내릴 때 병치는 남기고 포함만 꺼야 하기 때문이다
토글 수단 fillPrompt 가 {{TOKEN}} 치환만 한다. 조건부 포함이 없다 레벨을 입력으로 받아 블록을 넣고 빼는 조립 지점

그래서 필요한 모양은 셋이다. 블록을 레벨 단위로 쪼개고(topology-juxtaposition.md, topology-containment.md 처럼), 프롬프트 조립이 복잡도 레벨을 인자로 받고, 조립 결과가 레벨과 어긋나지 않는지 검사한다. 마지막 것이 특히 중요하다 — 레벨을 L2 로 내렸는데 생성기가 중첩을 내면 검색 오케스트레이션이 없는 상태에서 조용히 실패한다. 검증 쪽에는 이미 판정 재료가 있다. entity.components[] 사용 여부만 보면 산출물의 실제 레벨이 나오므로, 선언한 레벨과 대조하는 검사는 validate.ts 계측에 한 줄로 붙는다. 아직 구현되지 않았고 이 절이 요구사항만 적는다.

L3 안에서 이종이면 실질적으로 N3 부터 시작한다. D-7 이 이종중첩 템플릿을 검색 풀에서 제외했으므로(전체 약 1.10%) N1 과 N2 는 후보가 0이다. 동종중첩(자식 type 이 부모와 같은 경우)은 D-7 밖이라 N1·N2 가 살아 있다. 되돌릴지는 D-8 로 열려 있다.

'병치'라는 말이 두 층에서 쓰인다. 섞으면 안 된다.

  • 질의 쪽 병치 (이 절의 L2): 최상위 components[] 에 원소가 여럿인 것. 컴포넌트 경계가 이미 갈려 있다.
  • 색인 쪽 Juxtaposition (8.7절 위상 관계): 이종중첩으로 판정된 한 컴포넌트 안에서 서로 다른 semantic category 의 시각 영역이 겹치지 않고 나란히 놓인 것.

방향이 반대다. 앞은 나눠서 찾는 쪽이고 뒤는 이미 하나로 뭉쳐 있는 것을 부르는 이름이다. L2 질의의 조각들이 각각 검색돼 한 페이지에 함께 놓이면 결과물의 겉모습은 색인 쪽 Juxtaposition 과 닮지만, 후보 단위가 다르므로 같은 것이 아니다.

레벨과 카테고리 강등 칸 수는 서로 다른 예산이다. L3 질의라고 해서 카테고리를 더 많이 내릴 수 있는 것도, 덜 내려야 하는 것도 아니다. N-R4 가 정한 것과 같은 구분이다.


9. 열린 결정#

TODO.md B절이 SSoT 다. 2026-08-02 기준 남은 것은 아래 셋이고, 셋 다 규약 확정을 막지 않는다.

# 무엇 왜 규약을 막지 않나
B-7 갭 3종(BigNumber, QuoteTestimonial, DialogueChat)을 투영으로만 둘지, RLSC 에 전용 Role 조합을 표준화할지 투영으로 이미 동작한다. 표준화는 개선이다
B-9 HierarchicalTree 의 children 을 Graph relations 로 바꾸는 규칙, NestedScope 와 DisjointEuler 의미 정합 4단계 I.I.S. 협의 사안이다
B-10 NestedScope 를 스마트 다이어그램 concentric_rectangles 로 볼지 그 타입은 jari 머신이 없어 지금 어느 쪽도 렌더되지 않는다

capability-table.md 8절의 확인 목록(실측 없는 개수 조건 35종, fallback 순서 두 칸, fallback 순서의 카테고리 선택)도 규약과 별개로 열려 있다.


10. 참고#

↑ 목차로 · 2부 머리로

5장Capability 테이블 45종 전량 정본성립 조건·선택 속성·fallback 순서

원문 spec/capability-table.md GitHub 에서 보기 64 KB 원문 제목 「Capability 테이블: 45종 전량」

이 문서의 역할 semantic category 45종 각각에 대해 성립 조건(못 채우면 후보에서 빠지는 것), 선택 내용과 속성(안 적어도 되는 것. 각각 entity 쪽 값과 params 쪽 표현 값이다), fallback 순서(그 종류가 색인에 없을 때 어디로 물러나는가)를 정한다. 노드가 여럿인 질의에서 어느 노드를 먼저 포기하는지는 3.6절이 정한다. schema-v0.4.md 3절의 capability 테이블 11행을 확장한 것이고, TODO.md B-14 가 말한 rlsc-capability 실물의 내용이다.

범위 v7 단어장 49종에서 지도 전용 4종(Distribution, Location, Highlight, Flow)을 뺀 45종이다. 지도는 지원하지 않는다 (2026-08-29 확정, 정원석) — 어휘에도 capability 에도 규약 enum 에도 넣지 않는다. B-24 의 "49종 확정" 표현은 이 결정으로 정정됐다. 페이지 역할 4종(Opening, TableOfContents, SectionDivider, Ending)은 B-24(2026-08-06 확정)에 따라 어휘에 포함해 이 표에도 넣었다 — 성립 조건은 C-4(페이지 역할 태그를 검색 풀에서 어떻게 다룰지) 확정을 기다리며 미정으로 뒀다가, 2026-08-27에 정본 시트 값(../plan/page-navigation-capability-draft.md)으로 채웠다. C-4 가 정하는 것은 그 종이 뽑혔을 때의 템플릿 공급이지 성립 조건이 아니라서 선행 조건이 아니었다. 지도 전용 4종은 지도 데이터가 있어야 성립해 structure 가 표현할 대상이 아니고, 당분간 summary 생성 대상에서도 뺀다(2026-08-17 스쿼드 확인).

오너 정원석 (MORDOR) · 상태 DRAFT · 최종 갱신 2026-09-11 (1절 생성 시점 쓰기 의무 명문화 — B-33 정본 반영, 규약 0.5.6. 10.1절 생성기 불허 9종 — 첫 출시 공급 제외 Gauge·RadarChart 추가, 재지정을 capability.json generatorRedirect 로. 2026-09-05 판은 B-36 generatable flag 확정, Opening 판정 위치조건 제거, 엣지 130개 동기화 — 10절·2.6절·3.5절)

관련 schema-v0.5.md 규약 · semantic-taxonomy-v7.md 단어장 · smart-diagram-data-model.md 6-1절 항목 수 실측 · ../plan/fallback-map-review-2026-08-13.md v2 반영 근거

아직 미확정인 것 (2026-08-17 3차). 없음 — 원저자(김수현·허재영) 확인 대기 항목이던 Funnel도 2026-08-17에 사용자 결정으로 닫혔다(6.2절). List·SWOT·HighlightedMessage·LongFormTextLayout·ProfileCard·TableOfContents 6행은 "구조 성립해도 색인 공급 부족·구조 상이 시 다음 후보로 진행 가능"(2.1절)이 이미 원칙으로 서 있어 확인 없이 반영했다. PictogramChart↔Gauge는 다시 뺐다(2026-09-05, B-30) — measure↔matrix projection 규칙은 만들지 않기로 확정했다(3.4절). PageNavigation 4종의 성립 조건은 2026-08-27에 정본 시트 값으로 채웠다(2.6절). Opening 의 "마지막 슬라이드" 겹침 우려(../plan/page-navigation-capability-draft.md 4.1절)는 같은 날 닫혔다 — 최신 정본 시트가 Opening/Ending 을 분리한다(허재영 확인). v7 우선순위(P1~P4)는 2026-08-17에 원본 시트에서 동기화 완료(semantic-taxonomy-v7.md 3절).

이 문서 훑기

절 무엇에 답하나
1 무엇을 선언하고, 개수 조건은 어디서 왔나
2 45종 각각의 성립 조건과 기본값(45종 전량 확정)
3 45종 각각이 어디로 물러나나. 3.0.1절이 그래프 정의, 3.4~3.5절에 엣지 130개의 projection, 3.6절이 중첩과 병치처럼 노드가 여럿인 질의의 순서(2026-08-28 확정)
4 몇 번까지 따라가나
5 이 표를 코드 어디에 두나
6 어느 열린 결정이 닫혔나
7, 8 아직 근거가 실측이 아닌 것
10 45종 각각이 Native Complex Element 없이도 검색·대치되는지 (제안, 확인 대기)

1. 세 가지를 선언한다#

2026-08-02 결정이다. 요구 등급을 필수와 선호와 무시 세 값으로 나누는 대신, 성립 조건은 최소로 줄이고 나머지는 기본값을 가진 선택 값으로 옮긴다. 채점 코드는 건드리지 않는다.

선언 무엇인가 못 채우면
성립 조건 이게 없으면 그 종류로 배치가 불가능한 것 그 종류가 후보에서 빠진다
선택 내용 (optional content) 안 적어도 되는 entity 쪽 값과 관계 그 자리를 비우고 그린다
속성 (property, params) 안 적어도 되는 표현 값. 규약이 기본값을 정해 둔다 기본값으로 그린다
fallback 순서 그 종류가 색인에 없을 때 물러날 곳 fallback 순서를 따라 내려가고 마지막은 항상 List

여기 적히지 않은 것은 전부 무시한다. 규약 1절 "선언되지 않은 것은 무시한다"가 기본 동작이라 따로 선언하지 않는다.

무시는 읽는 쪽의 기본 동작이지 쓰는 쪽의 허가가 아니다 — 생성 시점에는 읽는다고 선언한 것만 쓴다 (B-33, 2026-09-05 정원석 확정). Summary 생성기는 2절이 그 카테고리의 선택 내용으로 선언하지 않은 내용 필드에 값을 담지 않는다. 담을 것이 있으면 그 entity 의 items 로 적는다(3.4절 P3 와 같은 방향). 생성 경로에는 P3 가 돌지 않는다 — 3.4절은 "fallback 은 카테고리를 바꾸는 일이므로" 출발→도착 엣지에서 옮길 것을 정하는 규칙이라, 생성기가 처음부터 그 카테고리로 쓰면 옮겨 줄 주체가 없어 내용이 그대로 소멸한다(HierarchicalTree 를 Pyramid 로 재지정한 실측에서 하위 9개가 사라졌다 — iui #1023). B-33 결정문이 "안 읽는 내용 필드는 P3 으로 보존돼 소멸하지 않는다"고 적은 것은 대치 경로 한정이다. 이 의무는 생성기 스코프에만 적용한다 — 색인 검색·대치 경로는 B-33 결정대로 그대로 두고("빈 결과보다 낫다"), 이미 만들어진 산출물을 소급해 무효로 보지 않는다.

기본값 규칙#

기본값은 속성에만 준다. 내용에는 주지 않는다.

apex(어느 쪽이 꼭대기인가)는 속성이라 기본값을 줘도 된다. value(숫자가 얼마인가)는 내용이라 기본값을 주면 없는 데이터를 지어내는 것이므로 성립 조건에 남긴다.

개수 조건의 출처#

실측 표시가 붙은 값은 jari 0.12.0 의 LAYOUT_CONTENT_CONTRACTS 에서 온 것이다(smart-diagram-data-model.md 6-1절). 배치 머신이 실제로 받는 항목 수 상하한이라 근거가 확실하다.

표시가 없는 값은 단어장 정의문에서 유도한 설계 판단이다. 예를 들어 Pyramid 의 최소 3은 정의문 "3단 이상 수평 층"에서, Versus 의 정확히 2는 "2개의 대상을 서로 대응시켜"에서 왔다. 상한이 없는 것은 상한을 두지 않았다는 뜻이다. 8절에 확인이 필요한 것을 모아 두었다.


2. 성립 조건과 선택 내용, 속성#

이 문서의 카테고리 이름은 밑줄 표기다. 이 표는 색인과 검색이 읽는 쪽이고, 그쪽 저장 키가 Enumeration_List 형식이다. 생성물(질의 쪽)의 표기는 점이다(Enumeration.List, 규약 2.3절). 같은 카테고리를 가리키며, 검색 모듈이 받는 즉시 점을 밑줄로 바꾼다.

선택 내용과 속성을 두 열로 갈랐다 (2026-08-23). 종전에는 "선택 속성" 한 열에 둘을 섞어 담았고, 그래서 fallback 규칙(3.4절)을 기계로 계산할 수 없었다.

열 무엇 fallback 때
선택 내용 (optional content) entity 가 담는 값과 항목 사이의 관계. period, weight, speaker, author, source, avatar, organization, children 과 compares, intersects 관계 옮긴다 (P3, P7)
속성 (property, params) 내용과 무관한 표현 값. orientation, apex, icon, header, sort 같은 것 버린다 (P2)

두 열의 정본은 schema/capability.json 의 reads.content / reads.relations 와 reads.params 다. 이 표는 그것을 사람이 읽는 모양으로 옮긴 것이고, 두 열의 값이 그쪽과 어긋나면 JSON 을 따른다.

n 은 entities 개수다. dataset 열의 값은 2026-08-02 에 확정한 2모양(measure, matrix)이고, 빈 칸은 dataset 을 쓰지 않는다는 뜻이다. 두 열 모두 빈 칸이면 그 카테고리는 선택 값이 하나도 없다는 뜻이다.

개수 상한(n 이하)은 생성기·질의 쪽 제약이지, I.I.S.가 실제 디자인을 인식하는 쪽 제약이 아니다 (2026-09-05, B-27 병행 확정). Timeline의 "2 이상 15 이하" 같은 상한은 Summary 생성기가 이 카테고리로 요청해도 되는 실용적 범위(검색 풀 다양성에 맞춘 것)를 정한 것이다. I.I.S.는 이미 존재하는 실제 디자인의 구조를 있는 그대로 인식·태깅하므로, 실제 Timeline 디자인이 20단계짜리여도 그 개수 때문에 "이건 Timeline이 아니다"로 재분류하지 않는다. 상한은 질의(생성물)의 성립 조건 판정과 그에 따른 fallback 사다리(3절)에만 적용된다.

2.1 나열과 비교#

# 카테고리 개수 내용 조건 dataset 선택 내용 (optional content) 속성 (property, params)
1 Enumeration_List 1 이상 없음 items (없으면 1단 나열)
2 Enumeration_ListingTable 행 2 이상 matrix 또는 entities+columns (2026-08-06 추가) matrix header (첫 행이 머리글)
3 AttributeComparison_Versus 정확히 2 없음 compares 관계 (없으면 가운데를 비움)
4 AttributeComparison_ComparisonTable 열 2 이상 matrix 또는 entities+columns (2026-08-06 추가) matrix header (첫 행이 머리글), axis (첫 열이 기준 축)
5 RelationalComparison_SWOT 정확히 4 없음 order (강점, 약점, 기회, 위협 순)
6 RelationalComparison_VennDiagram 2 이상 3 이하 실측 없음 intersects 관계 (없으면 교집합 영역을 비움)
7 RelationalComparison_Quadrant 1 이상 matrix 필수 (항목마다 x, y) matrix axisLabels (없으면 축 이름을 비움)
8 Transformation_Transformation 정확히 2 없음 direction (forward, 앞이 전)

#2, #4 의 entities+columns 경로 (2026-08-06 추가). 병합·계층 헤더가 있는 표는 matrix 로 못 채운다 — Column.role/axis/type 이 차트 어휘라 병합을 표현할 자리가 없다. 이 둘만 entities(행, entity.children[] 로 상위 범주가 하위 행을 거느리는 병합을 적는다)와 columns(헤더)의 조합을 대안으로 받는다. 모양과 범위(계층형 병합만, 축이 갈라지는 독립적 rowspan은 범위 밖)는 schema-v0.5.md 2.5.1절, 계기는 Slack. 이 경로로 채워진 컴포넌트는 3.2절 fallback에서 matrix 필수 카테고리(Quadrant, GanttChart, Calendar)로 가지 않는다 — 대부분 그 카테고리의 성립 조건 자체를 못 채우지만, projection 규칙(3.4절)이 entities 를 matrix.rows 로 접는 식으로 우회하지 않도록 여기 못박아 둔다.

2.2 순서와 흐름#

# 카테고리 개수 내용 조건 dataset 선택 내용 (optional content) 속성 (property, params)
9 ProcessFlow_Steps 2 이상 없음 sequence (없으면 배열 순서), marker (number)
10 TimelineSchedule_Timeline 2 이상 15 이하 실측 없음 period (없으면 라벨만 표시) orientation (horizontal)
11 Progression_Staircase 2 이상 8 이하 실측 없음 direction (ascending)
12 Cycle_Cycle 2 이상 8 이하 실측 없음 center (없으면 가운데를 비움), rotation (clockwise)
13 LogicFlow_FlowChart 2 이상 없음 분기 라벨 (없으면 연결선만)
14 Conversion_Funnel 2 이상 없음 matrix (선택) conversionRate (없으면 비율 표기 생략)
15 TimelineSchedule_GanttChart 행 1 이상 matrix 필수 (작업마다 시작과 끝) matrix unit (month)
16 TimelineSchedule_Calendar 1 이상 matrix 필수 (날짜와 일정) matrix weekStart (Mon)

2.3 계층과 범위#

# 카테고리 개수 내용 조건 dataset 선택 내용 (optional content) 속성 (property, params)
17 Hierarchy_HierarchicalTree 1 이상 실측 children 필수 children (깊이 상한 없음) orientation (vertical)
18 Hierarchy_Pyramid 3 이상 없음 weight (없으면 층 높이 균등) apex (last)
19 ScopeCoverage_NestedScope 2 이상 없음 direction (inward, outward. 기본 inward — 배열 첫 항목이 안쪽), shape (circle)
20 ReasoningStructure_Fishbone 2 이상 없음 children (없으면 원인을 1단으로)
21 ReasoningStructure_CombinedResult 2 이상 없음 결과 라벨 (없으면 결과 칸을 비움)
22 ReasoningStructure_ProblemSolution 1 이상 없음 쌍 표기 (없으면 label 이 문제, items 가 해결)

NestedScope 는 children 을 쓰지 않는다 (2026-09-04, ../TODO.md B-34 제안, 정원석 — 허재영 확인 대기). 2026-09-02 초안은 뿌리 1개짜리를 성립으로 보고 개수를 1 이상으로 내리며 표준 인코딩을 사슬(바깥 범위 label → 안쪽 범위 children)로 정했었다. 하지만 semantic-taxonomy-v7.md 145행 자신이 "NestedScope 와 HierarchicalTree 를 가르는 것은 의미가 아니라 동심원으로 그리는지다. 단일 사슬 포함 관계는 가지가 하나뿐인 트리와 구조적으로 같다"고 적어, 사슬 인코딩은 두 종을 데이터상 같은 모양으로 만들어 버린다. NestedScope 의 전형인 TAM/SAM/SOM 은 가변 깊이 트리가 아니라 정해진 개수의 동심원에 라벨 하나씩, 안쪽→바깥쪽 순서만 있으면 되는 구조다. 그래서 개수를 2 이상(원래 기준)으로 되돌리고 내용 조건을 없앤다. 순서는 별도 필드로 담지 않고 entities 배열 순서 그 자체로 표현한다 — Steps·Timeline·Staircase·Cycle 등 다른 순서형 카테고리도 전부 이 방식이고, SWOT 이 이미 order 를 다른 뜻(사분면 배치)의 속성으로 쓰고 있어 이름도 겹친다. 필요한 건 어느 쪽 끝이 안쪽인지 뒤집는 속성 direction(inward/outward, 기본 inward) 뿐이다 — 이 점에서 Pyramid(18행, weight/apex)보다 Staircase(11행, 내용 없음, direction 만)에 더 가깝다. 3.3절의 Pyramid↔NestedScope 짝은 유지하되, fallback 규칙(3.6절)의 children 언급은 배열 그대로 전달로 정정한다. 플랫 배열이 배타 조건과도 맞는다. golden-set-production-notes.md 2절이 골든셋 프롬프트 튜닝에서 역산한 진짜 배타 조건은 서술 순서(안→밖 vs 밖→안)가 아니라 갈라짐 여부였다 — HierarchicalTree는 한 상위가 하위 여럿을 거느려 가지가 갈라지고, NestedScope는 단일 사슬이다. children 트리는 실수로 가지를 칠 수 있지만, entities 배열은 애초에 분기가 불가능한 구조라 이 조건을 데이터 모양으로 강제한다.

2.4 수치#

# 카테고리 개수 내용 조건 dataset 선택 내용 (optional content) 속성 (property, params)
23 KPIKeyMetric_BigNumber 1 이상 value 필수 measure (선택) unit, delta, emphasis (없으면 첫 항목 강조)
24 KPIKeyMetric_Gauge 1 이상 measure.value 필수 measure target, max (100), min (0), thresholds, unit
25 MagnitudeRanking_BarChart 계열 1 이상 matrix 필수 matrix orientation (vertical), sort (입력 순서)
26 ProportionComposition_StackedBarChart 계열 2 이상 matrix 필수 matrix orientation (vertical), normalize (false 고정. true 는 렌더러 미지원으로 스펙에서 뺐다 — 2026-08-17, ../plan/fallback-map-review-2026-08-13.md 7절)
27 ProportionComposition_PieChart 조각 2 이상 matrix 필수 matrix startAngle (0), labelPosition (outside)
28 ProportionComposition_DonutChart 조각 2 이상 matrix 필수 matrix centerLabel (없으면 가운데를 비움), startAngle (0), labelPosition (outside)
29 ProportionComposition_TreeMap 1 이상 matrix 필수 matrix children (없으면 1단)
30 ProportionComposition_PictogramChart 1 이상 matrix 필수 matrix icon (없으면 기본 아이콘), unitPerIcon (1)
31 Trend_LineAreaChart 계열 1 이상, 축 2 이상 matrix 필수 matrix fill (false, 참이면 area)
32 Trend_ComboChart 계열 2 이상 matrix 필수 + 계열 type 2종 이상 또는 axis 2종 이상 matrix 계열별 type (bar), axis (left)
33 AttributeComparison_RadarChart 축 3 이상 matrix 필수 matrix maxValue (계열 최대값)
34 RelationalComparison_BubbleChart 1 이상 matrix 필수 (항목마다 x, y, 크기) matrix sizeScale (linear)

2.5 텍스트와 사람#

# 카테고리 개수 내용 조건 dataset 선택 내용 (optional content) 속성 (property, params)
35 TextBasedCommunication_HighlightedMessage 정확히 1 없음 items (없으면 문장만)
36 TextBasedCommunication_LongFormTextLayout 1 이상 없음 title (없으면 본문만)
37 QnACard_QnACard 1 이상 없음 쌍 표기 (없으면 label 이 질문, items 가 답변)
38 Utterance_DialogueChat 2 이상 없음 speaker (없으면 번갈아 배치)
39 Utterance_QuoteTestimonial 1 이상 없음 author, source (없으면 출처 없이 인용문만)
40 PortraitProfile_ProfileCard 1 이상 없음 avatar, organization (없으면 해당 칸 생략)
41 PortraitProfile_ResumeCV 1 이상 없음 period (없으면 기간 생략), children (없으면 1단)

2.6 페이지 역할#

# 카테고리 개수 내용 조건 dataset 선택 내용 (optional content) 속성 (property, params)
42 PageNavigation_Opening 정확히 1 없음 (2026-09-10 완화) 부제목, 발표자·날짜·소속 (items)
43 PageNavigation_TableOfContents 2 이상 흐름(화살표)·단계(계단)가 없다 (2026-09-10 좁힘) 섹션마다 한 줄 설명
44 PageNavigation_SectionDivider 정확히 1 다음 한 섹션의 번호와 제목. 본문 콘텐츠가 없다 한 줄 설명
45 PageNavigation_Ending 정확히 1 종료를 알리는 단일 메시지. 별도 콘텐츠가 없다

2.6절의 내용 조건은 배치 가능성만 남긴다 (2026-09-10, 허재영 판단 · 정원석 확인 대기). 1절이 성립 조건을 "이게 없으면 그 종류로 배치가 불가능한 것" 으로 정의한다. 그 기준으로 보면 "번호와 제목을 나열한다"(43행), "다음 한 섹션의 번호와 제목"(44행), "종료를 알리는 단일 메시지"(45행)는 종을 설명하는 문장이지 배치 가능성 조건이 아니다. 기계가 보는 것은 43행의 "흐름·단계가 없다"(relations 없음)와 44·45행의 "본문 콘텐츠가 없다"(dataset·children·components 없음)뿐이고, 설명 문장은 남겨 두되 성립 판정에 넣지 않는다. 어느 종인지는 질의가 적은 type 과 I.I.S. 태깅이 정한다.

2026-08-27에 정본 시트 값으로 채웠다. 출처와 근거는 ../plan/page-navigation-capability-draft.md — 원본 시트 레이아웃 텍사노미 신규 탭에 성립 조건이 이미 있었고, C-4(검색 풀 정책)가 정하는 것은 뽑힌 뒤의 템플릿 공급이지 성립 조건이 아니라서 선행 조건에서 풀었다. Opening·SectionDivider·Ending 이 정확히 1인 것은 시트가 셋 다 "단일 슬라이드 구조 / 단일 메시지" 로 적기 때문이고, TableOfContents 만 2 이상인 것은 항목이 하나면 목차가 아니라 간지이기 때문이다. dataset 은 넷 다 없다 — 수치 데이터를 담는 종이 아니다. 단 structure 자체는 필요하다: 규약 v0.5 의 structureSchema 가 entities 나 dataset 을 요구하므로, 이 4종은 entities 로 적는다(Opening 이면 제목이 label 이고 발표자·날짜·소속은 그 entity 의 items 다 — 42행 개수 "정확히 1" 과 시트의 "단일 슬라이드 구조" 를 살린 표기다. 2026-09-10 정정, 허재영 판단 · 정원석 확인 대기. 같은 날 그 items 는 성립 조건에서 선택 내용으로 내려갔다. TableOfContents 면 섹션마다 entity 하나). fallback 순서(3.2절)는 2026-08-17에 이미 유형별로 갈라 뒀다 — TableOfContents 는 List → ListingTable, 나머지 셋은 HighlightedMessage → List. Opening은 위치가 아니라 내용으로만 판정한다 (2026-09-05 정원석 확정). 파이프라인이 페이지를 독립적으로 처리해 추출·생성·검색·대치 어느 구간도 그 페이지가 몇 번째인지 알 수 있는 경로가 없다 — "맨 앞장 전용"은 시스템이 보장하거나 검증할 수 있는 조건이 아니다. 42번 행이 실제 판정 전부이고, 실사용에서 대개 첫 장에 나오는 것은 관습이지 시스템이 위치를 강제해서가 아니다. 2026-09-10 에 42번 행의 내용 조건을 없앴다 (허재영 판단, 정원석 확인 대기). 종전에는 "제목이 있고 발표자·날짜·소속 중 하나 이상이 함께 온다" 를 요구했다. 1절이 성립 조건을 "이게 없으면 그 종류로 배치가 불가능한 것" 으로 정의하는데, 제목만 있는 표지도 표지 레이아웃에 배치된다. 그래서 그 요구는 배치 가능성 조건이 아니라 종을 가르는 조건이었고, 발표자·날짜·소속은 선택 내용으로 내렸다. 그 결과 Opening·Ending·SectionDivider 는 구조로 구분되지 않는다 — 셋 다 entity 하나에 본문이 없다. 어느 종인지는 질의가 적은 type 과 I.I.S. 태깅이 정하고, 성립 판정은 배치 가능성만 본다.

성립 조건이 내용을 요구하는 것은 넷에 페이지 역할 4종이 더해졌다. BigNumber 와 Gauge 의 수치, HierarchicalTree 의 children, ComboChart 의 혼합 조건, 그리고 2.6절의 내용 조건 3행이다(Opening 은 2026-09-10 에 빠졌다). (NestedScope 는 2026-09-04 부로 children 요구가 빠져 다섯에서 넷으로 줄었다 — 2.3절.) 나머지는 개수 조건과 matrix 유무만 본다. 옛 테이블에서 필수였던 apex, author, speaker, intersects 는 전부 기본값을 가진 선택 값으로 내려갔다. 그중 apex 는 속성 열로, 나머지 셋은 선택 내용 열로 간다.


3. fallback 순서#

그 종류의 후보가 색인에 없을 때 어디로 물러나는지다. fallback 을 한 번 따라갈 때마다 무엇을 잃는지 함께 적는다.

3.5절까지는 노드 하나를 다룬다. 질의가 중첩이나 병치로 노드를 여럿 가질 때 어느 노드를 먼저 포기하는지는 3.6절이 정한다.

3.0.1 fallback 은 그래프다 (2026-08-04 확정)#

3.2절의 각 행은 그 카테고리 하나의 후속 우선순위 목록이고, 검색 모듈은 자기 행만 왼쪽부터 따라간다. 다른 카테고리의 행을 이어 붙이지 않는다.

AttributeComparison_Versus 행이 'ComparisonTable → List' 인 것은 'Versus 가 안 되면 ComparisonTable, 그것도 안 되면 List' 라는 뜻이다. ComparisonTable 의 fallback 이 List 라는 뜻이 아니다. ComparisonTable 자신의 fallback 은 자기 행이 정하고 'ListingTable → List' 다. 그래서 Versus 에서 출발한 대치는 ListingTable 을 거치지 않는다.

이 정의가 없으면 구현이 갈린다. 나가는 자리가 둘 이상인 카테고리가 34종이고, 행을 이어 붙여 읽으면 그중 21종에서 '어느 쪽이 먼저인가'가 정해지지 않는다. 예를 들어 ComparisonTable 은 자기 행이 ListingTable 을 먼저 두는데, Versus 행을 이어 붙이면 List 가 먼저인 것처럼 읽힌다.

3.5절 엣지 표는 이 정의대로 계산했다. 엣지 하나는 '출발 카테고리의 자기 행에 적힌 도착' 이다. 그래서 엣지 수가 행마다의 목록 길이 합인 75개다. 행을 경로로 읽어 연속쌍을 뽑으면 51개가 나오는데, 그 51개에는 위 Versus 예시처럼 어느 행에도 적혀 있지 않은 엣지가 섞인다.

3.0 마지막 fallback 은 셋이고 내용 모양이 고른다#

아래 표는 마지막 자리를 Enumeration_List 로 적었지만, 실제 마지막 fallback 은 셋이고 그중 하나를 내용 모양이 고른다 (2026-08-02 정원석). 규약 1절 "어떤 구조든 리스트로 읽힌다"는 나열이 가능하다까지만 보장하고, 나열이 언제나 최선의 바닥이라고 말하지는 않는다. 긴 문단 하나를 글머리표 목록으로 그리면 오히려 어색하다.

마지막 fallback 언제 고르나
Enumeration_List entities 가 2개 이상일 때. 기본값이다
TextBasedCommunication_LongFormTextLayout entities 가 1개이고 본문이 여러 문장일 때
TextBasedCommunication_HighlightedMessage entities 가 1개이고 한 문장으로 끝날 때

셋 다 성립 조건이 개수 조건 하나뿐이어서 언제나 하나는 성립한다. 그래서 fallback 순서는 반드시 끝난다.

이 규칙이 TODO.md C-5 의 답이기도 하다. '맞는 컴포넌트가 없으면 무엇을 보여줄지'는 별도 정책이 아니라 fallback 순서가 처리한다. 마지막 fallback 셋 중 하나가 항상 나오므로 빈 결과를 사용자에게 보여줄 일이 없다.

3.1 차트 계열은 BarChart 와 LineAreaChart 를 오간다 (2026-08-17 갱신)#

차트 아홉 종 대부분이 MagnitudeRanking_BarChart 나 Trend_LineAreaChart 를 거쳐 AttributeComparison_ComparisonTable 로, 최종적으로 List 로 간다. 어떤 matrix 든 막대나 선으로는 그릴 수 있고, BarChart 가 차트 중 색인이 가장 두껍기 때문이다(색인 236건, 질의 120건으로 차트 계열 1위). v2 는 BarChart 와 LineAreaChart 를 서로의 첫 후보로 두어(3.3절) 상한 폐지(4절) 이후에도 두 후보가 함께 막히는 경우를 줄였다.

PieChart ─┐
DonutChart ┤
TreeMap    ┤
Pictogram  ├→ BarChart ⇄ LineAreaChart → ComparisonTable → List
StackedBar ┤
Combo      ┤
Radar      ┤
Bubble    ─┘

3.2 fallback 순서 전량#

이 열은 그 카테고리 하나의 후속 우선순위 목록이다 (3.0.1절). 왼쪽부터 본다. 2026-08-17에 3차에 걸쳐 전량 확정했다 — 원저자 확인 대기였던 Funnel(6.2절)도 사용자 결정으로 닫혔다.

카테고리 후속 우선순위 한 번 따라갈 때 잃는 것
Enumeration_List ListingTable 행과 열의 대응. List 자체는 entities 1개 이상이면 구조적으로 항상 성립하지만(3.0절), 색인에 List 태그 후보가 없으면(2.1절 "색인 공급 없음") ListingTable로 물러난다
Enumeration_ListingTable ComparisonTable → List 행과 열의 대응
AttributeComparison_Versus ComparisonTable → List 좌우 대비의 시각적 강조
AttributeComparison_ComparisonTable ListingTable → Versus → List 기준 축에 따른 비교 구도. Versus(정확히 2)를 거치면 다시 List로 접힐 수 있어 안전망으로 뒀다
AttributeComparison_RadarChart BarChart → LineAreaChart → ComparisonTable → List 다축 프로파일의 형태. ComparisonTable은 같은 AttributeComparison 대분류라 열 2개 이상이면 대부분 여기서 끝난다
RelationalComparison_SWOT List → ListingTable 내부와 외부, 긍정과 부정의 2축 배치. Quadrant·ComparisonTable 경유 대신 List를 1순위로 확정했다(원저자 판단 채택)
RelationalComparison_VennDiagram Versus → ComparisonTable → List 교집합 영역. Versus는 정확히 2가 조건이라 ComparisonTable 경유를 안전망으로 더했다
RelationalComparison_Quadrant ComparisonTable → List 2축 위 상대 위치
RelationalComparison_BubbleChart PieChart → DonutChart → BarChart → List x축 위치와 버블 크기. 같은 RelationalComparison 대분류인 PieChart·DonutChart를 먼저 거친다
Transformation_Transformation Steps → Versus → ProblemSolution → List 전후의 방향성. Steps(ProcessFlow, "단계")가 Versus보다 의미상 가깝다고 판단해 1순위로 뒀다
ProcessFlow_Steps Timeline → Staircase → List 절차라는 의미. Timeline은 시간축, Staircase는 상승 방향으로 읽힌다
TimelineSchedule_Timeline Steps → Staircase → List 시간축. Steps는 순서만, Staircase는 상승 방향만 남는다
Progression_Staircase Steps → Timeline → Pyramid → List 상승이라는 방향. Steps(순서), Timeline(시간축), Pyramid(위계)를 차례로 거친다
Cycle_Cycle Steps → List 순환. 끝이 처음으로 돌아간다는 사실
LogicFlow_FlowChart Steps → List 분기
Conversion_Funnel Steps → BarChart → LineAreaChart → ComparisonTable → List 단계마다 줄어든다는 사실. v1의 차트 후보 셋을 안전망으로 전부 살렸다(2026-08-17 사용자 결정, 6.2절)
TimelineSchedule_GanttChart Timeline → Calendar → ListingTable → List 기간의 길이와 병렬 진행. Calendar(같은 TimelineSchedule)를 안전망으로 더했다
TimelineSchedule_Calendar Timeline → ListingTable → GanttChart → List 날짜 격자. GanttChart(같은 TimelineSchedule)를 안전망으로 더했다
Hierarchy_HierarchicalTree Pyramid → List 분기 연결선. Pyramid(개수 3 이상)가 위계를 층으로 대신 표현한다
Hierarchy_Pyramid NestedScope → Staircase → List 층 넓이가 나타내던 위계. NestedScope와 상호참조라 각자 안전망이 없으면 둘 다 막힌다
ScopeCoverage_NestedScope Pyramid → Staircase → List 범위의 크기. Pyramid와 상호참조
ReasoningStructure_Fishbone ProblemSolution → List 원인이 한 결과로 모인다는 방향. 같은 ReasoningStructure 대분류인 ProblemSolution으로 직행한다
ReasoningStructure_CombinedResult Steps → ProblemSolution → List 결합의 방향. Steps(ProcessFlow, "단계")가 원인 분해보다 의미상 가깝다고 판단했다
ReasoningStructure_ProblemSolution CombinedResult → Transformation → Steps → List 문제와 해결의 짝. CombinedResult(같은 ReasoningStructure)를 먼저 거친다
KPIKeyMetric_BigNumber Gauge → HighlightedMessage → List 수치의 크기 강조. Gauge(같은 KPIKeyMetric)로 목표 대비 위치를 대신 표현한다. HighlightedMessage는 개수 "정확히 1"이라 P8 폐기(B-27) 이후엔 상한을 넘으면 성립하지 않으므로 List를 안전망으로 더했다
KPIKeyMetric_Gauge BigNumber → HighlightedMessage → List 목표 대비 위치. PictogramChart·BarChart는 matrix 필수라 measure 전용인 Gauge에서 변환할 projection 규칙이 없어 뺐다(B-30, 2026-09-05 확정 — 2026-08-17에 한 번 같은 결론이 났다가 문서 동기화 중 유실됐던 것을 복원). HighlightedMessage(entities 1개 강조형)는 개수 "정확히 1"이라 P8 폐기(B-27) 이후엔 상한을 넘으면 성립하지 않으므로 List를 안전망으로 더했다
MagnitudeRanking_BarChart LineAreaChart → ComparisonTable → ListingTable → List 수치의 상대 크기. LineAreaChart(상호참조)를 거쳐 ComparisonTable, ListingTable로 물러난다
ProportionComposition_StackedBarChart BarChart → LineAreaChart → List 구성 비중. 계열 합계만 남는다. LineAreaChart를 안전망으로 더했다
ProportionComposition_PieChart DonutChart → BarChart → ComparisonTable → List 도넛과는 가운데 공간 차이뿐이다. ComparisonTable(열 2개 이상)을 안전망으로 더했다
ProportionComposition_DonutChart PieChart → BarChart → ComparisonTable → List 가운데 라벨 자리. ComparisonTable을 안전망으로 더했다
ProportionComposition_TreeMap PieChart → DonutChart → List 면적으로 나타내던 비율과 중첩. 같은 ProportionComposition 대분류인 PieChart, DonutChart를 먼저 거친다
ProportionComposition_PictogramChart BarChart → List 아이콘 반복의 직관성. Gauge·BigNumber는 둘 다 matrix가 아니라 단일값 기반이라 뺐다(B-30, 2026-09-05 확정 — BigNumber는 entities가 여럿이어도 entity 하나당 value 하나가 필수라 P4(label+items만 채움)로는 그 요구를 못 채운다). 같은 matrix 기반인 BarChart만 거치고 바로 List로 일반화한다
Trend_LineAreaChart BarChart → ComparisonTable → List 추세선의 연속성. BarChart와 상호참조, ComparisonTable을 안전망으로 더했다
Trend_ComboChart ComparisonTable → ListingTable → List 이중 축과 계열 타입 혼합. 차트 계열로는 물러나지 않는다 — 아래 상자 참고
TextBasedCommunication_HighlightedMessage QuoteTestimonial → List → LongFormTextLayout 인용 부호와 출처 표기. entities 1개·한 문장이면 구조적으로 항상 성립하지만(3.0절), 색인 공급이 부족하면 List나 LongFormTextLayout으로 물러난다
TextBasedCommunication_LongFormTextLayout List 여러 문장 본문 구조. entities 1개·여러 문장이면 구조적으로 항상 성립하지만, 색인 공급이 부족하면 List로 물러난다
QnACard_QnACard DialogueChat → List 카드 형태
Utterance_DialogueChat QuoteTestimonial → QnACard → List 말풍선과 화자 구분. QuoteTestimonial(같은 Utterance 대분류)을 먼저 거친다
Utterance_QuoteTestimonial DialogueChat → HighlightedMessage → List 인용 부호와 출처 표기. DialogueChat과 상호참조. HighlightedMessage는 개수 "정확히 1"이라 P8 폐기(B-27) 이후엔 상한을 넘으면 성립하지 않으므로 List를 안전망으로 더했다
PortraitProfile_ProfileCard ResumeCV → List → ListingTable 사진과 카드 배치. ResumeCV(같은 PortraitProfile)를 1순위로 확정했다
PortraitProfile_ResumeCV ProfileCard → Timeline → ListingTable → List 이력 항목의 기간 정렬. ProfileCard(같은 PortraitProfile)를 먼저 거친다
PageNavigation_Opening HighlightedMessage → List 없음. 발표 맨 앞장은 전형적으로 제목 한 줄짜리 단일 메시지형이라 HighlightedMessage를 1순위로 뒀다
PageNavigation_TableOfContents List → ListingTable 목차라는 구조. 나열형이라 List가 1순위이고, 항목이 많으면 ListingTable로 물러난다
PageNavigation_SectionDivider HighlightedMessage → List 없음. 장 전환은 제목 한 줄짜리 단일 메시지형이라 HighlightedMessage를 1순위로 뒀다
PageNavigation_Ending HighlightedMessage → List 없음. 마지막 장의 종결 메시지는 단일 메시지형이라 HighlightedMessage를 1순위로 뒀다

ComboChart 는 BarChart·LineAreaChart 로 물러나지 않는다 (2026-08-21 개정). 종전 규칙은 계열 타입을 읽어 "전부 bar 면 BarChart, 전부 line 이면 LineAreaChart, 섞여 있으면 표로" 판정하는 조건부였다(../plan/fallback-map-review-2026-08-13.md 5절). 폐기한다.

성립 조건이 그 분기를 스스로 배제한다. 2절 32번이 matrix 필수 + 계열 type 2종 이상 또는 axis 2종 이상을 요구한다. 계열이 전부 bar 면 type 은 1종이므로, 그 컴포넌트가 ComboChart 로 성립한 이유는 axis 2종밖에 없다. 즉 "계열이 전부 bar/line 인 ComboChart" 는 언제나 이중 축이다. 그런데 이중 축은 ComboChart 에서만 지원되므로(김수현, 2026-08-21) 막대나 선으로 내리면 반드시 축 하나가 사라진다. 조건부 분기는 발동할 때마다 데이터를 깨는 규칙이었다.

종전 규칙은 area 도 다루지 못했다. Column.type 은 bar·line·area 셋인데(규약 2.1절) "전부 area" 는 세 갈래 어디에도 걸리지 않았다. 고정 목록으로 바꾸면 이 구멍도 함께 닫힌다.

표로 물러나도 이중 축은 잃는다. 표는 축을 그리지 않는다. 다만 matrix 의 행과 열이 그대로 보존되므로 수치가 사라지지는 않는다. 잃는 것을 최소화하는 자리가 표다.

axis 는 이름이 겹친다. ComboChart 의 axis 는 계열이 어느 축에 붙는가(left/right)이고, ComparisonTable 의 axis 는 첫 열이 기준 축인가다(2절 4번). 키 이름이 같아 P2 가 버리지 않고 통과시키지만 뜻이 다르다. 3.5절 표는 이 엣지에서 axis 를 살아남는 것으로 적지 않는다.

3.3 서로를 가리키는 쌍이 있다 (2026-08-17 갱신)#

ListingTable과 ComparisonTable, Steps와 Timeline, Pyramid와 NestedScope, BigNumber와 Gauge, BarChart와 LineAreaChart, PieChart와 DonutChart, DialogueChat와 QuoteTestimonial, ProfileCard와 ResumeCV는 서로를 후속 우선순위의 첫 자리로 가리킨다. 순환이 아니다. 3.0.1절대로 검색 모듈이 자기 행만 따라가므로 도착 카테고리의 행으로 넘어가는 일이 없고, 되돌아갈 자리가 애초에 생기지 않는다. 방문 기록도 필요 없다.

Steps 행은 Timeline → Staircase → List 이고 Timeline 행은 Steps → Staircase → List 다. Steps 에서 출발해 Timeline 후보가 없으면 다음은 Staircase 이지 Timeline 행의 Steps 가 아니다.

이 쌍들은 7/24 회의의 "규칙 관점에서 비슷한 카테고리를 묶어 유형 간 관계도를 만들고"에 해당한다(회의록).


3.4 projection 규칙 열 (여덟 확정 + 둘 제안)#

fallback 은 카테고리를 바꾸는 일이므로, 출발 카테고리가 쓰던 것을 도착 카테고리가 못 읽는 경우가 생긴다. 그때 무엇을 어디로 보내는지를 여기서 정한다. 규약 5절 4번이 "삭제하지 않고 텍스트로 흘려보낸다"까지만 정하고 어디로인지는 안 정하던 자리다.

# 규칙
P1 entities 는 그대로 간다. 순서와 label, items, children 은 어느 엣지에서도 손대지 않는다
P2 도착이 선언하지 않은 params 는 버린다. 표현 선택이지 내용이 아니다
P3 도착이 안 읽는 내용 필드는 그 entity 의 items 맨 뒤에 이름: 값 으로 붙인다. 대상은 2절이 선언한 내용 필드다
P4 matrix 를 못 읽는 곳으로 가면 entities 로 편다. 행마다 entity 를 만들고 첫 열이 label, 나머지 열이 열이름: 값 으로 items 에 들어간다
P5 measure 를 못 읽는 곳으로 가면 value 와 unit 을 entity 로 옮긴다. 나머지 필드는 P3 을 따른다
P6 entities 만 있는데 matrix 가 필요하면 행으로 편다. label 이 첫 열, items 가 나머지 열이다
P7 도착이 관계를 못 그리면 relation.desc 를 of 첫 항목의 items 맨 뒤로 보낸다
P8 도착의 개수 상한을 넘으면 초과분을 마지막 항목의 items 로 접는다 폐기 (B-27, 2026-09-05). 개수 상한을 넘으면 접지 않고 규칙 1(성립 조건 미충족 → 후보 탈락)을 그대로 적용한다. 접으면 마지막 항목 하나에 텍스트가 몰려 overflow가 눈에 띄게 나기 쉽고, 탈락시켜도 사다리 끝에는 항상 개수 상한이 없는 안전망(List 등)이 있어 정보가 사라지지 않는다

P2 보충 — 표 도착지에서는 차트 열 어휘를 버린다 (2026-09-10 신설, 허재영 판단 · 정원석 확인 대기). matrix 를 그대로 읽는 표 도착지(2.5.1절 entities+columns 경로를 가진 ListingTable 과 ComparisonTable)로 갈 때 Column.role 과 axis 와 type 을 버린다. 2.5.1절이 이 셋을 순수 차트 어휘라고 적으므로 표에서는 뜻이 없고, P2 가 말하는 표현 선택과 같은 자리다. 이 규칙이 없으면 값 열에 문자열이 섞인 차트가 표 도착지에서도 B-37 로 탈락해 List 까지 내려간다 — 표는 문자열을 담는 자리인데도 그렇다. 규칙 라벨은 바뀌지 않고 손실 목록에 한 줄이 붙는다.

내용은 버리지 않고 표현만 버린다. P2 만 버리는 규칙이고 나머지는 전부 옮기는 규칙이다. 표현 선택(orientation, apex, icon 같은 것)은 도착 카테고리에서 뜻이 없으므로 버려도 내용이 줄지 않는다.

measure↔matrix projection 규칙은 만들지 않는다 (B-30, 2026-09-05 확정). 단일 값(measure)과 복합 데이터(matrix)는 형태가 근본적으로 달라서, 서로 변환하는 규칙(옛 제안 P9·P10)을 두면 원래 의도를 왜곡한다. Gauge(measure)를 matrix 필수인 BarChart·PictogramChart 로 접으면 비교·구성 정보가 사라진 막대 하나·아이콘 그룹 하나만 남고, 반대로 PictogramChart(matrix, 여러 값)의 여러 행 중 하나를 뽑아 Gauge(measure)의 대표값으로 쓰면 원본에 없던 target/max/min을 채울 근거도 없이 나머지 값을 전부 버리게 된다. 그래서 KPIKeyMetric_Gauge와 ProportionComposition_PictogramChart의 fallback 체인에서 서로를 뺐다(3.2절). 2026-08-17에 ../plan/fallback-map-review-2026-08-13.md 가 한 번 같은 결론을 냈었는데, 이후 이 문서와 report/*, TODO.md 가 옛 체인으로 되돌아가 있었다 — 이번이 그 유실 복원이다. PictogramChart → BigNumber도 뺐다(2026-09-05, 재검토). 처음엔 BigNumber가 entities 1개 이상을 받으니 P4(행마다 entity로 편다)로 행을 전부 보존할 수 있어 예외로 남겼는데, 확인해 보니 P4는 label과 items만 채우고 BigNumber의 필수 조건인 entity당 value는 채워주지 않는다. BigNumber도 entities가 여럿일 수 있을 뿐 entity 하나당 값 하나라는 단일값 콘텐츠 모델이라, matrix의 임의 열 구조를 우겨넣을 수 없다는 점에서 Gauge와 같은 범주다.

3.5 엣지 130개 전량#

3.2절의 각 행이 자기 카테고리에서 나가는 엣지를 정한다(3.0.1절). 행마다의 목록 길이를 합하면 130개이고 중복이 없다. 규칙은 2절 capability 에서 계산한 것이다.

2026-08-04 에 51개에서 75개로 다시 계산했다. 행을 경로로 읽고 연속쌍을 뽑는 방식이 어느 행에도 적혀 있지 않은 엣지를 만들고 행에 적힌 엣지 25개를 빠뜨렸기 때문이다(3.0.1절 근거).

2026-08-17 1차에 75개에서 111개로, 2차에 111개에서 125개로 다시 계산했다. fallback map v2 반영(../plan/fallback-map-review-2026-08-13.md)으로 상한이 폐지(4절)되고, 이어서 허재영님 2차 피드백을 반영하면서 List·SWOT·HighlightedMessage·LongFormTextLayout·TableOfContents·ProfileCard(6.1절)가 색인 공급 부족 시나리오로 확정됐고, PictogramChart↔Gauge(6.2절)가 근접도 우선으로 복원됐고, PageNavigation 3종(Opening·SectionDivider·Ending)이 List 대신 HighlightedMessage로 분화됐다(6.4절). Funnel만 여전히 원저자 확인 대기다.

2026-08-21 에 125개(3차 반영 뒤 128개)에서 131개로 다시 계산했다. ComboChart 의 조건부 규칙을 폐기하고 고정 목록(ComparisonTable → ListingTable → List)으로 바꾸면서 엣지 3개가 새로 생겼다(3.2절 상자).

출발 도착 규칙 무엇이 옮겨지나
BarChart ComparisonTable P1 P2 옮길 것이 없다. 같은 것을 읽는다
BarChart LineAreaChart P1 P2 옮길 것이 없다. 같은 것을 읽는다
BarChart List P1 P2 P3 P4 matrix 의 행이 entities 가 된다, unit, value 가 items 로 간다, orientation, sort 는 표현 선택이라 버린다
BarChart ListingTable P1 P2 옮길 것이 없다. 같은 것을 읽는다
BigNumber Gauge P1 P2 P3 unit, delta, emphasis
BigNumber HighlightedMessage P1 P2 P3 P5 value와 unit이 entity로 간다, unit, delta, emphasis, 1개를 넘으면 이 엣지는 성립하지 않는다(P8 폐기, B-27)
BigNumber List P1 P2 P3 P5 value, unit, delta, emphasis 가 items 로 간다. HighlightedMessage 가 개수 초과로 막힐 때의 안전망(B-27)
BubbleChart BarChart P1 P2 sizeScale 는 표현 선택이라 버린다
BubbleChart DonutChart P1 P2 옮길 것이 없다. 같은 것을 읽는다
BubbleChart List P1 P2 P3 P4 matrix 의 행이 entities 가 된다, unit, value 가 items 로 간다, sizeScale 는 표현 선택이라 버린다
BubbleChart PieChart P1 P2 옮길 것이 없다. 같은 것을 읽는다
Calendar GanttChart P1 P2 옮길 것이 없다. 같은 것을 읽는다
Calendar List P1 P2 P3 P4 matrix 의 행이 entities 가 된다, unit, value 가 items 로 간다, weekStart 는 표현 선택이라 버린다
Calendar ListingTable P1 P2 weekStart 는 표현 선택이라 버린다
Calendar Timeline P1 P2 P3 P4 matrix의 행이 entities가 된다, 15개를 넘으면 이 엣지는 성립하지 않는다(P8 폐기, B-27)
CombinedResult List P1 P2 옮길 것이 없다. 같은 것을 읽는다
CombinedResult ProblemSolution P1 P2 옮길 것이 없다. 같은 것을 읽는다
CombinedResult Steps P1 P2 옮길 것이 없다. 같은 것을 읽는다
ComboChart ComparisonTable P1 P2 type 은 표현 선택이라 버린다. 표는 축을 그리지 않아 이중 축 표현이 사라진다
ComboChart List P1 P2 P3 P4 matrix 의 행이 entities 가 된다, unit, value 가 items 로 간다, axis, type 은 표현 선택이라 버린다
ComboChart ListingTable P1 P2 axis, type 은 표현 선택이라 버린다
ComparisonTable List P1 P2 P3 P4 matrix 의 행이 entities 가 된다, unit, value 가 items 로 간다, axis, header 는 표현 선택이라 버린다
ComparisonTable ListingTable P1 P2 axis 는 표현 선택이라 버린다
ComparisonTable Versus P1 P2 P3 P4 matrix의 행이 entities가 된다, 2개를 넘으면 이 엣지는 성립하지 않는다(P8 폐기, B-27)
Cycle List P1 P2 center, rotation 는 표현 선택이라 버린다
Cycle Steps P1 P2 center, rotation 는 표현 선택이라 버린다
DialogueChat List P1 P2 P3 speaker 가 items 로 간다
DialogueChat QnACard P1 P2 P3 speaker 가 items 로 간다
DialogueChat QuoteTestimonial P1 P2 P3 speaker
DonutChart BarChart P1 P2 centerLabel 는 표현 선택이라 버린다
DonutChart ComparisonTable P1 P2 옮길 것이 없다. 같은 것을 읽는다
DonutChart List P1 P2 P3 P4 matrix 의 행이 entities 가 된다, unit, value 가 items 로 간다, centerLabel, labelPosition, startAngle 는 표현 선택이라 버린다
DonutChart PieChart P1 P2 centerLabel 는 표현 선택이라 버린다
Ending HighlightedMessage P1 P2 1개를 넘으면 이 엣지는 성립하지 않는다(P8 폐기, B-27)
Ending List P1 P2 옮길 것이 없다. 같은 것을 읽는다
Fishbone List P1 P2 P3 children 가 items 로 간다
Fishbone ProblemSolution P1 P2 P3 children
FlowChart List P1 P2 옮길 것이 없다. 같은 것을 읽는다
FlowChart Steps P1 P2 옮길 것이 없다. 같은 것을 읽는다
Funnel BarChart P1 P2 conversionRate 는 표현 선택이라 버린다
Funnel ComparisonTable P1 P2 conversionRate 는 표현 선택이라 버린다
Funnel LineAreaChart P1 P2 conversionRate 는 표현 선택이라 버린다
Funnel List P1 P2 P3 P4 matrix 의 행이 entities 가 된다, unit, value 가 items 로 간다, conversionRate 는 표현 선택이라 버린다
Funnel Steps P1 P2 P3 P4 matrix 의 행이 entities 가 된다, unit, value 가 items 로 간다, conversionRate 는 표현 선택이라 버린다
GanttChart Calendar P1 P2 옮길 것이 없다. 같은 것을 읽는다
GanttChart List P1 P2 P3 P4 matrix 의 행이 entities 가 된다, unit, value 가 items 로 간다, unit(기간 단위) 는 표현 선택이라 버린다
GanttChart ListingTable P1 P2 옮길 것이 없다. 같은 것을 읽는다
GanttChart Timeline P1 P2 P3 P4 matrix 의 행이 entities 가 된다, unit, value 가 items 로 간다. 15개를 넘으면 이 엣지는 성립하지 않는다(P8 폐기, B-27) — Timeline 도 상한 초과면 후보에서 빠지고 다음 후보로 넘어간다
Gauge BigNumber P1 P2 P3 target, max, min, thresholds
Gauge HighlightedMessage P1 P2 P3 P5 value와 unit이 entity로 간다, target, max, min, thresholds, 1개를 넘으면 이 엣지는 성립하지 않는다(P8 폐기, B-27)
Gauge List P1 P2 P3 P5 value, target, max, min, thresholds 가 items 로 간다. HighlightedMessage 가 개수 초과로 막힐 때의 안전망(B-27)
HierarchicalTree List P1 P2 P3 children 가 items 로 간다, orientation 는 표현 선택이라 버린다
HierarchicalTree Pyramid P1 P2 P3 children
HighlightedMessage List P1 P2 옮길 것이 없다. 같은 것을 읽는다
HighlightedMessage LongFormTextLayout P1 P2 옮길 것이 없다. 같은 것을 읽는다
HighlightedMessage QuoteTestimonial P1 P2 옮길 것이 없다. 같은 것을 읽는다
LineAreaChart BarChart P1 P2 fill 는 표현 선택이라 버린다
LineAreaChart ComparisonTable P1 P2 옮길 것이 없다. 같은 것을 읽는다
LineAreaChart List P1 P2 P3 P4 matrix 의 행이 entities 가 된다, unit, value 가 items 로 간다, fill 는 표현 선택이라 버린다
List ListingTable P1 P2 P6 entities가 matrix의 행이 된다
ListingTable ComparisonTable P1 P2 옮길 것이 없다. 같은 것을 읽는다
ListingTable List P1 P2 P3 P4 matrix 의 행이 entities 가 된다, unit, value 가 items 로 간다, header 는 표현 선택이라 버린다
LongFormTextLayout List P1 P2 옮길 것이 없다. 같은 것을 읽는다
NestedScope List P1 P2 entities 가 배열 순서 그대로 items 로 간다, direction·shape 는 표현 선택이라 버린다
NestedScope Pyramid P1 P2 entities 가 배열 순서 그대로 층 순서가 된다, weight 는 없어 층 높이가 균등해진다
NestedScope Staircase P1 P2 entities 가 배열 순서 그대로 간다, 8개를 넘으면 이 엣지는 성립하지 않는다(P8 폐기, B-27)
Opening HighlightedMessage P1 P2 1개를 넘으면 이 엣지는 성립하지 않는다(P8 폐기, B-27)
Opening List P1 P2 옮길 것이 없다. 같은 것을 읽는다
PictogramChart BarChart P1 P2 옮길 것이 없다. 같은 것을 읽는다
PictogramChart List P1 P2 P3 P4 matrix의 행이 entities가 된다
PieChart BarChart P1 P2 labelPosition, startAngle 는 표현 선택이라 버린다
PieChart ComparisonTable P1 P2 옮길 것이 없다. 같은 것을 읽는다
PieChart DonutChart P1 P2 labelPosition, startAngle 는 표현 선택이라 버린다
PieChart List P1 P2 P3 P4 matrix 의 행이 entities 가 된다, unit, value 가 items 로 간다, labelPosition, startAngle 는 표현 선택이라 버린다
ProblemSolution CombinedResult P1 P2 옮길 것이 없다. 같은 것을 읽는다
ProblemSolution List P1 P2 옮길 것이 없다. 같은 것을 읽는다
ProblemSolution Steps P1 P2 옮길 것이 없다. 같은 것을 읽는다
ProblemSolution Transformation P1 P2 2개를 넘으면 이 엣지는 성립하지 않는다(P8 폐기, B-27)
ProfileCard List P1 P2 P3 avatar, organization
ProfileCard ListingTable P1 P2 P3 P6 entities가 matrix의 행이 된다, avatar, organization
ProfileCard ResumeCV P1 P2 P3 avatar, organization
Pyramid List P1 P2 P3 weight 가 items 로 간다, apex 는 표현 선택이라 버린다
Pyramid NestedScope P1 P2 P3 entities 가 배열 순서 그대로 간다, weight 는 표현 선택이라 버린다
Pyramid Staircase P1 P2 P3 weight 가 items 로 간다, apex 는 표현 선택이라 버린다. 8개를 넘으면 이 엣지는 성립하지 않는다(P8 폐기, B-27) — Staircase 도 상한 초과면 후보에서 빠지고 다음 후보(List)로 넘어간다
QnACard DialogueChat P1 P2 옮길 것이 없다. 같은 것을 읽는다
QnACard List P1 P2 옮길 것이 없다. 같은 것을 읽는다
Quadrant ComparisonTable P1 P2 axisLabels 는 표현 선택이라 버린다
Quadrant List P1 P2 P3 P4 matrix 의 행이 entities 가 된다, unit, value 가 items 로 간다, axisLabels 는 표현 선택이라 버린다
QuoteTestimonial DialogueChat P1 P2 P3 author, source
QuoteTestimonial HighlightedMessage P1 P2 P3 author, source 가 items 로 간다
QuoteTestimonial List P1 P2 P3 author, source 가 items 로 간다. HighlightedMessage 가 개수 초과로 막힐 때의 안전망(B-27)
RadarChart BarChart P1 P2 maxValue 는 표현 선택이라 버린다
RadarChart ComparisonTable P1 P2 옮길 것이 없다. 같은 것을 읽는다
RadarChart LineAreaChart P1 P2 옮길 것이 없다. 같은 것을 읽는다
RadarChart List P1 P2 P3 P4 matrix 의 행이 entities 가 된다, unit, value 가 items 로 간다, maxValue 는 표현 선택이라 버린다
ResumeCV List P1 P2 P3 children, period 가 items 로 간다
ResumeCV ListingTable P1 P2 P3 P6 children, period 가 items 로 간다, entities 가 matrix 의 행이 된다
ResumeCV ProfileCard P1 P2 P3 period, children
ResumeCV Timeline P1 P2 P3 children, 15개를 넘으면 이 엣지는 성립하지 않는다(P8 폐기, B-27)
SWOT List P1 P2 옮길 것이 없다. 같은 것을 읽는다
SWOT ListingTable P1 P2 P6 entities가 matrix의 행이 된다
SectionDivider HighlightedMessage P1 P2 1개를 넘으면 이 엣지는 성립하지 않는다(P8 폐기, B-27)
SectionDivider List P1 P2 옮길 것이 없다. 같은 것을 읽는다
StackedBarChart BarChart P1 P2 normalize 는 표현 선택이라 버린다
StackedBarChart LineAreaChart P1 P2 옮길 것이 없다. 같은 것을 읽는다
StackedBarChart List P1 P2 P3 P4 matrix 의 행이 entities 가 된다, unit, value 가 items 로 간다, normalize, orientation 는 표현 선택이라 버린다
Staircase List P1 P2 direction 는 표현 선택이라 버린다
Staircase Pyramid P1 P2 옮길 것이 없다. 같은 것을 읽는다
Staircase Steps P1 P2 direction 는 표현 선택이라 버린다
Staircase Timeline P1 P2 15개를 넘으면 이 엣지는 성립하지 않는다(P8 폐기, B-27)
Steps List P1 P2 marker, sequence 는 표현 선택이라 버린다
Steps Staircase P1 P2 8개를 넘으면 이 엣지는 성립하지 않는다(P8 폐기, B-27)
Steps Timeline P1 P2 marker, sequence 는 표현 선택이라 버린다. 15개를 넘으면 이 엣지는 성립하지 않는다(P8 폐기, B-27) — Timeline 도 상한 초과면 후보에서 빠지고 다음 후보(Staircase, 그다음 List)로 넘어간다
TableOfContents List P1 P2 옮길 것이 없다. 같은 것을 읽는다
TableOfContents ListingTable P1 P2 P6 entities가 matrix의 행이 된다
Timeline List P1 P2 P3 period 가 items 로 간다, orientation 는 표현 선택이라 버린다
Timeline Staircase P1 P2 P3 period, 8개를 넘으면 이 엣지는 성립하지 않는다(P8 폐기, B-27)
Timeline Steps P1 P2 P3 period 가 items 로 간다, orientation 는 표현 선택이라 버린다
Transformation List P1 P2 direction 는 표현 선택이라 버린다
Transformation ProblemSolution P1 P2 옮길 것이 없다. 같은 것을 읽는다
Transformation Steps P1 P2 옮길 것이 없다. 같은 것을 읽는다
Transformation Versus P1 P2 direction 는 표현 선택이라 버린다
TreeMap DonutChart P1 P2 P3 children
TreeMap List P1 P2 P3 P4 matrix 의 행이 entities 가 된다, unit, value 가 items 로 간다
TreeMap PieChart P1 P2 P3 children
VennDiagram ComparisonTable P1 P2 P6 P7 entities 가 matrix 의 행이 된다, 관계의 desc 가 items 로 간다
VennDiagram List P1 P2 P7 관계의 desc 가 items 로 간다
VennDiagram Versus P1 P2 P7 관계의 desc가 items로 간다, 2개를 넘으면 이 엣지는 성립하지 않는다(P8 폐기, B-27)
Versus ComparisonTable P1 P2 P6 P7 entities 가 matrix 의 행이 된다, 관계의 desc 가 items 로 간다
Versus List P1 P2 P7 관계의 desc 가 items 로 간다

3.6 복합 구조 fallback 순서 (2026-08-28 확정)#

3.2절부터 3.5절까지는 노드 하나의 카테고리를 내리는 사다리다. 질의가 컴포넌트 하나로 끝나지 않고 중첩(entity.components[])이나 병치(최상위 components[] 여럿)로 여러 노드를 가질 때는, 어느 노드를 먼저 포기하는지를 따로 정해야 한다. 이 절이 그 순서를 정한다. 정한 사람은 정원석이고 날짜는 2026-08-28 이다. 종전에는 ../design/content-binding-contract.md 3.4절이 "아직 한 문서에 못 박지 않았다"로 열어 두던 자리다.

이 절의 SSoT 관계 순서와 규칙은 이 절이 SSoT 다. 한 단계씩 따라가는 예시는 ../design/content-binding-contract.md 3.7절에 있다.

3.6.1 다섯 단계#

위에서부터 시도하고 후보가 나오면 멈춘다. 이름 표기 (2026-09-05 정원석). N1~N5 코드는 유지하되, 이름만 봐도 동작이 짐작되도록 각각 ExactMatch: Structure&Semantic / ExactStructure-SemanticFallback / Root-Peeling / Re-Peeling / Content Flattening 을 병기한다.

단계 무엇을 하나 잃는 것
N1 (ExactMatch: Structure&Semantic) 질의 트리 전체를 그대로 담은 후보를 색인에서 찾는다 없다
N2 (ExactStructure-SemanticFallback) 트리 모양을 고정한 채, 노드마다 3.2절 fallback 순서를 따라 내려간다 노드마다의 카테고리 표현
N3 (Root-Peeling) 가장 바깥 부모를 벗겨 자식을 최상위 components[] 로 올리고, N1 부터 다시 시작한다 벗긴 부모의 카테고리 표현과 형제 사이의 공간적 인접성
N4 (Re-Peeling) 남은 노드가 모두 단일 카테고리가 될 때까지 N3(Root-Peeling)을 반복한다 위와 같다. 깊이마다 한 번씩
N5 (Content Flattening) 규약 1절 "중첩은 평탄화된다"대로 자식 entities 를 부모 items 로 편다 자식 전부의 카테고리 표현

N5 는 3.0절의 마지막 fallback 셋 중 하나로 끝나므로 반드시 성립한다. 그래서 복합 구조에서도 빈 결과가 나오지 않는다.

어느 절이 도는지는 질의의 복잡도 레벨이 정한다. L1(단일 컴포넌트)은 3.2~3.5절만 쓰고, L2(최상위 병치)는 그 사다리가 원소마다 독립으로 돌 뿐 이 절을 쓰지 않으며, 이 절이 실제로 필요한 것은 L3(entity.components[] 중첩)뿐이다. 레벨 정의는 schema-v0.5.md 8.8절이다.

사다리는 최상위 components[] 원소마다 독립으로 돈다. 최상위 병치는 이미 컴포넌트 경계가 갈려 있어 벗길 부모가 없다. 색인의 후보 단위도 컴포넌트 하나이므로, 최상위 원소 둘을 한 후보가 함께 담는 일은 애초에 없다. 그래서 N1 부터 N5 는 원소마다 따로 진행하고 한 원소의 결과가 다른 원소를 바꾸지 않는다.

3.6.2 왜 N2 가 N3 보다 먼저인가#

구조가 다르면 그 구조 안에 있던 내용이 갈 자리가 없어진다. 모양을 지키면서 카테고리만 내리는 시도가 배치를 깨는 시도보다 손실이 작으므로 먼저 둔다.

3.6.3 왜 아래가 아니라 위부터 벗기나#

부모를 벗기는 것과 자식을 벗기는 것은 대칭이 아니다.

질의에 생기는 일 내용 성공률
부모를 벗긴다 (N3) 질의 1개가 질의 여럿으로 갈라진다 자식의 내용과 카테고리 표현이 전부 남는다 질의가 작아지므로 후보 풀이 커진다
자식을 벗긴다 질의 1개가 그대로 복합 구조로 남는다 벗긴 자식의 내용이 지워지거나 부모로 접힌다 부모가 여전히 복합이라 그대로다

자식부터 벗기면 내용을 지불하고 검색 가능성을 얻지 못한다. 그래서 N3 은 위부터만 간다.

3.6.4 규칙 다섯#
# 규칙
N-R1 벗긴 부모의 내용은 버리지 않는다. N3 에서 부모를 버릴 때 부모의 label, items, relations[].desc 는 승격된 첫 조각의 첫 entity items 맨 뒤로 보낸다. P3 과 P7 과 같은 방식이다. 부모가 고유 항목을 갖지 않으면 옮길 것이 없다
N-R2 형제는 큰 묶음부터 그리디로 나눈다. 형제가 n개면 묶음 크기 n 으로 한 번 찾고, 안 되면 크기를 하나씩 줄인다. 4형제면 [4], [3,1], [2,2], [2,1,1], [1,1,1,1] 순이다. 연속한 형제만 묶고 순서를 바꾸지 않는다
N-R3 Unclassified 는 카테고리가 아니라 경계다. 색인 태깅 어휘의 Unclassified 는 질의 type 이 될 수 없고 검증이 거부한다. 질의 안에서 뿌리 없는 묶음이 그 자리를 차지하면, 그 자리를 카테고리로 검색하지 않는다. N2 의 노드 강등 대상에서 빼고, N3 에서 벗길 때는 그 경계에 걸친 형제를 N-R2 의 한 묶음으로 유지한다
N-R4 N 단계 수와 3.2절 칸 수는 다른 예산이다. N3 을 한 번 하는 것이 카테고리 fallback 칸을 소모하지 않는다. 4절의 상한 폐지는 카테고리 축에만 적용된다
N-R6 N2 의 조합 순서는 강등 칸수 합이 작은 것부터다. 노드가 셋이면 (0,0,0) 다음이 합 1인 조합 셋이고 그다음이 합 2다. 합이 같은 조합끼리는 뿌리에서 먼 노드를 먼저 내린다. 뿌리의 카테고리가 형제 배치를 정하므로, 뿌리를 내리면 자식이 들어갈 슬롯이 함께 바뀐다
N-R5 확정 (2026-09-05, B-31). 부모의 대분류가 Hierarchy 면 Root-Peeling(N3)을 건너뛰고 Content Flattening(N5)으로 바로 간다. 규약 6절 셋째가 "계층이 이 내용의 핵심인가"를 대분류로 판정한다고 정했고, 계층을 벗기면 N-R1 이 남기는 흔적이 부모 한 층뿐이어서 깊이 3 이상에서는 소속 관계를 되살릴 수 없다(3단 조직도 예시: 벗기기를 반복하면 회사→부서→직원의 소속 관계가 사라지고 직원들이 그냥 나란히 나열된다). 평탄화는 들여쓴 items 로 임의 깊이를 그대로 보존한다. 실측 없이도 구조적 논증만으로 확정했다
3.6.5 N1 은 지금 정책상 언제나 비어 있다#

D-7 이 이종중첩 템플릿을 검색 풀에서 제외하기로 확정했다(2026-08-11 체크인, ../TODO.md D-7). 뿌리가 Unclassified 인 템플릿이 후보로 걸리지 않으므로, 이종 구조 질의에 대한 N1 은 후보가 0이다. N1 을 실제로 쓰려면 D-7 을 부분적으로 되돌려야 한다. 되돌릴지는 아직 정하지 않았다(../TODO.md D-8).

D-7 의 결정 근거가 "대부분 조합 Fallback 만으로 해결될 것으로 예상"이었으므로, 이 절의 N2 부터 N5 가 그 예상의 검증 대상이기도 하다. 검증 전에는 N1 을 순서에 남겨 두고 후보 0으로 통과시킨다. 순서에서 지우면 D-7 을 되돌릴 때 순서 정의를 다시 써야 한다.

3.6.6 이 순서가 닫는 항목과 남기는 항목#
항목 어떻게 됐나
질의 쪽 포함 위상을 평탄화한 뒤 fallback 할지, 구조를 유지한 채 노드 태그로 자를지 닫혔다. 유지하고 ExactStructure-SemanticFallback(N2)으로 먼저 시도한 뒤, 실패하면 Root-Peeling(N3)으로 벗기고, 마지막에만 Content Flattening(N5)한다
평탄화와 떼어내기 중 어느 쪽이 내용을 더 잘 보존하는지 닫혔다. 둘 다 텍스트를 안 버리지만 떼어내기가 카테고리 표현을 더 남기므로 Root-Peeling(N3)이 Content Flattening(N5)보다 먼저다
병치를 쪼개고 조각마다 검색을 호출하고 결과를 다시 조립하는 코드 열려 있다. 검색 재료(T-5 노드 태그 검색)만 있고 오케스트레이션이 없다 (../TODO.md I-11) — 재조립 설계에 인접 클러스터링 아이디어가 추가됐다(2026-09-05, I-11 참고): Re-Peeling으로 갈라진 조각들도 원래 부모가 같으면 배치 시점에 크기를 고려해 2~3개씩 묶어 화면에 배치하는 안
이종중첩 템플릿을 검색 풀에 되돌릴지 (N1) 열려 있다 (3.6.5절, ../TODO.md D-8)
계층 대분류에서 Root-Peeling(N3)을 건너뛸지 (N-R5) 닫혔다 (2026-09-05, B-31). 건너뛰고 바로 Content Flattening(N5)으로 간다
Root-Peeling(N3)과 Re-Peeling(N4)을 반복해 최상위 컴포넌트 수가 늘어날 때 상한을 둘지 닫혔다 (2026-09-05, B-32). 상한을 두지 않는다 — 배치 엔진이 분리된 컴포넌트를 페이지마다 나눠 배치하는 정책이라, 개수가 늘어도 여러 페이지에 걸쳐 배치하면 된다. 규약 8.6절 현행 유지

4. fallback 을 몇 번까지 따라가나 (2026-08-17 개정 — 상한 폐지)#

상한을 두지 않는다. 종전엔 최대 두 번까지 따라가고 그래도 후보가 없으면 마지막 fallback 으로 갔는데, 이 상한은 실측 근거가 없는 설계 판단이었다(구판 8절). fallback map v2 원칙 4(../plan/fallback-map-review-2026-08-13.md 1절)가 폐지했다. schema-v0.5.md 규약 5절 2번도 동기화했다. 근접도(원칙 2) 순서만 지키면 몇 칸이든 허용한다.

3.0.1절의 그래프 정의는 상한 폐지와 무관하게 그대로다. 검색 모듈은 여전히 자기 행만 왼쪽부터 따라가고, 다른 카테고리의 행을 이어 붙이지 않는다. 상한 폐지는 "한 행에 후속 후보를 몇 개까지 적을 수 있는가"만 바꾼다.

45행의 목록 길이 분포는 이렇다.

후속 목록 길이 행 수 어느 행인가
5 1 Funnel(2026-08-17 사용자 결정, 6.2절 — v1 차트 후보 셋을 안전망으로 전부 살렸다)
4 11 BarChart, BubbleChart, Calendar, DonutChart, GanttChart, PieChart, ProblemSolution, RadarChart, ResumeCV, Staircase, Transformation
3 17 BigNumber, ComboChart, CombinedResult, ComparisonTable, DialogueChat, Gauge, HighlightedMessage, LineAreaChart, NestedScope, ProfileCard, Pyramid, QuoteTestimonial, StackedBarChart, Steps, Timeline, TreeMap, VennDiagram
2 14 Cycle, Ending, Fishbone, FlowChart, HierarchicalTree, ListingTable, Opening, PictogramChart, QnACard, Quadrant, SWOT, SectionDivider, TableOfContents, Versus
1 2 List, LongFormTextLayout
0 0 없다 — 2026-08-17 2차 반영으로 List·HighlightedMessage·LongFormTextLayout도 색인 공급 부족 대비 후속 후보를 갖게 됐다(3.2절). 3.0절 "마지막 fallback 셋" 개념은 그대로 유효하다 — 이 셋은 자기 구조 조건(개수)이 항상 성립한다는 뜻이지, 자기 자신도 fallback 이 필요 없다는 뜻은 아니다

2026-09-05 갱신 (B-30·B-27). Gauge는 measure↔matrix 변환 엣지(옛 P9)가 없어져 4개→2개로, PictogramChart는 Gauge(P10)와 BigNumber(P4가 value 필수 조건을 못 채움, 재검토로 추가 제외) 둘 다 빠져 4개→2개로 줄었다(B-30, 131→127). 이후 B-27로 P8(개수 초과 접기)을 폐기하면서, HighlightedMessage가 종단으로 걸려 있던 BigNumber·Gauge·QuoteTestimonial 세 행에 안전망 List를 추가해 127→130이 됐다. 길이의 합이 3.5절 엣지 수 130과 같다.

상한을 없애면서 얻는 것과 잃는 것. 얻는 것은 근접도 순서를 끝까지 지킬 수 있다는 것이다 — 종전엔 세 번째로 가까운 후보가 있어도 두 칸에서 끊겨 마지막 fallback 으로 바로 떨어졌다. 잃는 것은 표현이 원래 의도에서 멀어지는 정도를 표 길이만으로는 더 이상 가늠할 수 없다는 것이다 — 배포 후 fallback 경로별 judge 점수로 실측해 조정한다(8절).


5. 어디에 두고 어떻게 로드하나 (B-14)#

TypeScript 상수를 SSoT 로 두고 BE 소비용은 JSON 으로 내보낸다 (2026-08-02 확정).

이 저장소 안에도 사본이 하나 있다. schema/capability.json 이다. 런타임이 읽는 물건이 아니라 문서 검증용이고, 이 문서 2절과 3.2절에서 뽑은 것이다. reads.content 와 reads.relations 와 reads.params 로 갈라 담는데, 2026-08-23 부터 이 문서 2절도 같은 구분을 두 열(선택 내용, 속성)로 갖는다. 둘이 어긋나면 JSON 을 따른다. 자세한 것은 schema/README.md 다.

packages/comp-sign 에 두고, semanticTaxonomy.gen.ts 와 같은 방식으로 생성물을 만든다. 근거는 schema-v0.4.md 5.1절이다. RLSC 어휘가 이미 타입으로 존재해 컴파일러가 대응을 검사할 수 있고, comp-sign 에 YAML 자원 파일 선례가 없다.

ts
// rlsc-capability.ts
export const CAPABILITIES = [
  {
    id: 'RelationalComparison_VennDiagram',
    rlsc: { type: LayoutElementKey.VennDiagram },
    requires: { entities: { min: 2, max: 3 } },        // jari 실측
    optional: { intersects: null },                      // 없으면 교집합을 비움
    fallback: ['AttributeComparison_ComparisonTable', 'Enumeration_List'],
  },
  {
    id: 'KPIKeyMetric_Gauge',
    rlsc: { type: LayoutElementKey.Chart, chartType: [ChartType.BAR_GAUGE, ChartType.CIRCLE_GAUGE] },
    requires: { dataset: 'measure', needs: ['value'] },
    optional: { target: null, max: 100, min: 0, thresholds: [] },
    fallback: ['KPIKeyMetric_BigNumber', 'Enumeration_List'],
  },
  {
    id: 'Enumeration_List',
    rlsc: { type: [LayoutElementKey.VStack, LayoutElementKey.HStack, LayoutElementKey.Grid, LayoutElementKey.Group] },
    requires: { entities: { min: 1 } },
    optional: {},
    fallback: [],                                        // 더 갈 곳이 없다
  },
] as const satisfies readonly Capability[]

6. 이 표가 닫는 항목#

항목 무엇이었나 어떻게 닫혔나
B-1 요구를 필수와 선호와 무시 3단계로 나눌지 나누지 않는다. 성립 조건을 최소로 줄이고 나머지는 기본값 있는 선택 속성으로 옮겼다 (1절)
B-4 카테고리별 포기 가능 여부를 명시할지 별도 선언을 두지 않는다. 여러 요구를 가진 입력은 어느 종류도 탈락하지 않고 후보로 남으며, 못 채운 요구는 fallback 규칙 3번대로 텍스트로 흘러간다
B-8 역방향 갭 중 2단계 지원 범위 (Quadrant, Pie, Donut) 41종 전량을 이 표에 넣어 갭이 사라졌다
B-14 capability 테이블을 어디에 두고 어떻게 로드할지 TS 상수 SSoT 에 JSON 생성물 (5절)
E-2 폴백 카테고리 테이블 3절이 그 테이블이다
I-12 질의 쪽 중첩을 평탄화한 뒤 fallback 할지 구조를 유지할지 유지하고 N2 로 시도한 뒤 N3 으로 벗기고 마지막에만 평탄화한다 (3.6절)
B-30 measure↔matrix projection 규칙(P9, P10) 정의 규칙을 만들지 않는다 (2026-09-05). 단일 값과 복합 데이터는 형태가 달라 서로 변환하면 원래 의도를 왜곡한다. Gauge↔PictogramChart/BarChart fallback 엣지를 뺐다 (3.4절, 3.5절)
B-27 개수 상한 초과를 P8(접기)로 받을지 규칙 1(탈락)로 뺄지 규칙 1(탈락)로 확정 (2026-09-05). 접으면 overflow가 눈에 띄고, 탈락하면 상한 없는 안전망까지 계속 내려가 정보도 안 사라진다. P8 규칙 폐기 (3.4절, 3.5절)

7. RLSC 대응은 별도 확인이 필요하다#

schema-v0.4.md 3절의 옛 11행 표에는 RLSC type 과 슬롯이 소비되는 Role 열이 있었다. 41종으로 늘리면서 이 두 열은 채우지 않았다. RLSC LayoutElementKeyType 이 41종을 다 갖고 있지 않기 때문이다. 옛 표에서 이미 big_number, quote, dialogue 셋이 '갭'(전용 컨테이너 없음)이었고, 늘어난 30종 중 상당수가 같은 상태일 것으로 보인다.

이 대응은 실물 어휘(src/app/pipeline/common/type/layoutStructure.ts)를 다시 읽어 채워야 한다. 이 표의 성립 조건과 fallback 순서는 RLSC 대응과 무관하게 성립하므로, 대응 확인을 기다리지 않고 규약에 넣는다.


8. 확인이 필요한 것#

이 표에서 근거가 실측이 아닌 것들이다.

항목 지금 값 무엇을 확인하면 되나
실측 없는 개수 조건 35종 단어장 정의문에서 유도 색인 쪽 실데이터에서 카테고리별 실제 항목 수 분포를 재면 상한을 근거 있게 정할 수 있다
fallback 상한 폐지됨 2026-08-17 결정 (fallback map v2 원칙 4). 종전 "최대 두 번" 규칙은 실측 근거 없는 설계 판단이었다. schema-v0.5.md 규약 5절 2번도 동기화했다
개수 상한 초과를 P8 로 접을지 규칙 1 로 탈락시킬지 표 안에서는 P8 로 통일했다 닫힘 (B-27, 2026-09-05). 규칙 1(탈락)로 확정. 접기(P8)는 정보를 안 버려도 마지막 항목에 텍스트가 몰려 overflow가 나기 쉽고, 탈락은 사다리를 계속 타고 내려가 상한 없는 안전망(List 등)에 안착하므로 정보도 안 사라지고 렌더링도 안 깨진다. P8 규칙은 폐기하고 표 전체(16개 엣지)에서 뗐다
RelationalComparison_VennDiagram 상한 3 jari 실측 jari 는 스마트 다이어그램 엔진이고, RLSC VennDiagram 은 다른 렌더 경로다. RLSC 쪽 상한이 다를 수 있다
fallback 순서의 카테고리 선택 45건 설계 판단 7/24 Next action "폴백 카테고리 테이블 정리, 유형 간 관계도로 스코어링 가능성 검토"의 대상이다. 45건 전량이 2026-08-17에 스쿼드(정원석)·사용자 결정으로 확정됐다. 김수현·류민 전원 확인은 아직 없다
RLSC 대응 미기입 7절
장식 축 열이 없다 이 표에 장식 언급이 0건이다 2026-08-03 에 장식을 축 둘(ornament, background)로 두기로 정했고(../design/decoration-model.md 4절), 그중 카테고리 기본값을 41종 전량에 채우는 것이 이 표의 몫이다. 예를 들어 Enumeration_List 는 background: prefer 다. 규약 표기가 확정되면 채운다 (../TODO.md B-18, B-21)

10. Native Complex Element/non-Native Complex Element 판정 (B-36)#

제안 상태다 — 정원석 초안, 스쿼드 확인 대기. 검색·대치가 "실제 엔진 컴포넌트"(Native Complex Element)인지, "디자이너가 도형·선·텍스트로 손수 그린 것"(non-Native Complex Element, VStack/HStack/ZStack/Group/Grid/Frame 같은 범용 컨테이너로만 이뤄진 것)인지에 따라 대치 가능 여부가 갈리는 카테고리가 있다.

판정 기준이 2026-09-04에 한 번 좁혀졌다. 처음에는 RLSC LayoutElementKeyType 이 Chart/Table/VennDiagram/LayeredDiagram/DisjointEulerDiagram/Quadrant/Gantt/Graph 처럼 도메인 전용 이름을 갖고 있으면 Native 로 봤는데, 실물 코드를 보니 이 이름들 중 상당수가 진짜 native 노드가 아니라 RLSC 가 도형 배치를 보고 붙이는 구조 패턴 태그였다.

  • 원본 미리캔버스 페이지 JSON 의 실제 노드 타입(pageJson.ts 의 NodeType)은 ChartItem, TableItem, GaugeItem 등이고, SmartDiagramItem(벤·트리·계단·타임라인·원형·허브형을 담는 미리캔버스 native 컴포넌트)이 이 목록에 아예 없다. RLSC 추출 파이프라인이 그 노드 타입 자체를 모른다.
  • custom-rlsc-type-remap-from-sheet.fixtures.ts 에서 GaugeItem(진짜 원본 노드) → RLSC Chart 타입으로 사람이 만든 remap 표가 실제로 쓰이는 것을 확인했다 — Chart/Table 은 원본 native 노드에 근거해서 붙는다는 증거다.
  • 반대로 VennDiagramLayoutAttributeSchema, LayeredDiagramLayoutAttributeSchema, DisjointEulerDiagramLayoutAttributeSchema, GraphLayoutAttributeSchema(relations/mermaidType 뿐), GanttLayoutAttributeSchema, QuadrantLayoutAttributeSchema(axisLabels 뿐, 항목별 좌표 없음)는 전부 값→기하 재계산에 쓸 데이터가 스키마에 없다(layoutStructure.ts 302~499행). 미리캔버스 엔진에 대응하는 native 컴포넌트(Smart Diagram)가 실제로 있어도, RLSC 가 그 노드 타입을 못 알아보므로 이 프로젝트가 쓰는 경로 안에서는 native 신호로 못 쓴다.

그래서 진짜 Native Complex Element 는 Chart(차트류 + Gauge, 원본 ChartItem/GaugeItem 노드에 근거)와 Table(원본 TableItem 노드에 근거) 딱 둘뿐이다. 나머지는 원본에 대응 노드가 없거나(Venn/Layered/DisjointEuler/Quadrant/Gantt — Smart Diagram/전용 노드가 RLSC 미검출), 있어도 RLSC 가 못 본다.

판정 기준(불변). 대치가 텍스트·소규모 위상 치환이면 Native Complex Element 여부가 안 갈린다. 대치가 실측값을 길이·각도·좌표·면적 같은 연속 기하로 바꿔야 하면, non-Native Complex Element 후보는 원래 그려진 숫자에 기하가 고정돼 있어 새 값을 반영할 수 없다.

이 문서에서 각 카테고리를 네 가지 상태 중 하나로 분류한다. 표의 "상태" 열에 아래 이름을 그대로 적는다.

상태 이름 뜻 검색·대치 규칙(제안)
둘 다 가능 대치가 텍스트나 작은 위상 수준이라 non-Native Complex Element(손그림)로도 신뢰 가능하다. Native Complex Element 대응이 있는지 없는지는 상관없다 후보 제한 없음
Native 필요, 있음 실측값을 연속 기하로 그려야 하고, 그걸 뒷받침하는 엔진(Chart/Table)이 실제로 있다 non-Native Complex Element 후보는 배제하거나 랭킹에서 크게 감점
Native 필요, 영구 없음 실측값을 연속 기하로 그려야 하는데, 그걸 뒷받침할 엔진이 RLSC 에도 미리캔버스 Smart Diagram 에도 어디에도 없다 10.1절의 "생성기 어휘 제외" 규칙 적용
Native 필요, 지금은 없음(구현 대기) 실측값을 연속 기하로 그려야 하는데, 뒷받침할 엔진은 실제로 있고 그 엔진에 데이터를 넣는 연결 코드(어댑터, B-13)만 아직 없다 어댑터가 나올 때까지는 위와 같지만, 나오면 재검토 없이 "Native 필요, 있음"으로 옮긴다

10.1 대치가 안 되는 카테고리는 생성기 어휘에서 뺀다 (2026-09-04 정원석 확정 · 2026-09-11 아홉으로 개정)#

"Native 필요, 영구 없음"과 "Native 필요, 지금은 없음"은 I.I.S. 컨텍스트에는 남기고, Visual Summary 생성기 어휘에서는 뺀다. 두 층의 역할이 다르기 때문이다.

  • I.I.S.(4·5단계, 검색·색인 쪽): 이미 존재하는 실제 컴포넌트를 구조적으로 인식하는 일이다. 네이티브가 없어도 "이 도형 배치가 Quadrant 처럼 생겼다"는 패턴 인식은 여전히 유효하고 검색·매칭에 필요하다. 뺄 이유가 없다.
  • Visual Summary 생성기(질의 쪽, 신규 structure 를 쓰는 LLM): 없는 걸 만들어 달라고 요청하면 대치가 원천적으로 불가능한 요청을 만들어내는 셈이다. type 어휘에서 이 카테고리들을 빼서 생성기가 아예 못 고르게 막는다.

RLSC 자체 태그 유무는 기준이 아니다. I.I.S. 는 RLSC LayoutElementKeyType 어휘와 무관하게 구조를 독립적으로 추론할 수 있으므로, RLSC 가 그 카테고리에 대응하는 type 을 안 갖고 있다는 사실만으로는 대치가 막히지 않는다. 진짜 기준은 그 semantic category 를 뒷받침할 엔진이 어디에든(RLSC Chart/Table 이든, 미리캔버스 Smart Diagram vizType 이든) 존재하는가다. 존재하는데 규약(어댑터, B-13)만 아직 없으면 엔진은 있지만 연결 코드가 없는 상태이고, 아무 엔진도 없으면 엔진 자체가 없는 상태다.

엔진 자체가 없는 여섯(2026-09-04 정원석 확정) — 생성기 어휘에서 뺀다

카테고리 왜 안 되나
RelationalComparison_Quadrant 원본 노드 없음, Smart Diagram vizType 6종에도 없음, RLSC 스키마엔 axisLabels 뿐 항목별 좌표 없음. 지금 수단으로는 못 만든다
TimelineSchedule_GanttChart 원본 노드 없음, Smart Diagram vizType 에도 ChartType 에도 없음
TimelineSchedule_Calendar 원본 노드도 Smart Diagram vizType 도 없음. 달마다 시작 요일과 주 수가 달라 요일-날짜 정렬을 매번 계산해야 하는데, 그 계산을 해줄 엔진이 없다
ProportionComposition_TreeMap 원본 노드도 Smart Diagram vizType 도 ChartType 도 없음. 값 → 면적 비율은 Native Complex Element 없이는 어렵다
ProportionComposition_PictogramChart 원본 노드도 Smart Diagram vizType 도 ChartType 도 없음. 값 → 아이콘 반복 수는 Native Complex Element 없이는 어렵다
RelationalComparison_BubbleChart 원본 노드도 Smart Diagram vizType 도 ChartType 도 없음. 축(axis)과 버블 크기까지 있어 Native Complex Element 없이는 어렵다

둘 다 가능으로 내린 것 — Conversion_Funnel(2026-09-04 정원석 판단). 처음엔 이 다섯과 같이 묶었는데, Funnel 은 List 와 같은 종류다 — 단계 수가 적고 순서만 맞으면 되는 카드형이라 conversionRate 폭 비례가 정확하지 않아도 손그림으로 충분하다. Native Complex Element 가 없어도 대치된다. 45종 표(아래) 14행 참고.

필터링을 어디서 걸지 (B-36/I-16 구현 설계, 2026-09-05 정원석 확정 · 2026-09-11 목록 개정). capability 스키마를 생성기용/I.I.S.용으로 나누지 않는다. 대신 capability 스키마 자체에 카테고리별 generatable: boolean 필드를 심는다(schema/capability.json 에 반영). generatable: false 는 아홉이다 — 엔진 자체가 없는 여섯 + HierarchicalTree + 아래 첫 출시 공급 제외 둘(Gauge, RadarChart). 나머지 36종은 generatable: true 다. Visual Summary 생성기는 프롬프트 조립 시 generatable === false 인 카테고리를 type 어휘에서 제외하고, 검증기도 같은 목록을 재시도 사유로 본다. I.I.S. 쪽(iis/extractBoundary.ts, nodeSearch.ts)은 이 필드를 참조하지 않고 45종 전량을 그대로 본다 — 스키마는 하나, 소비하는 쪽만 갈린다. /comp-frame 검색 불허 배지(iui.semantic_tag_policy)도 같은 아홉 행이다.

엔진은 있지만 연결 코드가 없는 하나

카테고리 근거
Hierarchy_HierarchicalTree Smart Diagram vizType 에 hierarchical 이 실제로 있고, smart-diagram-data-model.md 10절 실측상 "렌더+저장 모두 되는 둘뿐인 타입"(tree, venn) 중 하나다. B-13(dataset→dataVizProps 어댑터)가 아직 없어서 지금은 신뢰 가능한 합성이 안 될 뿐 — 그 어댑터가 나오면 재검토 없이 생성기 어휘에 복귀시킨다

첫 출시 공급 제외 둘 (2026-09-11 정원석 확정) — 엔진은 있어도 생성기 어휘에서 뺀다. 상태는 「Native 필요, 있음」 그대로다. remap(GaugeItem → BAR_GAUGE/CIRCLE_GAUGE, ChartItem → RADAR_CHART)이 있어서 표를 「영구 없음」으로 내리면 안 된다. 다만 Visual Summary 첫 출시에서는 이 둘을 생성기가 고르지 못하게 한다. fallback 을 염두에 두고 생성하기보다 애초 생성 가능 범위에서 빼는 편이 유형 결정에 낫다(정원석). 스마트 다이어그램 대응이 가능해지면 재검토 없이 생성기 어휘에 복귀시킨다. 검색·I.I.S. 추출 어휘는 45종 전량을 유지한다.

카테고리 왜 지금 빼나 생성기가 대신 고르는 자리 (정본 3.2절 첫 후속이 아님을 명시)
KPIKeyMetric_Gauge Native Chart 풀이 비어 있다(2026-09-11 운영 I.I.S. 실측: 라벨 38·DO 38, structure_json 에 "type":"Chart" 0). 손그림 게이지를 만들어 달라고 하면 대치가 원천적으로 안 된다 KPIKeyMetric_BigNumber (3.2절 첫 후속과 같음)
AttributeComparison_RadarChart Native Chart 풀이 비어 있다(같은 실측: 라벨 1·DO 1, RADAR_CHART 0). 다축을 BarChart 로 내리면 축이 붕괴한다 AttributeComparison_ComparisonTable (3.2절 첫 후속은 BarChart 이나, 생성기 재지정만 이 예외)

재지정을 기계가 읽는 자리 (2026-09-11). 생성기가 뺀 자리에 대신 고르는 카테고리는 기본이 3.2절 첫 후속이다 — PictogramChart→BarChart, TreeMap·BubbleChart→PieChart, Quadrant→ComparisonTable, GanttChart·Calendar→Timeline, HierarchicalTree→Pyramid, Gauge→BigNumber. 예외는 RadarChart 하나이고 그 한 줄만 schema/capability.json 의 generatorRedirect 로 적었다. 소비하는 쪽은 generatorRedirect ?? fallback[0] 으로 읽는다. 3절 fallback 배열 자체는 검색용이라 이 필드가 바꾸지 않는다 — json 만 보고 첫 후속을 그대로 쓰면 RadarChart 가 BarChart 로 가 축이 붕괴하므로, 생성기 쪽 소비자는 이 필드를 반드시 함께 본다.

남는 일: 생성기 어휘 필터(아홉)는 iui /comp-frame V2(NON_GENERATABLE_CATEGORIES)와 capability.json generatable 에 반영됐다. HierarchicalTree 는 B-13 완료 시, Gauge·RadarChart 는 스마트 다이어그램 대응이 가능해지면 각 한 줄을 generatable: true 로 되돌린다. I.I.S. 어휘는 계속 45종이다.

# 카테고리 Native Complex Element 대응 상태 생성기 허용 (9월 말 기준) 근거
1 Enumeration_List 없음 둘 다 가능 허용 텍스트 나열만. 질의 최대 수요라 애초에 전량 non-Native Complex Element 로 돌고 있다
2 Enumeration_ListingTable Table(원본 TableItem) 둘 다 가능 허용 matrix 는 셀 텍스트일 뿐, 1:1 텍스트 치환이면 충분
3 AttributeComparison_Versus 없음 둘 다 가능 허용 좌우 배치 텍스트
4 AttributeComparison_ComparisonTable Table(원본 TableItem) 둘 다 가능 허용 2 와 같음
5 RelationalComparison_SWOT 없음 둘 다 가능 허용 4칸 고정 텍스트
6 RelationalComparison_VennDiagram 없음(미리캔버스엔 Smart Diagram venn 있으나 RLSC 미검출) 둘 다 가능 허용 원 2~3개 상한이라 손그림도 신뢰 가능하다
7 RelationalComparison_Quadrant 없음 Native 필요, 영구 없음 불허 (9/30 dev/stg 판정 전까지 생성기가 못 고르게 막는다) 항목별 x, y 좌표가 필요한데 원본 노드도 RLSC 필드도 없다
8 Transformation_Transformation 없음 둘 다 가능 허용 좌우 대비 텍스트
9 ProcessFlow_Steps 없음 둘 다 가능 허용 순서 있는 텍스트 카드
10 TimelineSchedule_Timeline 없음 둘 다 가능 허용 period 라벨 텍스트, 크기 스케일 없음
11 Progression_Staircase 없음 둘 다 가능 허용 순서 카드
12 Cycle_Cycle 없음 둘 다 가능 허용 순환 배치 카드
13 LogicFlow_FlowChart 없음 둘 다 가능 허용 분기 라벨 텍스트. LayoutElementKeyType.Graph 는 relations(from/to)·mermaidType 을 기록하는 메타데이터 컨테이너일 뿐, 값에서 위치를 다시 계산하는 엔진이 아니다(layoutStructure.ts:392). 그래도 항목 수 작아 손그림 무난
14 Conversion_Funnel 없음 둘 다 가능 허용 2026-09-04 정원석 판단: List 처럼 단계 수가 적고 순서만 맞으면 되는 카드형이라, conversionRate 폭 비례가 정확하지 않아도 손그림으로 충분하다. Native Complex Element 없이도 된다
15 TimelineSchedule_GanttChart 없음 Native 필요, 영구 없음 불허 (9/30 dev/stg 판정 전까지 생성기가 못 고르게 막는다) 시작·끝 날짜 → 막대 위치가 필요한데 원본 노드도 RLSC 필드도 없다(GanttLayoutAttributeSchema 는 빈 스키마)
16 TimelineSchedule_Calendar 없음 Native 필요, 영구 없음 불허 (9/30 dev/stg 판정 전까지 생성기가 못 고르게 막는다) 2026-09-04 정정: 달마다 시작 요일과 주 수가 달라 요일-날짜 정렬은 매번 계산해야 하는 실측 기하다. 고정 격자 하나로는 새 달 데이터를 못 받는다. Smart Diagram vizType 에도, pageJson.ts NodeType 에도, RLSC 에도 대응이 없어 Quadrant/GanttChart 와 같은 부류다
17 Hierarchy_HierarchicalTree Smart Diagram vizType: hierarchical(엔진엔 있음, RLSC 미검출) Native 필요, 지금은 없음 불허 (9/30 dev/stg 판정 전까지 생성기가 못 고르게 막는다) 임의 깊이·분기 자동배치가 필요. 엔진은 지원하는데 합성 규약(B-13)이 아직 없다 — 영구 없음이 아니라 구현 대기
18 Hierarchy_Pyramid 없음(jari 머신도 없음) 둘 다 가능 허용 weight 선택이고 층 수 적어 손그림도 무난
19 ScopeCoverage_NestedScope 없음(jari 미지원) 둘 다 가능 허용 항목 적고 direction/shape 뿐이라 손그림 무난
20 ReasoningStructure_Fishbone 없음 둘 다 가능 허용 Graph 는 13행과 같은 이유로 Native Complex Element 가 아니다. 가지 수 작아 손그림 무난
21 ReasoningStructure_CombinedResult 없음 둘 다 가능 허용 텍스트
22 ReasoningStructure_ProblemSolution 없음 둘 다 가능 허용 텍스트 쌍
23 KPIKeyMetric_BigNumber 없음 둘 다 가능 허용 큰 텍스트일 뿐, 길이·각도 인코딩 없음
24 KPIKeyMetric_Gauge Chart(원본 GaugeItem → remap, BAR_GAUGE/CIRCLE_GAUGE) Native 필요, 있음 불허 (첫 출시 공급 제외, 2026-09-11) 값 → 바늘·호 각도. 엔진 remap 은 있으나 운영 I.I.S. native Chart 풀이 비어 생성기가 고르면 대치 불능 질의가 된다
25 MagnitudeRanking_BarChart Chart(원본 ChartItem, BAR_CHART_VER/HOR) Native 필요, 있음 허용 값 → 막대 길이
26 ProportionComposition_StackedBarChart Chart(원본 ChartItem + isStacked) Native 필요, 있음 허용 값 → 막대 길이, 누적
27 ProportionComposition_PieChart Chart(원본 ChartItem, PIE_CHART) Native 필요, 있음 허용 값 → 각도
28 ProportionComposition_DonutChart Chart(원본 ChartItem, DONUT_CHART) Native 필요, 있음 허용 값 → 각도
29 ProportionComposition_TreeMap 없음(ChartType 에도 없다) Native 필요, 영구 없음 불허 (9/30 dev/stg 판정 전까지 생성기가 못 고르게 막는다) 값 → 면적. 원본 노드도 ChartType 도 없다
30 ProportionComposition_PictogramChart 없음 Native 필요, 영구 없음 불허 (9/30 dev/stg 판정 전까지 생성기가 못 고르게 막는다) 값 → 아이콘 반복 수. ChartType 에 없다
31 Trend_LineAreaChart Chart(원본 ChartItem, LINE_CHART/AREA_LINE_CHART) Native 필요, 있음 허용 값 → 선 높이
32 Trend_ComboChart Chart(원본 ChartItem, COMBO_CHART) Native 필요, 있음 허용 값 → 막대+선 혼합
33 AttributeComparison_RadarChart Chart(원본 ChartItem, RADAR_CHART) Native 필요, 있음 불허 (첫 출시 공급 제외, 2026-09-11) 값 → 축별 반경. 엔진은 있으나 운영 I.I.S. native Chart 풀이 비어 있고, 생성 재지정은 ComparisonTable(다축 보존). BarChart 로 내리면 축이 붕괴한다
34 RelationalComparison_BubbleChart 없음 Native 필요, 영구 없음 불허 (9/30 dev/stg 판정 전까지 생성기가 못 고르게 막는다) 값 → x, y, 크기. ChartType 에 없다
35 TextBasedCommunication_HighlightedMessage 없음 둘 다 가능 허용 텍스트
36 TextBasedCommunication_LongFormTextLayout 없음 둘 다 가능 허용 텍스트
37 QnACard_QnACard 없음 둘 다 가능 허용 텍스트 쌍
38 Utterance_DialogueChat 없음 둘 다 가능 허용 텍스트
39 Utterance_QuoteTestimonial 없음 둘 다 가능 허용 텍스트
40 PortraitProfile_ProfileCard 없음 둘 다 가능 허용 텍스트
41 PortraitProfile_ResumeCV 없음 둘 다 가능 허용 텍스트
42 PageNavigation_Opening 없음 둘 다 가능 허용 텍스트
43 PageNavigation_TableOfContents 없음 둘 다 가능 허용 텍스트
44 PageNavigation_SectionDivider 없음 둘 다 가능 허용 텍스트
45 PageNavigation_Ending 없음 둘 다 가능 허용 텍스트

남는 확인

  1. "Native 필요, 있음"인 카테고리를 검색에서 하드 배제할지 랭킹 감점만 할지 — 정해야 실제 필터 규칙이 나온다.
  2. 생성기 어휘에서 빼는 목록은 아홉이다 (2026-09-11). 엔진 없음 여섯 + HierarchicalTree + 첫 출시 공급 제외 Gauge·RadarChart. Funnel 은 재검토 결과 여기 없다(위 14행). I-16 의 generatable 필드는 capability.json 에 심었다. 3절 fallback 사다리는 검색용으로 그대로 두고, 생성기만 Gauge→BigNumber, RadarChart→ComparisonTable 로 재지정한다.
  3. Venn/Pyramid/NestedScope/Fishbone/FlowChart 는 항목 수가 작아 버킷 1 로 유지했다 — 실사용에서 항목 수가 늘어나는 사례가 나오면 재검토.

9. 참고#

↑ 목차로 · 2부 머리로

6장YAML 예시 정본질의 상위 9종과 안티패턴 5종

원문 spec/yaml-examples.md GitHub 에서 보기 14 KB 원문 제목 「Visual Summary YAML 예시 (v0.5)」

이 문서의 역할 사용자 입력을 Visual Summary YAML 로 옮긴 예시 모음이다. 규약 본문은 schema-v0.5.md 이고, 이 문서는 그 규약을 실제 입력에 적용한 모습을 보인다. 카테고리 선정은 질의 쪽 실사용 분포를 따랐다.

모든 예시는 규약 스키마 검증을 통과한다 (2026-08-02 확인). 7절의 안티패턴만 의도적으로 통과하지 않는다. entity.id 는 생성물에 없다. 파싱 뒤 기계가 매긴다 (규약 3.2절).

오너 정원석 (MORDOR) · 상태 확정 · 최종 갱신 2026-08-02

관련 schema-v0.5.md 규약 · capability-table.md 41종 성립 조건 · markdown-summary-samples.md 현행 Markdown 실사용 샘플 · README.md 허브

이 문서 훑기

절 무엇을 보나
0 v0.4 예시에서 표기가 무엇이 바뀌었나
1 질의의 85% 를 덮는 아홉 종을 어떻게 적나
2 measure 와 matrix 를 어떻게 적나
3 intersects 와 compares 를 어떻게 적나
4 한 페이지에 컴포넌트가 여럿일 때
5 대신 쓸 카테고리와 표현 선택 속성을 어떻게 적나
6 후보가 없을 때 결과가 어떻게 되나
7 이렇게 쓰면 안 되는 다섯

0. v0.4 예시와 무엇이 다른가#

이 문서의 이전 판은 v0.4 기준이었다. 표기가 넷 바뀌었다.

v0.4 v0.5
component: { type: list } 와 최상위 structure components: 배열 안에 type 과 structure
type: list, type: steps 같은 단층 값 type: Enumeration.List 처럼 대분류.중분류
entity 마다 id 필수 id 를 적지 않는다. 파싱 뒤 기계가 매긴다 (규약 3.2절)
dataset: { type: gauge, ... } dataset: { shape: measure, ... }
dataset: { type: stacked_bar, dimensions, series } dataset: { shape: matrix, columns, rows }

이전 판의 예시 중 셋(steps, timeline, versus)은 당시 enum 에 없는 값을 써서 스키마 검증을 통과하지 못했다. v0.5 에서 어휘가 41종으로 늘면서 이 문제가 없어졌다.


1. 질의 상위 카테고리#

semantic-taxonomy-v7.md 3절의 실사용 빈도 순이다. 이 아홉이 질의의 약 85% 를 덮는다.

1.1 List (31.96%)#

입력: '효과적인 회의를 위한 원칙은 셋이다. 명확한 아젠다, 시간 관리, 액션 아이템.'

yaml
version: "0.5.4"
title: 효과적인 회의를 위한 3가지 원칙
components:
  - type: Enumeration.List
    structure:
      entities:
        - { label: 명확한 아젠다, items: [회의 전 안건 공유, 목표 한 문장 정의] }
        - { label: 시간 관리, items: [시작과 종료 시간 엄수, 항목별 타임박스] }
        - { label: 액션 아이템, items: [담당자 지정, 마감일 명시] }

컴포넌트가 하나여도 배열이다. List 는 마지막 fallback 이라 alternatives 를 적을 이유가 없다.

1.2 ListingTable (15.08%)#

입력: '요금제별 월 요금과 저장 용량, 지원 방식을 정리했다.'

yaml
version: "0.5.4"
title: 요금제 안내
components:
  - type: Enumeration.ListingTable
    structure:
      dataset:
        shape: matrix
        columns:
          - { label: 요금제, role: category }
          - { label: 월 요금, unit: 원 }
          - { label: 저장 용량 }
          - { label: 지원 }
        rows:
          - [베이직, 9900, 10GB, 이메일]
          - [프로, 19900, 100GB, 전화와 이메일]
          - [엔터프라이즈, 49900, 무제한, 전담 매니저]

첫 열이 범주 축이다. 이 규약이 없으면 생성기가 행과 열을 바꿔 적어도 검증이 통과한다.

1.3 Steps (10.92%)#

입력: '온보딩은 4단계다. 계정 생성, 프로필 설정, 첫 프로젝트, 팀 초대.'

yaml
version: "0.5.4"
title: 온보딩 4단계
components:
  - type: ProcessFlow.Steps
    structure:
      entities:
        - { label: 계정 생성, items: [이메일 인증] }
        - { label: 프로필 설정, items: [팀과 역할 선택] }
        - { label: 첫 프로젝트, items: [템플릿 선택] }
        - { label: 팀 초대, items: [권한 부여] }

순서는 배열 순서가 담는다. params.sequence 를 따로 적지 않아도 된다.

1.4 ComparisonTable (7.24%)#

입력: '기존 방식과 새 방식을 처리 시간, 오류율, 비용 기준으로 비교했다.'

yaml
version: "0.5.4"
title: 방식별 비교
components:
  - type: AttributeComparison.ComparisonTable
    structure:
      dataset:
        shape: matrix
        columns:
          - { label: 기준, role: category }
          - { label: 기존 방식 }
          - { label: 새 방식 }
        rows:
          - [처리 시간, 4시간, 20분]
          - [오류율, 12%, 1%]
          - [월 비용, 320만원, 90만원]

ListingTable 과 같은 모양이다. 차이는 데이터가 아니라 표현이고, 표현은 type 이 정한다.

1.5 HighlightedMessage (5.75%)#

입력: '결론은 하나다. 자동화 없이는 목표 달성이 불가능하다.'

yaml
version: "0.5.4"
title: 결론
components:
  - type: TextBasedCommunication.HighlightedMessage
    structure:
      entities:
        - { label: 자동화 없이는 목표 달성이 불가능하다, emphasis: true }

entities 가 하나이고 한 문장이라 이것이 마지막 fallback 이다. 더 내려갈 곳이 없다.

1.6 LongFormTextLayout (4.77%)#

입력: 여러 문장으로 이어지는 배경 설명.

yaml
version: "0.5.4"
title: 프로젝트 배경
components:
  - type: TextBasedCommunication.LongFormTextLayout
    structure:
      entities:
        - label: 왜 지금 자동화인가
          items:
            - 지난 3년간 처리량이 4배로 늘었지만 인원은 그대로였다.
            - 수작업 검수에 하루 4시간이 들어가고 오류율이 12%에 이른다.
            - 같은 방식으로 내년 물량을 감당할 수 없다는 것이 확인됐다.

문장을 항목으로 쪼개도 마지막 fallback 이 LongFormTextLayout 이면 본문 흐름으로 그린다.

1.7 Timeline (4.27%)#

입력: '2021년 창업, 2023년 시리즈 A, 2025년 100만 유저.'

yaml
version: "0.5.4"
title: 회사 연혁
components:
  - type: TimelineSchedule.Timeline
    structure:
      entities:
        - { label: 창업, period: "2021", items: [시드 투자 유치] }
        - { label: 시리즈 A, period: "2023", items: [해외 진출] }
        - { label: 100만 유저, period: "2025" }

period 는 entity 필드다. 연도는 내용이지 표현 선택이 아니다.

1.8 Versus (3.11%)#

입력: '기존 방식은 수작업이라 느리고 오류가 많다. 새 방식은 자동화로 빠르고 정확하다. 수작업에서 자동화로 전환하는 것이 핵심이다.'

yaml
version: "0.5.4"
title: 프로세스 전환
components:
  - type: AttributeComparison.Versus
    structure:
      entities:
        - { label: 기존 방식, items: [수작업, 느림, 오류 많음] }
        - { label: 새 방식, items: [자동화, 빠름, 정확] }
      relations:
        - type: compares
          of: [기존 방식, 새 방식]
          desc: 수작업에서 자동화로 전환하는 것이 핵심

마지막 문장은 두 항목 중 어디에도 속하지 않는다. 그래서 관계로 적는다. 이것이 관계 어휘를 만드는 유일한 기준이다.

1.9 BarChart (1.67%)#

입력: '채널별 연간 매출은 온라인 67억, 오프라인 34억, 파트너 18억이다.'

yaml
version: "0.5.4"
title: 채널별 연간 매출
components:
  - type: MagnitudeRanking.BarChart
    structure:
      dataset:
        shape: matrix
        columns:
          - { label: 채널, role: category }
          - { label: 매출, role: value, unit: 억원 }
        rows:
          - [온라인, 67]
          - [오프라인, 34]
          - [파트너, 18]

BarChart 는 차트 계열이 거쳐 가는 자리다. 다른 차트가 후보를 못 찾으면 여기로 온다.


2. dataset 두 모양#

2.1 measure: 값 하나와 기준#

입력: '3분기 목표 달성률은 65%다. 목표는 80%이고 50% 아래면 주의 구간이다.'

yaml
version: "0.5.4"
title: 3분기 목표 달성률
components:
  - type: KPIKeyMetric.Gauge
    structure:
      entities:
        - { label: 목표 달성률, value: 65, unit: "%" }
      dataset:
        shape: measure
        value: 65
        unit: "%"
        min: 0
        max: 100
        target: 80
        thresholds:
          - { at: 50, state: warn, label: 주의 }
          - { at: 80, state: safe, label: 목표선 }

entities 의 헤드라인은 fallback될 때 쓴다. Gauge 후보가 없으면 fallback 순서가 BigNumber 로 내려가고, 그때 이 entity 가 그대로 쓰인다.

max 를 안 적으면 기본값 100 이고 min 은 0 이다. target 과 thresholds 도 선택이다.

2.2 matrix: 행렬#

입력: '분기별 채널 매출 구성이다. 온라인이 12, 15, 18, 22억으로 늘었고 오프라인과 파트너는 완만하다.'

yaml
version: "0.5.4"
title: 분기별 채널 매출 구성
components:
  - type: ProportionComposition.StackedBarChart
    structure:
      dataset:
        shape: matrix
        columns:
          - { label: 분기, role: category }
          - { label: 온라인, role: value, unit: 억원 }
          - { label: 오프라인, role: value, unit: 억원 }
          - { label: 파트너, role: value, unit: 억원 }
        rows:
          - [1분기, 12, 8, 3]
          - [2분기, 15, 7, 4]
          - [3분기, 18, 9, 5]
          - [4분기, 22, 10, 6]

2.3 matrix 로 이중 축을 적을 때#

입력: '월별 매출과 성장률이다. 매출은 억원 단위 막대, 성장률은 퍼센트 선으로 본다.'

yaml
version: "0.5.4"
title: 월별 매출과 성장률
components:
  - type: Trend.ComboChart
    structure:
      dataset:
        shape: matrix
        columns:
          - { label: 월, role: category }
          - { label: 매출, role: value, unit: 억원, axis: left, type: bar }
          - { label: 성장률, role: value, unit: "%", axis: right, type: line }
        rows:
          - ["1월", 10, 0]
          - ["2월", 12, 20]
          - ["3월", 11, -8]
          - ["4월", 14, 27]

axis 와 type 이 열 정의에 붙는다. ComboChart 의 성립 조건이 '계열 type 2종 이상 또는 axis 2종 이상'인데 여기서는 둘 다 만족한다.

2.4 matrix 로 좌표를 적을 때#

입력: '제품별 시장 점유율과 성장률로 포지션을 봤다.'

yaml
version: "0.5.4"
title: 제품 포트폴리오
components:
  - type: RelationalComparison.Quadrant
    params: { axisLabels: { x: 시장 점유율, y: 성장률 } }
    structure:
      dataset:
        shape: matrix
        columns:
          - { label: 제품, role: category }
          - { label: 시장 점유율, role: x, unit: "%" }
          - { label: 성장률, role: y, unit: "%" }
        rows:
          - [A제품, 34, 8]
          - [B제품, 12, 41]
          - [C제품, 45, -3]

role 이 x 와 y 를 밝힌다. 축 이름은 표현 선택이라 params 에 있다.


3. 관계#

관계 어휘는 둘뿐이다. 기준은 구조로 적을 수 없는 제3의 내용이 있는가이다.

3.1 intersects#

yaml
version: "0.5.4"
title: 장수기업 HR의 핵심 과제
components:
  - type: RelationalComparison.VennDiagram
    structure:
      entities:
        - { label: 전통, items: [유산, 정체성, 신뢰] }
        - { label: 변화, items: [혁신, 적응, 미래] }
      relations:
        - type: intersects
          of: [전통, 변화]
          desc: 두 요소를 조화롭게 관리하는 것이 장수기업 HR의 핵심

desc 는 교집합 영역에 들어갈 텍스트다. 전통에도 변화에도 속하지 않는다.

3.2 compares 는 3항 이상도 받는다#

yaml
version: "0.5.4"
title: 클라우드 3사 비교
components:
  - type: AttributeComparison.ComparisonTable
    structure:
      entities:
        - { label: A사, items: [저렴, 리전 적음] }
        - { label: B사, items: [중간 가격, 리전 많음] }
        - { label: C사, items: [비쌈, 관리형 서비스 풍부] }
      dataset:
        shape: matrix
        columns:
          - { label: 기준, role: category }
          - { label: A사 }
          - { label: B사 }
          - { label: C사 }
        rows:
          - [가격, 저렴, 중간, 비쌈]
          - [리전 수, 적음, 많음, 많음]
          - [관리형 서비스, 보통, 보통, 풍부]
      relations:
        - type: compares
          of: [A사, B사, C사]
          desc: 가격과 리전 수는 반비례하고, 관리형 서비스는 가격에 비례한다

of 가 배열이라 항목 수에 제한이 없다. versus_left 와 versus_right 는 색인 쪽 렌더 슬롯 이름일 뿐이고 질의 스키마에 들어오지 않는다.

matrix 와 entities 가 둘 다 있는 이유가 둘이다. matrix 는 ComparisonTable 의 성립 조건이라 없으면 이 카테고리가 후보에서 빠진다(capability-table.md 2.1절). entities 는 relations[].of 가 가리킬 대상이고(규약 3.2절이 label 로 찾는다), fallback 으로 내려갈 때 쓰인다. 8.4절 Gauge 가 measure 와 entities 를 함께 두는 것과 같은 구조다.

이 예시가 드러내는 것 (2026-08-04). compares 를 성립 조건으로 선언한 카테고리는 AttributeComparison.Versus 하나이고 개수 조건이 '정확히 2' 다. 그래서 3항 이상 compares 는 스키마를 통과해도 지금 어느 카테고리도 그리지 못한다. 위 ComparisonTable 도 관계를 안 읽으므로 규약 1절 "선언되지 않은 것은 무시한다"에 걸려 desc 가 그냥 지나간다. ../TODO.md B-28 이 이 항목이다.


4. 다중 컴포넌트#

한 페이지가 성격이 다른 덩어리로 나뉘는 경우다.

yaml
version: "0.5.4"
title: 2025 사업 성과 요약
components:
  - type: KPIKeyMetric.BigNumber
    structure:
      entities:
        - { label: 총매출, value: 152, unit: 억원, emphasis: true }
        - { label: 전년 대비, value: 38, unit: "%" }

  - type: ProportionComposition.StackedBarChart
    structure:
      dataset:
        shape: matrix
        columns:
          - { label: 분기, role: category }
          - { label: 온라인, role: value, unit: 억원 }
          - { label: 오프라인, role: value, unit: 억원 }
        rows:
          - [1분기, 12, 8]
          - [2분기, 15, 7]
          - [3분기, 18, 9]
          - [4분기, 22, 10]

  - type: Enumeration.List
    structure:
      entities:
        - { label: 신규 시장 진출, items: [동남아 3개국] }
        - { label: 반복 구매 증가, items: [구독 전환율 상승] }

원소 수 상한은 없다. 컴포넌트 블록의 id 도 생성기는 채우지 않는다. page 수준 relations 가 블록을 가리킬 자리로 예약만 해 두었다.


5. alternatives 와 params#

5.1 대신 쓸 카테고리를 적어 두는 경우#

yaml
version: "0.5.4"
title: 매슬로 욕구 5단계
components:
  - type: Hierarchy.Pyramid
    params: { apex: last }
    alternatives:
      - Progression.Staircase
      - Enumeration.List
    structure:
      entities:
        - { label: 생리적 욕구 }
        - { label: 안전 욕구 }
        - { label: 사회적 욕구 }
        - { label: 존경 욕구 }
        - { label: 자아실현 }

apex 를 안 적으면 기본값 last 다. alternatives 도 선택이고, 적으면 검색 모듈이 fallback 순서보다 먼저 참고한다.

5.2 중분류를 못 정하겠을 때#

yaml
version: "0.5.4"
title: 조직 개편안
components:
  - type: Hierarchy.HierarchicalTree
    alternatives: [Hierarchy.Pyramid]
    structure:
      entities:
        - label: 개발본부
          children:
            - label: 프론트팀
              children:
                - { label: 웹파트 }
                - { label: 앱파트 }
            - { label: 백엔드팀 }

중분류는 생략할 수 없다 (2026-08-29, 0.5.2). 종전에는 Hierarchy 만 적고 검색이 그 안에서 고르게 할 수 있었는데, 대분류는 그릴 수 있는 표현형이 아니라 결합값을 묶어 부르는 축이라 type 값에서 뺐다(schema-v0.5.md 6절 개정 상자). 둘 중 어느 쪽인지 확신이 덜하면 하나를 고르고 나머지를 alternatives 에 적는다. 검색이 정적 fallback 순서보다 그것을 먼저 본다. 그래도 못 고르겠으면 종단 셋 중 하나를 고른다(규약 5절 3번).


6. fallback이 어떻게 보이나#

같은 structure 를 다른 카테고리가 받으면 무엇이 남고 무엇이 사라지는지다. 2.2 의 StackedBarChart 입력을 예로 든다.

어디까지 내려갔나 결과
StackedBarChart 분기별 누적 막대. 구성 비중이 보인다
BarChart (한 번 따라감) 채널별 합계 막대. 분기별 추이가 사라진다
List (두 번 따라간 자리, 마지막) 아래 나열
온라인 — 67억원
오프라인 — 34억원
파트너 — 18억원
(1분기부터 4분기까지 합계)

structure 는 한 글자도 바뀌지 않는다. 소비하는 쪽이 무엇을 읽느냐만 달라진다.


7. 안티패턴#

아래는 스키마가 거부하거나 규약에 어긋나는 예다. 생성기 프롬프트에 반례로 넣을 것들이다.

7.1 대분류와 중분류가 어긋난 조합#

yaml
# 거부된다
type: Hierarchy.BarChart

BarChart 는 MagnitudeRanking 소속이다. 스키마의 값 62개 목록에 이 조합이 없다.

7.2 최상위에 structure 를 바로 두는 것#

yaml
# 거부된다. v0.4 표기다
version: "0.5.4"
title: 제목
structure:
  entities: [...]

v0.5 는 항상 components: 배열이다.

7.3 값을 label 문자열에 남기는 것#

yaml
# 스키마는 통과하지만 규약에 어긋난다
entities:
  - { label: "총매출 152억원" }

value 와 unit 이 따로 있는 이유가 이것이다. 문자열에 남기면 검색과 대치가 숫자를 못 쓴다. 이렇게 적는다.

yaml
entities:
  - { label: 총매출, value: 152, unit: 억원 }

7.4 행과 열을 바꿔 적는 것#

yaml
# 스키마는 통과하지만 규약에 어긋난다
columns:
  - { label: 매출 }
  - { label: 1분기 }
  - { label: 2분기 }
rows:
  - [온라인, 12, 15]

첫 열이 범주 축이다. 위는 범주가 행에 가 있다. 이렇게 적는다.

yaml
columns:
  - { label: 분기, role: category }
  - { label: 온라인, role: value, unit: 억원 }
rows:
  - [1분기, 12]
  - [2분기, 15]

7.5 구조로 적을 수 있는 것을 관계로 적는 것#

yaml
# 거부된다. 어휘에 없다
relations:
  - { type: solves, of: [problem, solution] }

문제와 해결은 entity 하나가 담는다(label 이 문제, items 가 해결). 관계 어휘는 제3의 내용이 있을 때만 만든다.


8. 참고#

↑ 목차로 · 2부 머리로

7장표면 형식 대안 · KDL 검토 참고토큰 실측과 다시 볼 조건

원문 spec/format-alternatives.md GitHub 에서 보기 8 KB 원문 제목 「표면 형식 대안: KDL 검토」

이 문서의 역할 Visual Summary 의 표면 형식으로 KDL 을 쓸 경우의 표현 설계와 토큰 실측을 담는다. 규약(schema-v0.5.md)은 YAML 로 확정돼 있고, 이 문서는 그 결정을 다시 볼 때 쓰는 자료다. 예시는 kdl-examples.md 에 18종 전량이 있다.

왜 지금 다시 보나 2026-08-02 에 "LLM 이 YAML 을 직접 생성한다"로 정하면서 자동 검증이 사라졌다. structured output 을 쓰면 스키마 위반이 생성 단계에서 막히는데, 표면 형식을 직접 쓰게 하면 그 보호가 없다. 그래서 어느 형식을 쓰든 '표면을 JSON 으로 바꾸고 검증기를 돌리는' 수동 절차가 필요하다. 이 절차가 형식과 무관하게 같으므로, KDL 을 배제했던 근거 하나(파서와 검증 연결을 직접 만들어야 한다)의 무게가 줄었다.

오너 정원석 (MORDOR) · 상태 검토 자료 (결정 아님) · 최종 갱신 2026-08-02

관련 schema-v0.5.md 규약 · yaml-examples.md YAML 예시 · kdl-examples.md KDL 예시 18종 · ../README.md 2.1절 YAML 채택 근거 · ../TODO.md F-1


1. 검증 파이프라인은 형식과 무관하게 같다#

표면 형식을 LLM 이 직접 쓰는 이상, 검증은 아래 세 단계다.

LLM 생성 (표면)  →  파서로 JSON 변환  →  스키마 검증  →  실패하면 재생성
단계 YAML KDL
파서 js-yaml (이미 씀) kdljs 또는 동등물 (새로 넣어야 함)
JSON 변환 파서 출력이 곧 JSON 구조 노드 트리를 스키마 모양으로 매핑하는 코드가 필요하다
스키마 검증 zod 또는 JSON Schema 같음
재생성 같음 같음

차이는 두 번째 줄 하나다. YAML 은 파서 출력이 곧 목표 구조인데, KDL 은 노드와 인자와 속성으로 이루어진 트리라 스키마 모양으로 옮기는 매핑 코드를 써야 한다. 4절이 그 매핑이고, 실제로 구현해 검증까지 돌려 봤다(6절).

검증기는 zod 를 쓴다. 규약이 JSON Schema 로 적혀 있지만 소비 코드가 TypeScript 이므로, JSON Schema 를 SSoT 로 두고 zod 스키마를 생성물로 내보내는 방식이 capability 상수(semanticTaxonomy.gen.ts)와 같은 선례를 따른다. 이 결정은 형식 선택과 독립이다.


2. 토큰 실측#

같은 문서 18종을 YAML 과 KDL 과 JSON 으로 적고 쟀다(2026-08-02 재측정). 아래 표는 성격이 다른 7종을 뽑은 것이고, 각각이 kdl-examples.md 의 어느 절인지는 5절에 있다.

예시 YAML KDL JSON(압축) KDL / YAML
List 116 115 106 -0.9%
ListingTable (matrix) 141 124 119 -12.1%
Gauge (measure) 128 91 111 -28.9%
VennDiagram (relations) 117 112 111 -4.3%
Pyramid (params, alternatives) 110 91 93 -17.3%
계층 (children 3단) 78 66 71 -15.4%
다중 컴포넌트 (3블록) 262 218 214 -16.8%
7종 합계 952 817 825 -14.2%
18종 합계 2,274 1,980 1,963 -12.9%

18종 전량에서 KDL 이 YAML 보다 12.9% 적다. 예시별로는 Gauge 의 28.9% 가 가장 크고 List 의 0.9% 가 가장 작다. 같은 18종의 JSON 압축본은 13.7% 적어 KDL 보다 0.8%p 앞선다. 이전 판에서는 KDL -17.9% 대 JSON -10.2% 로 KDL 이 앞섰는데, v0.5 표기에서 순서가 뒤집혔다. 토큰만 놓고 보면 KDL 이 JSON 압축본보다 나을 것이 없다. KDL 을 고를 근거는 토큰이 아니라 3절의 silent corruption 쪽에 남는다.

이 수치는 v0.5 표기 기준이다. 이전 판은 7종 합계 -17.9% 였는데, 그때는 entity.id 가 두 형식 모두에 있었다. v0.5 에서 id 를 생성물에서 뺀 결과 같은 7종이 -14.2% 로 줄었다. id 는 KDL 에서 위치 인자 하나였고 YAML 에서는 id: 라는 키였으므로, 없어지면서 KDL 쪽 이득이 더 많이 깎였다.

2.1 어디서 절약되나#

구조 키를 안 적는다. YAML 은 components:, type:, structure:, entities:, dataset:, shape: 를 매번 적는데, KDL 은 노드 이름 하나가 그 역할을 한다. Gauge 예시에서 28.9% 가 절약된 것이 이 때문이다. 그 문서는 내용보다 구조 키의 비중이 크다.

위치 인자가 키 이름을 지운다. entity "명확한 아젠다" 는 label: 을, summary "0.5" "요금제 안내" 는 version: 과 title: 을 안 쓴다. row "베이직" 9900 "10GB" "이메일" 도 같다. 다만 entity 하나당 지우는 키가 하나뿐이라, 이득은 entity 수가 아니라 구조 키의 비중을 따라간다.

절약분이 가장 작은 쪽이 List 다(0.9%). YAML 의 flow style(- { label: X, items: [a, b] })이 이미 짧아서, entity 와 items 를 노드 둘로 나누는 KDL 이 따라잡지 못한다. 그런데 실사용 질의의 31.96% 가 List 다(semantic-taxonomy-v7.md 3절). 1절 아홉 종에 그 빈도를 가중하면 절약분이 6.6% 로 내려간다. 18종 단순 합계 12.9% 는 실제 트래픽에서의 절약분보다 큰 값이다.

2.2 절약되지 않는 곳#

따옴표가 필수다. KDL 1.0 은 모든 문자열 값에 따옴표를 요구한다. 숫자와 불리언만 예외다. 이것을 실제 파서로 확인했다.

col 요금제                     →  파싱 실패
col "요금제"                   →  통과
component Enumeration.List    →  파싱 실패
component "Enumeration.List"  →  통과

한 필드만 놓고 보면 KDL 이 오히려 불리하다.

표기 토큰
label: 명확한 아젠다 (YAML, 따옴표 없음) 8
label="명확한 아젠다" (KDL) 9
"label":"명확한 아젠다" (JSON) 10

즉 KDL 의 이득은 문법이 간결해서가 아니라 키 이름을 덜 반복해서 생긴다. 따옴표 비용을 물고도 18종 합계로 12.9% 가 남는 것이 실측 결과다.

2.3 이 수치의 한계#

토크나이저가 다르다. tiktoken 의 o200k_base 로 쟀다. Claude 의 토크나이저가 아니므로 절대값이 아니라 상대 비교의 근사값이다. 한국어 비중이 높은 내용이라 토크나이저 차이가 결과를 뒤집을 여지가 있다.

표본이 작다. 18종이고 전부 손으로 만든 예시다. 실사용 로그로 다시 재야 한다.

KDL 2.0 은 더 줄 수 있다. KDL 2.0 은 공백이 없는 문자열을 따옴표 없이 쓰는 것을 허용한다. 그러면 component Enumeration.List 처럼 적을 수 있어 토큰이 더 준다. 다만 kdl-py 는 1.0 구현이고, TypeScript 쪽 파서가 2.0 을 지원하는지는 확인하지 못했다. 위 수치는 1.0 기준이라 KDL 에 보수적인 값이다. 한국어 label 은 공백을 포함하는 것이 많아 2.0 에서도 따옴표를 벗지 못한다.


3. 토큰이 적다고 더 나은 것은 아니다#

절약분과 재생성 비용을 같은 자로 재야 한다.

LLM 이 형식을 틀리면 재생성한다. 재생성 한 번은 그 문서를 처음부터 다시 쓰는 비용이므로 100% 다. 그러니 KDL 의 형식 오류율이 YAML 보다 12.9%p 이상 높으면 절약분이 사라진다. 2026-08-02 재측정 전에는 이 값이 17.9%p 였다. 같은 7종을 v0.5 표기로 다시 재면 14.2%p 이고, 표본을 18종으로 넓히면 12.9%p 다. 어느 쪽으로 보아도 KDL 이 통과해야 할 조건이 전보다 빡빡하다.

그리고 KDL 의 오류율은 알려져 있지 않다. YAML 은 여러 실측에서 파싱 유효율 90% 안팎이 확인돼 있지만, KDL 은 LLM 생성 신뢰성 벤치마크가 0건이다. 학습 코퍼스 노출량이 생성 안정성을 크게 좌우한다는 점에서 KDL 이 불리할 사전 확률이 높다. 이것은 추정이지 실측이 아니다.

그래서 판단 기준은 하나다. 같은 스키마 N개 케이스를 두 형식으로 생성해 형식 오류율의 차이가 12.9%p 보다 작은지 본다. 작으면 KDL 이 이득이고, 크면 YAML 이 이득이다. 이 실측이 ../TODO.md F-1 이다.

기준선을 더 낮게 잡을 근거도 있다. 2.1절의 빈도 가중 절약분은 6.6% 이고, 이 값이 실제 트래픽에 가깝다. 다만 빈도가 확인된 것은 1절 아홉 종뿐이므로, 판단에는 18종 합계인 12.9%p 를 쓰고 6.6%p 는 F-1 결과가 그 사이에 들어올 때 다시 본다.

부차 지표도 함께 본다. silent corruption 은 YAML 의 알려진 사고 지점이다. 암묵적 타이핑 때문에 no 가 false 로, 1.0 이 숫자로 바뀐다. KDL 은 값 타입이 문법으로 드러나므로(따옴표가 있으면 문자열) 이 사고가 구조적으로 적다. 이 항목만 보면 KDL 이 유리하다.


4. KDL 표현 설계#

v0.5 스키마의 모든 구성물을 KDL 노드로 옮긴 것이다.

v0.5 스키마 KDL
문서 루트 summary 노드. 위치 인자 둘이 version 과 title
components[] component 자식 노드의 반복
type (대분류.중분류 결합 문자열) component 의 위치 인자 하나. 대분류만 적을 수도 있다
블록 id id= 속성
alternatives[] alt 자식 노드의 반복. 위치 인자 하나이고 type 과 같은 결합 문자열이다
params params 자식 노드의 속성들
structure.entities[] entity 자식 노드의 반복
entity.label entity 의 위치 인자 하나. entity.id 는 생성물에 없다 (규약 3.2절)
entity.items[] items 자식 노드의 위치 인자들
entity 의 나머지 필드 속성 (value=, unit=, period= 등)
entity.children[] entity 안의 entity
entity.components[] entity 안의 component
structure.relations[], page relations[] rel 노드. 첫 인자가 type, 나머지가 of, desc= 속성. of 는 label 을 가리킨다
dataset.shape: measure measure 노드. 속성이 값들, threshold 자식이 임계
dataset.shape: matrix matrix 노드. col 과 row 자식

설계 규칙 둘. 첫째, 순서가 있는 필수 값은 위치 인자로, 선택 값은 속성으로 둔다. 둘째, 배열은 노드 반복으로 표현한다.


5. 측정 대상 7종은 어디 있나#

예시는 kdl-examples.md 에 18종 전량이 있다. 절 번호와 제목이 yaml-examples.md 와 같으므로 같은 번호끼리 놓고 대조하면 된다. 2절 표가 이름으로 부르는 일곱은 아래 절이다.

2절의 이름 kdl-examples.md 그 예시에서 보는 것
List 1.1 entity 와 items 만 있는 최소 형태
ListingTable (matrix) 1.2 matrix 의 col 과 row
Gauge (measure) 2.1 measure 와 threshold
VennDiagram (relations) 3.1 rel 과 desc=
다중 컴포넌트 (3블록) 4 component 노드의 반복
Pyramid (params, alternatives) 5.1 params 속성과 alt 노드
계층 (children 3단) 5.2 entity 안의 entity

6. 실제로 돌려 본 결과#

kdl-examples.md 의 18종을 전부 파싱하고 v0.5 JSON 으로 변환해 규약 스키마로 검증했다 (2026-08-02). 검사 대상은 그 문서에 실린 KDL 블록 자체다.

검사 결과
KDL 파싱 (kdl-py) 18종 통과
4절 매핑으로 JSON 변환 18종 통과
v0.5 JSON Schema 검증 18종 통과
YAML 경로 결과와 동일 문서인가 18종 일치

마지막 줄이 중요하다. 같은 입력을 YAML 로 적어 파싱한 것과 KDL 로 적어 변환한 것이 바이트 단위로 같은 JSON 이 된다. 4절 매핑에 정보 손실이 없다는 뜻이다.

매핑 코드는 200줄이 안 됐다. KDL 을 배제했던 근거 중 '파서와 검증 연결과 정규화와 호환 계층을 직접 만들어야 한다'는 부분이 이 실측으로 작아진다. 다만 그 코드를 유지보수해야 하는 것은 그대로다.


7. 지금 상태와 다시 볼 조건#

규약은 YAML 이다. 이 문서는 결정을 바꾸지 않는다. 근거는 ../README.md 2.1절에 있고, 요약하면 산출물 전체가 이미 YAML 위에 쌓여 있고 KDL 의 생성 안정성이 미지수라는 것이다.

다시 볼 조건은 하나다. 3절의 실측에서 KDL 의 형식 오류율이 YAML 보다 12.9%p 넘게 높지 않다고 확인되면 전환을 검토할 값이 있다. 그 전에는 토큰 절약분이 재생성 비용에 먹힐 수 있다.

전환한다면 바뀌는 것은 셋이다. 생성기 프롬프트, 파서와 매핑 코드, 그리고 이 폴더의 예시 문서다. 규약 스키마(JSON Schema)와 capability 는 형식과 무관하므로 그대로다. 표면만 바뀌고 의미 계층은 안 바뀌는 구조로 설계해 둔 것이 여기서 값을 한다.


8. 참고#

↑ 목차로 · 2부 머리로

8장KDL 예시 참고YAML 예시와 절 번호가 같다

원문 spec/kdl-examples.md GitHub 에서 보기 9 KB 원문 제목 「Visual Summary KDL 예시 (v0.5)」

이 문서의 역할 yaml-examples.md 의 전체 문서 예시 18종을 KDL 로 그대로 옮긴 것이다. 절 번호와 제목을 그 문서와 같게 맞췄으므로 같은 번호끼리 놓고 대조하면 된다.

KDL 은 규약이 아니다. 규약은 YAML 이고 SSoT 는 schema-v0.5.md 와 yaml-examples.md 다. 이 문서는 표면 형식 결정을 다시 볼 때 쓰는 검토 자료다. 왜 다시 보는지는 format-alternatives.md 에 있다.

18종 전량이 네 검사를 통과한다 (2026-08-02 확인). kdl-py 파싱, format-alternatives.md 4절 매핑으로 JSON 변환, v0.5 JSON Schema 검증, 같은 예시의 YAML 경로 결과와 바이트 단위로 같은 JSON 인지다.

오너 정원석 (MORDOR) · 상태 검토 자료 (결정 아님) · 최종 갱신 2026-08-02

관련 yaml-examples.md 대응 원본 · format-alternatives.md 표현 설계와 토큰 실측 · schema-v0.5.md 규약

이 문서 훑기

절 무엇을 보나
0 KDL 표기를 다섯 줄로 요약한 것
1 질의의 85% 를 덮는 아홉 종을 어떻게 적나
2 measure 와 matrix 를 어떻게 적나
3 intersects 와 compares 를 어떻게 적나
4 한 페이지에 컴포넌트가 여럿일 때
5 대신 쓸 카테고리와 표현 선택 속성을 어떻게 적나
6, 7 표면 형식과 무관하다. yaml-examples.md 6절과 7절을 본다
8 참고

입력 문장은 여기에 다시 싣지 않는다. 절 번호가 같으므로 yaml-examples.md 의 같은 절에서 본다.


0. 표기 다섯 줄#

전체 매핑은 format-alternatives.md 4절이다. 아래는 예시를 읽는 데 필요한 만큼이다.

# 규칙 예
1 문서 루트는 summary 노드다. 위치 인자 둘이 version 과 title 이다 summary "0.5" "요금제 안내"
2 type 은 결합 문자열 하나다. 대분류만 확신하면 대분류만 적는다 component "Enumeration.List", component "Hierarchy"
3 entity 의 위치 인자는 label 하나다. id 는 생성물에 없다 (규약 3.2절) entity "명확한 아젠다"
4 rel 의 첫 인자가 type 이고 나머지가 of 다. of 는 label 을 가리킨다 rel "compares" "기존 방식" "새 방식"
5 모든 문자열 값에 따옴표가 필요하다. 숫자와 불리언만 예외다 col "월 요금" unit="원", row "베이직" 9900

5번은 KDL 1.0 기준이고, kdl-py 로 확인한 것이다.


1. 질의 상위 카테고리#

1.1 List (31.96%)#

kdl
summary "0.5" "효과적인 회의를 위한 3가지 원칙" {
  component "Enumeration.List" {
    entity "명확한 아젠다" {
      items "회의 전 안건 공유" "목표 한 문장 정의"
    }
    entity "시간 관리" {
      items "시작과 종료 시간 엄수" "항목별 타임박스"
    }
    entity "액션 아이템" {
      items "담당자 지정" "마감일 명시"
    }
  }
}

items 는 entity 의 자식 노드 하나이고, 항목이 그 노드의 위치 인자다.

1.2 ListingTable (15.08%)#

kdl
summary "0.5" "요금제 안내" {
  component "Enumeration.ListingTable" {
    matrix {
      col "요금제" role="category"
      col "월 요금" unit="원"
      col "저장 용량"
      col "지원"
      row "베이직" 9900 "10GB" "이메일"
      row "프로" 19900 "100GB" "전화와 이메일"
      row "엔터프라이즈" 49900 "무제한" "전담 매니저"
    }
  }
}

행을 한 줄에 적을 수 있어 표 계열에서 읽기 좋다. YAML 의 rows: - [...] 보다 들여쓰기 깊이가 얕다.

1.3 Steps (10.92%)#

kdl
summary "0.5" "온보딩 4단계" {
  component "ProcessFlow.Steps" {
    entity "계정 생성" {
      items "이메일 인증"
    }
    entity "프로필 설정" {
      items "팀과 역할 선택"
    }
    entity "첫 프로젝트" {
      items "템플릿 선택"
    }
    entity "팀 초대" {
      items "권한 부여"
    }
  }
}

순서는 노드가 나온 순서가 담는다. YAML 의 배열 순서와 같은 역할이다.

1.4 ComparisonTable (7.24%)#

kdl
summary "0.5" "방식별 비교" {
  component "AttributeComparison.ComparisonTable" {
    matrix {
      col "기준" role="category"
      col "기존 방식"
      col "새 방식"
      row "처리 시간" "4시간" "20분"
      row "오류율" "12%" "1%"
      row "월 비용" "320만원" "90만원"
    }
  }
}

1.2 와 노드 구성이 같다. 차이는 component 의 인자 하나뿐이다.

1.5 HighlightedMessage (5.75%)#

kdl
summary "0.5" "결론" {
  component "TextBasedCommunication.HighlightedMessage" {
    entity "자동화 없이는 목표 달성이 불가능하다" emphasis=true
  }
}

emphasis 는 불리언이라 따옴표를 붙이지 않는다. 붙이면 문자열 "true" 가 되어 스키마 검증에서 걸린다.

1.6 LongFormTextLayout (4.77%)#

kdl
summary "0.5" "프로젝트 배경" {
  component "TextBasedCommunication.LongFormTextLayout" {
    entity "왜 지금 자동화인가" {
      items "지난 3년간 처리량이 4배로 늘었지만 인원은 그대로였다." \
            "수작업 검수에 하루 4시간이 들어가고 오류율이 12%에 이른다." \
            "같은 방식으로 내년 물량을 감당할 수 없다는 것이 확인됐다."
    }
  }
}

인자가 길면 줄 끝에 \ 를 두고 다음 줄로 잇는다. 이어 붙인 것과 한 줄로 적은 것이 같은 노드다.

1.7 Timeline (4.27%)#

kdl
summary "0.5" "회사 연혁" {
  component "TimelineSchedule.Timeline" {
    entity "창업" period="2021" {
      items "시드 투자 유치"
    }
    entity "시리즈 A" period="2023" {
      items "해외 진출"
    }
    entity "100만 유저" period="2025"
  }
}

period 에 따옴표를 붙였다. 안 붙이면 숫자 2021 이 되어 YAML 의 period: "2021" 과 다른 JSON 이 나온다.

1.8 Versus (3.11%)#

kdl
summary "0.5" "프로세스 전환" {
  component "AttributeComparison.Versus" {
    entity "기존 방식" {
      items "수작업" "느림" "오류 많음"
    }
    entity "새 방식" {
      items "자동화" "빠름" "정확"
    }
    rel "compares" "기존 방식" "새 방식" desc="수작업에서 자동화로 전환하는 것이 핵심"
  }
}

rel 은 entity 와 같은 깊이에 둔다. 이 자리가 structure.relations[] 다.

1.9 BarChart (1.67%)#

kdl
summary "0.5" "채널별 연간 매출" {
  component "MagnitudeRanking.BarChart" {
    matrix {
      col "채널" role="category"
      col "매출" role="value" unit="억원"
      row "온라인" 67
      row "오프라인" 34
      row "파트너" 18
    }
  }
}

2. dataset 두 모양#

shape 를 값으로 적지 않는다. 노드 이름 measure 와 matrix 가 그 자리다.

2.1 measure: 값 하나와 기준#

kdl
summary "0.5" "3분기 목표 달성률" {
  component "KPIKeyMetric.Gauge" {
    entity "목표 달성률" value=65 unit="%"
    measure value=65 unit="%" min=0 max=100 target=80 {
      threshold 50 state="warn" label="주의"
      threshold 80 state="safe" label="목표선"
    }
  }
}

18종 중 토큰 절약분이 가장 큰 예시다(28.9%, format-alternatives.md 2절). 값이 많고 내용 텍스트가 적어 구조 키의 비중이 크기 때문이다. threshold 의 위치 인자가 at 이고 나머지는 속성이다.

2.2 matrix: 행렬#

kdl
summary "0.5" "분기별 채널 매출 구성" {
  component "ProportionComposition.StackedBarChart" {
    matrix {
      col "분기" role="category"
      col "온라인" role="value" unit="억원"
      col "오프라인" role="value" unit="억원"
      col "파트너" role="value" unit="억원"
      row "1분기" 12 8 3
      row "2분기" 15 7 4
      row "3분기" 18 9 5
      row "4분기" 22 10 6
    }
  }
}

2.3 matrix 로 이중 축을 적을 때#

kdl
summary "0.5" "월별 매출과 성장률" {
  component "Trend.ComboChart" {
    matrix {
      col "월" role="category"
      col "매출" role="value" unit="억원" axis="left" type="bar"
      col "성장률" role="value" unit="%" axis="right" type="line"
      row "1월" 10 0
      row "2월" 12 20
      row "3월" 11 -8
      row "4월" 14 27
    }
  }
}

col 의 type= 은 열의 표현 종류이고, component 의 type 과 이름만 같다. 두 자리가 다르므로 섞이지 않는다.

2.4 matrix 로 좌표를 적을 때#

kdl
summary "0.5" "제품 포트폴리오" {
  component "RelationalComparison.Quadrant" {
    params {
      axisLabels x="시장 점유율" y="성장률"
    }
    matrix {
      col "제품" role="category"
      col "시장 점유율" role="x" unit="%"
      col "성장률" role="y" unit="%"
      row "A제품" 34 8
      row "B제품" 12 41
      row "C제품" 45 -3
    }
  }
}

params 안에 중첩 객체가 필요하면 자식 노드로 적는다. axisLabels 가 그 경우다.


3. 관계#

3.1 intersects#

kdl
summary "0.5" "장수기업 HR의 핵심 과제" {
  component "RelationalComparison.VennDiagram" {
    entity "전통" {
      items "유산" "정체성" "신뢰"
    }
    entity "변화" {
      items "혁신" "적응" "미래"
    }
    rel "intersects" "전통" "변화" desc="두 요소를 조화롭게 관리하는 것이 장수기업 HR의 핵심"
  }
}

rel 의 위치 인자가 type 다음에 of 를 그대로 편다. 3항 이상 비교도 인자를 늘리면 된다.

3.2 compares 는 3항 이상도 받는다#

kdl
summary "0.5" "클라우드 3사 비교" {
  component "AttributeComparison.ComparisonTable" {
    entity "A사" {
      items "저렴" "리전 적음"
    }
    entity "B사" {
      items "중간 가격" "리전 많음"
    }
    entity "C사" {
      items "비쌈" "관리형 서비스 풍부"
    }
    rel "compares" "A사" "B사" "C사" desc="가격과 리전 수는 반비례하고, 관리형 서비스는 가격에 비례한다"
  }
}

인자를 셋으로 늘린 것 말고 3.1 과 다른 것이 없다. of 가 가리키는 것은 위 entity 의 label 이다.


4. 다중 컴포넌트#

kdl
summary "0.5" "2025 사업 성과 요약" {
  component "KPIKeyMetric.BigNumber" {
    entity "총매출" value=152 unit="억원" emphasis=true
    entity "전년 대비" value=38 unit="%"
  }
  component "ProportionComposition.StackedBarChart" {
    matrix {
      col "분기" role="category"
      col "온라인" role="value" unit="억원"
      col "오프라인" role="value" unit="억원"
      row "1분기" 12 8
      row "2분기" 15 7
      row "3분기" 18 9
      row "4분기" 22 10
    }
  }
  component "Enumeration.List" {
    entity "신규 시장 진출" {
      items "동남아 3개국"
    }
    entity "반복 구매 증가" {
      items "구독 전환율 상승"
    }
  }
}

page 수준 rel 을 쓸 자리는 component 와 같은 깊이다. YAML 처럼 최상위 키를 따로 두지 않아도 위치로 구분된다. 위 예시에는 블록 id 도 page relations 도 없다. 생성기가 채우지 않기 때문이고, 이것은 규약 쪽 사정이라 형식과 무관하다.


5. alternatives 와 params#

5.1 대신 쓸 카테고리를 적어 두는 경우#

kdl
summary "0.5" "매슬로 욕구 5단계" {
  component "Hierarchy.Pyramid" {
    params apex="last"
    alt "Progression.Staircase"
    alt "Enumeration.List"
    entity "생리적 욕구"
    entity "안전 욕구"
    entity "사회적 욕구"
    entity "존경 욕구"
    entity "자아실현"
  }
}

alternatives[] 의 원소 하나가 alt 노드 하나다. 인자는 component 의 type 과 같은 결합 문자열이다. params 는 값이 평평하면 속성으로 적고, 2.4 처럼 중첩이면 자식 노드로 적는다.

5.2 대분류까지만 확신할 때#

kdl
summary "0.5" "조직 개편안" {
  component "Hierarchy" {
    entity "개발본부" {
      entity "프론트팀" {
        entity "웹파트"
        entity "앱파트"
      }
      entity "백엔드팀"
    }
  }
}

중분류를 생략해 대분류만 적은 형태다. 중첩이 깊어질수록 KDL 의 절약분이 커진다(15.4%). YAML 은 깊이마다 children: 과 - 와 들여쓰기가 늘어난다.


8. 참고#

↑ 목차로 · 2부 머리로

9장기계가 읽는 규약 정본JSON Schema 와 capability.json

원문 spec/schema/README.md GitHub 에서 보기 4 KB 원문 제목 「생성물: 기계가 읽는 규약」

이 폴더는 SSoT 가 아니다. 규약의 SSoT 는 ../schema-v0.5.md 와 ../capability-table.md 다. 이 폴더의 파일은 그 문서에서 뽑아 낸 것이다.

오너 정원석 (MORDOR) · 최종 갱신 2026-09-11 (generatable 9/36 + 재지정 예외 한 줄 generatorRedirect — 첫 출시 생성기 어휘. 2026-09-09 I-17 필드 위에 덧붙였고, I-17 자체는 정원석 확인 대기 그대로다)

왜 파일로 두나#

규약 문서의 JSON Schema 는 마크다운 코드펜스 안에 있다. 그 상태로는 검증기가 $ref 로 가리킬 수 없고, 예시를 자동으로 돌려 볼 수도 없다. 그래서 같은 내용을 파일로 한 벌 내보낸다.

파일 무엇인가 어디서 나오나
visual-summary-0.5.schema.json v0.5 core schema. 2026-08-29 에 2.2절에서 다시 내보냈다 — 2026-08-06 병합 헤더 경로(structure.columns, entity.values[])가 그때까지 이 파일에만 빠져 있었다. $id 는 https://miridih.com/schemas/visual-summary/0.5. 파일 이름과 $id 는 MAJOR.MINOR 만 따라가고 patch 는 따라가지 않는다(규약 0.1절). 현행 값은 0.5.1 이고 version 은 0.5.x 대역을 받는 패턴이다 ../schema-v0.5.md 2.2절 코드펜스를 그대로 옮긴 것이다
capability.json 45종의 성립 조건, 읽는 필드, 후속 우선순위, generatable. 2026-09-09 (I-17) 표 2절에 산문으로만 있던 개수 단위(count.basis, 어휘는 countBasisVocabulary)·내용 조건(contentConditions, 어휘는 contentConditionsVocabulary)·2.5.1절 대안 경로 표시(entitiesColumnsPath)·속성 기본값과 열거값(paramSpecs)을 필드로 넣었다. 표에 적힌 것만 담는다 — 개수 단위는 표가 "행"·"조각"·"축"·"계열"·"열" 로 적은 자리에만 붙이고 단위 없이 "n 이상" 으로 적은 행은 2절 머리말대로 entities 로 센다. 속성 기본값은 표가 백틱으로 적은 값만 옮기고 설명 문장(header, SWOT order)은 빈 선언으로 둔다. entities.min/max 는 count 와 같은 값이고 호환용으로 남겼다. 2026-08-29 에 PageNavigation 4종(엣지 8개)을 넣어 45종이 됐고, 2026-09-05 B-30·B-27로 엣지 131→127→130이 됐다. 2026-09-11 10.1절을 기계가 읽도록 generatable 을 심었다(false 9 · true 36). 뺀 자리의 생성 재지정은 기본이 fallback 첫 후속이고, 그와 다른 한 줄(RadarChart → ComparisonTable)만 generatorRedirect 로 적었다 — 생성기 쪽 소비자는 generatorRedirect ?? fallback[0] 으로 읽는다. I.I.S. 는 두 필드 다 보지 않는다 ../capability-table.md 2절·3.2절·10.1절

capability.json 과 문서 2절이 같은 구분을 쓴다#

entity 가 담는 선택 내용(period, children 같은 것)과 params 가 담는 속성(orientation, apex 같은 것)은 fallback 할 때 다르게 다뤄진다. 앞은 옮기고 뒤는 버린다(3.4절 P2 와 P3). 이 구분이 없으면 projection 규칙을 계산할 수 없다.

capability.json 은 이것을 reads.content 와 reads.relations 와 reads.params 로 갈라 담는다. 2026-08-23 부터 문서 2절도 같은 구분을 두 열(선택 내용, 속성)로 갖는다. 종전에는 한 열("선택 속성")에 섞여 있어 이 파일에만 기계가 읽는 형태로 존재했다. 지금은 둘이 같은 것을 말해야 하고, 어긋나면 이 JSON 을 따른다.

문서를 고치면 이 파일도 손으로 맞춘다#

자동 생성 도구를 저장소에 두지 않는다. 검사는 작업할 때 스크립트를 짜서 돌린다. 2026-08-29 에 아래를 전부 다시 돌려 통과시킨 판이 지금 파일이다 (그전 전량 실행은 2026-08-04 이고, 그 사이 개정 다섯 번이 검사 없이 들어가 있었다. 2026-08-06 병합 헤더, 2026-08-17 fallback v2, 2026-08-21 ComboChart, 2026-08-28 복합 순서, 2026-08-29 type 필수와 대분류 제거와 enum 45종).

# 무엇을 보나
S1 규약 2.2절 JSON Schema 가 draft 2020-12 로 유효한가
S2 visual-summary-0.5.schema.json 이 그 2.2절과 같은 내용인가
S3 semanticCategory enum 이 결합값 45종이고 중복이 없으며 대분류 단독이 섞이지 않았는가 (2026-08-29 개정)
E1 문서의 전체 문서 YAML 예시가 전량 스키마를 통과하는가 (2026-08-29 기준 규약·yaml-examples 28건, 그 밖의 문서 31건)
E2 안티패턴 절의 예시는 스키마가 거부하는가
C1 capability 2절 45종이 스키마 결합값 45종과 같고 번호가 1..45 연속인가
C2 capability.json 이 capability 2절, 3.2절과 일치하는가
C3 예시가 자기가 선언한 카테고리의 성립 조건을 채우는가
F1 3.2절 후속 목록의 도착이 45종 안에 있는가
F2 어느 행도 대체 후보를 셋 이상 두지 않는가 폐기된 검사다. 두 칸 상한이 2026-08-17 에 없어졌다
F3 모든 행이 마지막 fallback 셋 중 하나를 거치거나 자신이 그 셋인가. "끝나는가"가 아니다 — 2026-08-17 에 List 뒤에 ListingTable 을 안전망으로 덧붙인 행이 넷 생겼다(List, SWOT, ProfileCard, TableOfContents)
F4 3.5절 엣지 표가 3.2절에서 유도한 그래프 엣지와 같고 중복이 없는가. 2026-09-05 기준 130개
F5 3.5절의 규칙이 capability.json 에서 계산한 것과 같은가
N1 본문이 말하는 수(45, 130, 아홉)가 표의 실제 수와 맞는가
C4 구현 상수가 이 파일과 같은가 — aippt-prisonbreak src/lib/visual-summary/capability.test.ts 의 "문서 검증용 사본과의 일치" 스위트가 45종 전 필드(개수와 단위, 내용 조건, 속성 선언, dataset, reads, fallback, entities 호환 필드)를 대조한다. pnpm check:docs-sync 로 돌린다. 이 저장소가 private 서브모듈이라 aippt CI 체크아웃에는 없어서 그쪽 CI 에서는 건너뛴다 — 항상 돌리려면 토큰이 필요하고, 그건 ../../TODO.md I-17 에 남겼다 (2026-09-09 신설)

F5 가 이 폴더를 살아 있게 하는 검사다. capability.json 의 reads 로 3.5절 엣지 130개의 규칙(P1~P8)을 다시 계산해 표와 대조한다. 구분이 틀리면 이 검사가 걸린다. 파이썬으로 짤 때 필요한 것은 pyyaml 과 jsonschema 둘이다.

구현 상수는 여기가 아니다#

검색 모듈이 실제로 로드하는 capability 상수는 packages/comp-sign 에 TypeScript 로 두고 BE 소비용은 JSON 으로 내보낸다 (../../TODO.md B-14). 이 폴더의 capability.json 은 문서 검증용 사본이고 런타임이 읽는 물건이 아니다. 둘을 섞으면 같은 사실이 두 곳에 있게 된다.

↑ 목차로 · 2부 머리로

3부

설계 — design/

규약이 왜 그 모양인지. 흐름·대치 계약·I.I.S. 게이트·꾸밈구조 넷이 설계의 뼈대다.

템플릿 페이지layouts RLSC구조와 역할 트리 I.I.S.경계를 잡고 structure 를점증적으로 세운다 정합성 게이트structure 에 안 실린텍스트 노드를 찾는다 색인comp_frames 빠진 노드가 있으면 기록하고 색인 전에 거른다GC 게이트와는 거르는 대상이 다르다 사전의 I.I. Structuriser 문서에서 구현과 운영을 함께 본다 색인 쪽 흐름 · 질의 쪽(사용자 입력)과는 따로 미리 돈다
I.I.S.(Incremental Information Structuriser)는 색인 쪽에 있다. RLSC 가 만든 역할 트리에서 경계를 잡아 structure 를 세우고, 정합성 게이트가 그 구조에 실리지 못한 텍스트 노드를 색인 전에 걸러 낸다. 설계는 3부 12장, 사전 안의 구현 설명은 I.I. Structuriser (IIS) 에 있다.
10장전체 흐름 정본입력 → Visual Summary → Search Signature → RLSC

원문 design/pipeline-flow.md GitHub 에서 보기 20 KB 원문 제목 「전체 흐름: 사용자 입력 → Visual Summary → Search Signature → RLSC」

이 문서의 역할 프로젝트 문서가 각자 자기 구간만 설명해서, '사용자가 입력한 글이 어느 경로로 컴포넌트에 닿는가'를 한곳에서 볼 수 없다. 그 경로를 한 장으로 그린다. 각 상자의 상세는 해당 문서를 인용하고, 여기서는 연결과 경계만 다룬다.

오너 정원석 (MORDOR) · 상태 DRAFT · 최종 갱신 2026-08-22

관련 README.md 로드맵 · schema-v0.5.md 규약 · content-binding-contract.md 내용 대치 contract · semantic-taxonomy-v7.md 단어장 · ../comp-signature.md 색인 contract · ../incremental-information-structuriser-blueprint.md I.I.S.

이 문서 훑기

절 무엇에 답하나
0 사용자 입력이 RLSC 가 되는가 (아니다)
1 한 장으로 보면 어떤 모양인가
2 같은 입력이 구간마다 어떻게 바뀌나
3 단계마다 어느 선이 바뀌나. 데이터 실물은 어떤 모양인가
4 어느 조직이 어느 상자를 소유하나
5 아직 미정인 곳은 어디인가

0. 두 갈래가 만나는 구조다#

흔한 오해가 '사용자 입력이 RLSC 로 변환된다'는 것이다. 그렇지 않다. 경로가 둘이고, 둘은 별개로 진행된다.

갈래 무엇을 하나 여기 있는 것
질의 쪽 (query side) 사용자 입력을 검색 조건으로 바꾼다 Summary, structure
색인 쪽 (index side) 이미 있는 템플릿 컴포넌트를 검색 가능한 형태로 미리 뽑아 둔다 RLSC, Search Signature v2

둘은 BE 검색 모듈에서 합류한다. 사용자 입력이 RLSC 가 되는 것이 아니라, 사용자 입력에서 뽑은 조건을 미리 색인해 둔 RLSC 파생물과 대조한다. structure 에서 RLSC 로 가는 화살표는 변환이 아니라 capability 대조이고, 그 규약이 capability-table.md 이다.


1. 전체 그림#

mermaid
flowchart TB
    subgraph QuerySide["질의 쪽: 사용자가 입력한 글"]
        direction TB
        UserInput["사용자 입력<br/><code>input_text</code>"]
        SummaryGenerator["Summary 생성기"]
        MarkdownSummary["<b>Markdown Summary</b><br/>현행, 1·2단계"]
        VisualSummary["<b>Visual Summary (YAML)</b><br/>3단계 이후<br/><code>component</code> + <code>structure</code>"]
        LayoutDescription["Layout Description 추출<br/><i>gpt-5.4-mini</i>"]
        TaxonomyClassify["Taxonomy 분류<br/><i>claude-haiku-4-5</i>"]
        QueryTag["<b>질의 쪽 semantic tag</b><br/>v7 중분류 1개 이상"]
        QueryStructureHash["structureHash[]<br/><code>buildCompSearchData()</code>"]

        UserInput --> SummaryGenerator --> MarkdownSummary
        SummaryGenerator -. "3단계부터" .-> VisualSummary
        UserInput --> LayoutDescription
        MarkdownSummary --> LayoutDescription --> TaxonomyClassify --> QueryTag
        MarkdownSummary --> QueryStructureHash
        VisualSummary -. 3단계부터 .-> QueryStructureHash
    end

    subgraph IndexSide["색인 쪽: 이미 있는 템플릿"]
        direction TB
        TemplatePage["템플릿 페이지<br/><code>layouts</code>"]
        ALSC["ALSC"]
        Structuriser["Structuriser"]
        RLSC["<b>RLSC</b><br/>레이아웃 구조와 요소 역할 트리"]
        ValidComponentGate["유효 컴포넌트 게이트<br/>v3.4 / v3.5"]
        DesignObjects["<code>design_objects</code><br/>컴포넌트 83,771건"]
        SignatureExtract["comp-sign<br/><code>buildCompSignatures()</code>"]
        SearchSignature["<b>Search Signature v2</b><br/><code>CompSignature</code> 최대 3건/컴포넌트"]
        ComponentTagging["컴포넌트 semantic 태깅<br/>rule(List 일괄) + LLM"]
        SemanticTagTable["<code>design_object_semantic_tags</code>"]

        TemplatePage --> ALSC --> Structuriser --> RLSC --> ValidComponentGate --> DesignObjects
        DesignObjects --> SignatureExtract --> SearchSignature
        DesignObjects --> ComponentTagging --> SemanticTagTable
    end

    subgraph BackEnd["BE 검색과 대치"]
        direction TB
        StructureFilter["① 구조 1차 필터<br/>structure_hash IN (...)"]
        CandidatePool["② 태그 군으로 후보 풀 구성<br/>0군 사용자 지정 → 1군 LLM 추정 → 2군 그 외"]
        Scoring["③ 채점<br/><code>rankCompCandidates()</code><br/>textMap, scoreFeatures, textNodeRoles<br/>fingerprint 로 중복 제거"]
        ComponentPick["컴포넌트 확정<br/>군이 점수보다 우선"]
        ContentFill["내용 대치<br/><code>buildContentReplacement()</code>"]
        StructureFilter --> CandidatePool --> Scoring --> ComponentPick --> ContentFill
    end

    QueryStructureHash --> StructureFilter
    QueryTag --> CandidatePool
    SearchSignature --> StructureFilter
    SemanticTagTable --> CandidatePool
    RLSC -. 슬롯 구조 제공 .-> ContentFill

    style QuerySide fill:#eef6ff,stroke:#4a7fb5
    style IndexSide fill:#f2f7ee,stroke:#6a9a4a
    style BackEnd fill:#fff6e8,stroke:#c08a3e
    style VisualSummary stroke-width:3px
    style RLSC stroke-width:3px
    style SearchSignature stroke-width:3px

굵은 테두리 셋이 이 프로젝트가 정의하는 contract 다. Visual Summary 는 질의 표현, Search Signature v2 는 색인 contract, RLSC 는 구조 어휘다.

BE 구간의 네 함수는 이석님의 packages/comp-sign v4.3.8 에 있다. 상세는 content-binding-contract.md 2절에 있다.

그림에 처음 나오는 상자 몇 개#

대부분은 이름만으로 읽히지만, 셋은 한 줄 설명이 필요하다.

Layout Description 추출과 Taxonomy 분류 요약을 보고 '가로로 늘어선 단계 네 개' 같은 시각 레이아웃 모양을 한국어로 묘사한 뒤(gpt-5.4-mini), 그 묘사를 v7 단어장 값으로 분류하는(claude-haiku-4-5) 2단 구성이다. 결과가 질의 쪽 semantic tag 다.

design_object_semantic_tags 컴포넌트에 붙인 v7 태그가 담기는 테이블이다. 컬럼은 design_object_id, tags(배열), version 이고, 3절에서 계속 나온다.

내용 대치 고른 컴포넌트의 슬롯에 사용자 내용을 실제로 넣는 마지막 구간이다. buildContentReplacement() 가 담당하고, 상세는 content-binding-contract.md 2절에 있다.


2. 입력 하나가 구간마다 어떻게 바뀌나#

같은 입력이 각 구간에서 어떤 모양이 되는지 따라간다. 아래 입력은 실사용 로그가 아니라 yaml-examples.md 2.1절의 예시다. 규약을 보이려고 만든 것이므로 실측이 아니다. 실사용 로그로 같은 대비를 보는 것은 3절 끝의 100046_2 다.

mermaid
flowchart LR
    UserInput["<b>①  사용자 입력</b><br/>&quot;올해 매출은 전년 대비 38%<br/>성장한 총 152억이다.<br/>채널별로는 온라인이 가장 크고<br/>분기마다 늘었다…&quot;"]
    MarkdownSummary["<b>②  Markdown Summary</b><br/># 2025 사업 성과<br/>* 총매출 152억<br/>* 온라인 매출 22억<br/><i>값과 단위, 계열이 문자열로만 남음</i>"]
    VisualSummary["<b>②'  Visual Summary</b><br/>components:<br/>&nbsp;- big_number (value 152, unit 억원)<br/>&nbsp;- stacked_bar (dimensions×series)<br/><i>슬롯이 그대로 남음</i>"]
    SearchCondition["<b>③  검색 조건</b><br/>semantic tag<br/>KPIKeyMetric_BigNumber<br/>ProportionComposition_StackedBarChart<br/>structureHash: …"]
    CandidateComponent["<b>④  후보 컴포넌트</b><br/>design_object + CompSignature<br/>+ semantic_tags"]
    ContentReplacement["<b>⑤  대치</b><br/>RLSC Role.Element.Title / Description<br/>Chart data cell 에 값 주입"]

    UserInput --> MarkdownSummary --> SearchCondition
    UserInput -. "3단계" .-> VisualSummary -. "3단계" .-> SearchCondition
    SearchCondition --> CandidateComponent --> ContentReplacement

    style VisualSummary fill:#eef6ff,stroke:#4a7fb5,stroke-width:3px
    style MarkdownSummary fill:#f7f7f7

②와 ②' 의 차이가 이 프로젝트가 바꾸려는 지점이다. Markdown 은 입력을 평탄화하고 YAML 은 슬롯을 보존한다. 실제 손실 유형 다섯 가지는 markdown-summary-samples.md 3절에 있다.


3. 단계별로 그림이 어떻게 바뀌나#

로드맵 단계마다 그림의 어느 선이 바뀌는지다. 단계 구분은 README.md 1절 로드맵과 같다.

단계 질의 쪽 산출물 검색에 실제로 쓰는 것 태그가 붙는 위치 그림에서 달라지는 선
1단계 (현행) Markdown Summary 임베딩 유사도 없음 태그로 들어가는 선이 양쪽 다 아직 없음
2단계 (2026-08-18) Markdown Summary structureHash + semantic tag 컴포넌트 root 질의 쪽 태그와 design_object_semantic_tags 에서 후보 풀 구성으로 가는 선이 새로 생김
3단계 (실행하지 않음) Visual Summary (YAML) structure 를 질의로 변환 컴포넌트 root Summary 생성기에서 Visual Summary 로 가는 선과 Visual Summary 에서 구조 해시로 가는 선이 함께 실선이 됨
4단계 Visual Summary RLSC node 단위 태그 추가 컴포넌트 안 node 마다 RLSC 아래 I.I.S. 상자가 삽입되고, design_object_semantic_tags 의 한 행이 컴포넌트 하나가 아니라 node 하나를 가리키게 됨
5단계 (실행 여부 미정) Visual Summary category 별 property 추가 node 내용 대치 구간이 텍스트뿐 아니라 category 별 property(sequence, rank 등)까지 넣게 됨

4단계에서 색인 쪽에 추가되는 것: I.I.S.#

mermaid
flowchart LR
    ALSC["ALSC"] --> Structuriser["Structuriser"] --> OldRLSC["Old RLSC"]
    OldRLSC --> IncrementalStructuriser["<b>I.I.S.</b><br/>(Semantic Component Boundary)"] --> NewRLSC["New RLSC<br/>node마다 semantic tag"]
    NewRLSC -. New RLSC 충분히 축적 후 .-> NewStructuriser["New Structuriser<br/>(자체모델 재학습)"]
    style IncrementalStructuriser fill:#fff0f0,stroke:#c05050,stroke-width:2px

2단계부터 3단계까지는 태그가 컴포넌트 하나에 붙는다. 그래서 그 태그가 root 자체를 가리키는지 하위 node 를 가리키는지 구분되지 않는다. 4단계에서 I.I.S. 가 node 마다 태그를 달면 그 모호성이 사라진다. 근거는 README.md 1절과 5절이다.

2, 3단계:  [component] → tag: "Steps"       (root인지 하위인지 미상)

4단계:     [component] → tag: "Steps"
                  ├─ [node A] → tag: "LayeredDiagram"
                  └─ [node B] → tag: "Timeline"

단계마다 데이터가 실제로 어떤 모양인가#

위 표에는 그림의 어느 선이 바뀌는지만 적었다. 실제 데이터가 어떻게 생겼는지를 한 건으로 따라간다. 입력과 Markdown Summary 는 markdown-summary-samples.md 2.3절의 실사용 샘플 100046_2 이고, 그 뒤 단계는 아직 배포 전이라 규약과 티켓이 정한 형태에 맞춰 이 문서에서 조립한 것이다. 값 자체(해시 문자열, design_object_id 등)는 자리를 보이려고 채운 것이므로 실측값이 아니다.

공통 입력 (input_text, 발췌)

text
… 2. 입찰 참여 및 견적 제출
-견적 제출: 참여 업체들은 현장 상황과 본인들의 기술력, 장비 가동 효율을 고려하여
 업체별 단가(낙찰가)를 기입한 견적서를 작성합니다.
-기한 준수: 정해진 마감 기한 내에 견적서와 함께 시공 계획서, 장비 사양서 등을 제출합니다. …
1단계 (현행)#

질의 쪽은 요약 문자열 하나뿐이고, 색인 쪽에는 태그가 없다. 대조는 요약 텍스트의 임베딩 유사도로만 한다.

markdown
# 입찰 참여 및 견적 제출
## 1. 견적 제출
* 참여 업체 현장 상황 고려
* 기술력 및 장비 가동 효율 고려
* 업체별 단가(낙찰가) 기입
* 견적서 작성
## 2. 기한 준수
* 정해진 마감 기한 내 제출
* 시공 계획서 제출
* 장비 사양서 제출

여기서 이미 잃은 것 원문의 '1. → 2.' 순서가 ## 두 개로 바뀌었다. 이것이 순서형인지 그냥 두 덩어리인지가 문자열에 남지 않는다.

2단계 (2026-08-18 목표)#

요약은 1단계와 같다. 옆에 값 두 개가 새로 붙는다.

jsonc
// 질의 쪽: 사용자 입력에서 뽑은 태그와 구조 해시
{
  "semantic_tags":   ["ProcessFlow_Steps"],   // {major}_{minor} 표기가 SSoT
  "structure_hash":  ["h_2c41…", "h_9be0…"]   // 값은 자리 표시
}
jsonc
// 색인 쪽: design_object_semantic_tags 한 행
{ "design_object_id": 4757227, "tags": ["ProcessFlow_Steps"], "version": "2.2" }

// 색인 쪽: Search Signature v2 한 행 (iui.comp_signs_v3_4)
{
  "structure_hash":  "h_2c41…",
  "text_map":        { /* 노드별 글자 용량 */ },
  "text_node_roles": [ { "role": "Title", "chars": 14 }, { "role": "Description", "chars": 62 } ],
  "semantic_tags":   ["ProcessFlow_Steps"]     // 태그 테이블을 denormalize 한 컬럼
}

검색 모듈이 하는 일 structure_hash 로 구조 1차 필터를 걸고, 태그가 맞는 것을 0군에서 2군 순으로 후보 풀에 담고, text_map 과 text_node_roles 로 채점한다.

아직 모르는 것 tags: ["ProcessFlow_Steps"] 가 이 컴포넌트의 root 가 Steps 라서 붙은 것인지, 하위 어딘가에 Steps 모양의 node 가 있어서 컴포넌트 전체가 받은 것인지 구분되지 않는다. 태그가 컴포넌트 단위로 붙는 한 이 모호성이 남는다.

3단계 (실행하지 않음)#

질의 쪽만 바뀐다. 색인 쪽은 2단계와 같다.

yaml
version: "0.5.4"
title: 입찰 참여 및 견적 제출
components:
  - type: ProcessFlow.Steps
    structure:
      entities:
        - { label: 견적 제출, items: [현장 상황 고려, 기술력과 장비 가동 효율 고려, 업체별 단가 기입] }
        - { label: 기한 준수, items: [마감 기한 내 제출, 시공 계획서, 장비 사양서] }

1단계에서 잃었다가 다시 담기는 것 항목이 둘이라는 사실, 각 항목의 제목과 하위 항목의 구분, 그리고 둘 사이에 순서가 있다는 사실이다. ## 문자열이 아니라 entities 배열이라 검색 조건으로 바로 쓸 수 있다.

여기가 아직 점선인 이유 이 YAML 을 structure_hash 로 바꾸는 변환기가 없다. buildCompSearchData() 의 입력이 아직 markdown 이라 entities 가 들어갈 자리가 없다 (TODO.md D-4).

4단계#

색인 쪽이 바뀐다. 컴포넌트마다 한 행이던 태그가 node 마다 한 행이 된다.

jsonc
// 2단계와 3단계: 컴포넌트 하나에 한 행
{ "design_object_id": 4757227, "tags": ["ProcessFlow_Steps"] }

// 4단계: node 마다 한 행 (I.I.S. 가 붙임)
{ "design_object_id": 4757227, "node_id": "root",   "tags": ["ProcessFlow_Steps"] }
{ "design_object_id": 4757227, "node_id": "n_0012", "tags": ["Enumeration_List"] }
{ "design_object_id": 4757227, "node_id": "n_0034", "tags": ["Enumeration_List"] }

달라지는 것 '이 컴포넌트 어딘가에 Steps 가 있다'가 'root 가 Steps 이고 그 아래 List 가 둘 있다'가 된다. 검색과 대치를 n_0012 라는 정확한 범위에 걸 수 있다. node_id 컬럼 이름은 아직 정해지지 않았다.

5단계 (실행 여부 미정)#

node 에 그 category 고유의 property 가 함께 실린다. Steps 의 속성 넷은 MOR-1856 이 정의했다.

jsonc
{
  "design_object_id": 4757227, "node_id": "n_0012",
  "tags": ["ProcessFlow_Steps"],
  "props": {
    "sequence": 1,        // 기하 좌표로 확정한 반복 단위의 순서
    "rank":     1,        // 순위 값 1..N
    "attach":   null,     // 앞 단위에 귀속된 연결자
    "span":     null      // 여러 gap 을 가로지르는 연결자
  }
}

달라지는 것 지금은 대치가 텍스트만 넣는다. 5단계에서는 '이 항목이 몇 번째인가'까지 넣으므로, 사용자 입력의 '1. 견적 제출'이 컴포넌트의 첫 번째 자리에 들어간다고 보장할 수 있다.

지금 상태 Steps 는 4단계보다 먼저 진행 중이라 로드맵과 실제 작업 순서가 어긋나 있다. 응집 PASS 911건 전량에 속성을 부여했고, 마커 보유 540건에서 마커 순서와 기하가 534건 일치했다(99%, 기준 95%). 상세는 content-binding-contract.md 1.2절에 있다.

나머지 category 는 아직 비어 있다. Pyramid 의 층 방향, Venn 의 교집합 영역, ListingTable 의 행과 열 인덱스에는 속성 이름조차 정해지지 않았다 (content-binding-contract.md 1.3절).


4. 경계선: 누가 무엇을 소유하나#

0절의 두 갈래를 그대로 두고, 각 상자에 소유자를 붙였다. 네 조직이 한 그림에 다 들어가면 상자가 옆으로 늘어서서 한 화면에 담기지 않는다. 그래서 갈래별로 나눠 그린다. 색인 갈래가 먼저이고 질의 갈래가 다음이다. 규약이 어디를 제약하는지는 그 뒤에 따로 그린다.

색인 갈래의 소유#

이미 있는 템플릿 컴포넌트를 검색 가능한 형태로 미리 만들어 두는 쪽이다. 단어장을 정의하고 태깅을 수행하는 곳이 내용시각화 스쿼드이고, 그 결과를 받아 검증하고 적재하는 곳과 RLSC 를 뽑는 곳이 RAT pod 이다.

mermaid
flowchart TB
    subgraph ContentVizSquad["내용시각화 스쿼드"]
        TaxonomyDefine["단어장 v7 정의<br/>김수현"]
        ComponentTagging["컴포넌트 태깅 수행<br/>황현진"]
    end

    subgraph RatPod["MORDOR / RAT pod"]
        NodeClassify["노드 분류, category별 속성<br/>신영빈·류민"]
        IncrementalStructuriser["I.I.S. (4단계)<br/>류민"]
        RlscExtract["RLSC 추출, 유효 컴포넌트 게이트<br/>MORDOR 파이프라인"]
        TagIngest["태깅 결과 수신·검증·적재<br/>류민·박지우"]
    end

    subgraph CompSignPackage["comp-sign 패키지 · 이석"]
        BuildSignatures["buildCompSignatures<br/>색인"]
    end

    subgraph Backend["BE (UNICORN) · 최운식"]
        StoreTagsAndSignatures["태그·시그니처 적재"]
    end

    TaxonomyDefine --> ComponentTagging --> TagIngest
    NodeClassify --> RlscExtract
    IncrementalStructuriser -. 4단계 .-> RlscExtract
    RlscExtract --> TagIngest
    RlscExtract --> BuildSignatures
    TagIngest --> StoreTagsAndSignatures
    BuildSignatures --> StoreTagsAndSignatures

    style ContentVizSquad fill:#fdf6ee,stroke:#c08a3e
    style RatPod fill:#f2f7ee,stroke:#6a9a4a
    style CompSignPackage fill:#eef6ff,stroke:#4a7fb5
    style Backend fill:#fff6e8,stroke:#c08a3e

질의 갈래와 실행의 소유#

사용자가 친 글이 검색 조건이 되는 쪽이다. 여기서 두 갈래가 만난다. 아래 그림의 점선 상자는 앞 그림의 마지막 상자, 즉 색인 갈래가 이미 적재해 둔 태그와 시그니처다.

mermaid
flowchart TB
    UserInput["사용자 입력"]

    subgraph RatPodQuery["MORDOR / RAT pod"]
        SummaryGenerator["<b>Summary 생성기</b><br/>허재영·황현진<br/><i>규약 확정 후 착수</i>"]
    end

    subgraph ContentVizSquadQuery["내용시각화 스쿼드"]
        SemanticQueryExtract["시멘틱 쿼리 추출 LLM 모듈<br/>황현진"]
    end

    subgraph CompSignPackageQuery["comp-sign 패키지 · 이석"]
        BuildSearchData["buildCompSearchData<br/>질의 해시"]
        RankCandidates["rankCompCandidates<br/>랭킹"]
        BuildReplacement["buildContentReplacement<br/>대치"]
    end

    subgraph BackendQuery["BE (UNICORN) · 최운식"]
        RunSearch["검색 실행"]
        RunReplacement["대치 실행"]
    end

    IndexedData["<i>색인 갈래가 적재해 둔<br/>태그·시그니처</i>"]

    UserInput --> SummaryGenerator --> SemanticQueryExtract --> RunSearch
    SummaryGenerator --> BuildSearchData --> RunSearch
    RankCandidates --> RunSearch --> RunReplacement
    BuildReplacement --> RunReplacement
    IndexedData --> RunSearch

    style SummaryGenerator fill:#eef6ff,stroke:#4a7fb5,stroke-width:2px
    style IndexedData fill:#f2f7ee,stroke:#6a9a4a,stroke-dasharray:4 3
    style ContentVizSquadQuery fill:#fdf6ee,stroke:#c08a3e
    style RatPodQuery fill:#f2f7ee,stroke:#6a9a4a
    style CompSignPackageQuery fill:#eef6ff,stroke:#4a7fb5
    style BackendQuery fill:#fff6e8,stroke:#c08a3e

규약이 제약하는 네 곳#

두 그림에 나눠 그린 탓에 규약이 걸리는 자리가 흩어져 보이므로, 그 넷만 따로 모은다. Visual Summary 규약은 검색 한 곳만 제약하지 않는다. 네 곳을 제약하고, 그중 둘이 질의 쪽이다.

mermaid
flowchart LR
    Spec["<b>Visual Summary 규약</b><br/>정원석"]
    SummaryGenerator["① <b>Summary 생성기</b><br/>무엇을 써 낼지"]
    SemanticQueryExtract["② <b>시멘틱 쿼리 추출</b><br/>structure 를 질의로"]
    Ranking["③ <b>랭킹</b><br/>capability 판정"]
    ContentReplacement["④ <b>대치</b><br/>슬롯 매핑"]

    Spec --> SummaryGenerator
    Spec --> SemanticQueryExtract
    Spec --> Ranking
    Spec --> ContentReplacement

    style Spec fill:#f6eeff,stroke:#8a5ab5,stroke-width:3px
# 어디 규약의 무엇이 어느 단계에서
① Summary 생성기 core schema. LLM 이 무엇을 써 낼지를 정한다 2.5단계
② 시멘틱 쿼리 추출 structure 를 검색 질의로 바꾸는 규칙 3단계
③ 랭킹 capability 테이블. 이 structure 가 이 컴포넌트에 대치 가능한지 2단계부터
④ 대치 슬롯 매핑. entities, relations, dataset 이 어느 Role 로 가는지 2단계부터

①과 ②가 질의 쪽이다. 규약을 '검색 모듈이 읽는 contract'으로만 보면 이 둘이 빠지고, 그러면 규약 확정이 Summary 생성기 착수를 막고 있다는 사실이 그림에서 사라진다. README.md 4절에서 3단계 오너를 "스키마는 정원석, 생성 실무는 규약이 확정된 뒤 허재영님과 황현진님"으로 적은 것이 ①이다.

③과 ④는 규약이 구현보다 뒤에 있다. 상세는 content-binding-contract.md 에 있다.

소유 표#

구간 담당 근거
단어장 v7 정의와 유지 김수현 (내용시각화) 시멘틱 검색v2 타임라인
Summary 생성기 스키마는 정원석, 생성 실무는 허재영·황현진 (규약 확정 후 착수) README.md 4절 3단계 오너
시멘틱 쿼리 추출 LLM 모듈 황현진 (내용시각화) Confluence 2713223553
컴포넌트 태깅 수행 황현진 (내용시각화) 절차 명세 P2
태깅 결과 수신·검증·적재 류민 → 박지우 (RAT pod) Slack 2026-07-16
노드 분류와 category 별 속성 신영빈·류민 (RAT pod) MOR-1856, Slack 2026-07-31
I.I.S. (4단계) 류민 (RAT pod) MOR-1425 외 3건. 2026년 6월 중순부터 정지
시그니처·랭킹·대치 계산 이석 (comp-sign 패키지) ../comp-signature.md 2절
시그니처 적재 정원석·이예림 같은 문서 2절
Visual Summary 규약 정원석 MOR-1773
검색과 대치 실행 최운식 (BE) UNICORN-77316, UNICORN-77328

layouts 태깅은 이 그림에서 BE 로 가지 않는다. 페이지 태깅도 존재하지만 RAT pod 내부용이고, 검색에는 컴포넌트 태깅만 쓴다. 앞으로도 전달할 계획이 없다. 근거는 MOR-1755 본문의 '확인 필요' 절이다. "레이아웃 태깅도 있나? 있긴한데 rat pod 에서 사용하는 데이터이며, 검색에는 쓰이지 않는다" 로 적혀 있고, 원 Slack 근거가 달려 있다.


5. 그림에서 아직 점선인 곳#

각 항목의 담당과 기한은 TODO.md 에 있다. 여기서는 그림의 어느 위치가 미정인지만 짚는다.

위치 무엇이 미정인가 어디에
Visual Summary → 구조 해시 structure 를 검색 질의로 바꾸는 변환기의 소유자. buildCompSearchData() 의 입력이 아직 markdown 이라, Visual Summary 의 entities, relations, dataset 이 들어갈 자리가 없다 TODO.md D-4
Search Signature v2 적재 comp_signs 컬럼과 필드 명세 ../comp-signature.md 2절
후보 풀 구성의 판정 기준 capability 테이블(rlsc-capability.yaml)을 저장소 어디에 두고 어떻게 로드할지. 실물 파일이 아직 없다 TODO.md B-14
채점 위계 가중치를 어떻게 둘지. BAD 원인 분석이 지목한 다음 실험이다 TODO.md I-6
컴포넌트 확정 실패 시 맞는 컴포넌트가 없거나 대치가 실패하면 무엇을 보여줄지 TODO.md C-5
내용 대치의 입력 색인 쪽이 붙인 속성(sequence, rank, CORE/DESC/DECO/OUT)을 comp_signs 로 넘기는 경로가 없다 TODO.md I-4
RLSC → I.I.S. (3절 그림) I.I.S. 계열 티켓의 재개 시점. 2026년 6월 중순부터 멈춰 있다 TODO.md D-5

해소된 것 질의 쪽 태그가 후보 풀 구성으로 들어가는 선은 한동안 "semantic tag 를 후보 필터로 쓸지 순위 가산점으로 쓸지 미정"으로 적혀 있었다. 답이 이미 구현에 있다. 둘 다 아니고 태그 군(tier) 셋이다. 위 그림에 반영했고 상세는 content-binding-contract.md 2.1절에 있다.


6. 참고#

↑ 목차로 · 3부 머리로

11장내용 대치 contract 정본고른 다음에 무엇을 어디에 어떻게 넣나

원문 design/content-binding-contract.md GitHub 에서 보기 37 KB 원문 제목 「내용 대치 contract: 고른 다음에 무엇을 어디에 어떻게 넣는가」

이 문서의 역할 컴포넌트를 고른 다음의 세 구간이 규약에 없다. 그 구간의 실물 현황과 비어 있는 부분을 정리한다. 오너 정원석 (MORDOR) · 상태 DRAFT · 최종 갱신 2026-09-05 (1.1절 — RLSC OUT 태그 폐기, CORE/DESC/DECO 재정의. 신영빈 노드 분류는 RAT pod 파생 트랙으로 확인) 관련 schema-v0.5.md 5절 · capability-table.md · pipeline-flow.md · ../comp-signature.md · TODO.md I절

규약(schema-v0.5.md)과 capability 테이블(capability-table.md)은 '어떤 컴포넌트를 고를 것인가'까지만 정의한다. 41종 테이블에는 RLSC 슬롯 대응 열이 아직 비어 있다(그 문서 7절).

그 다음 세 구간이 비어 있다. 추출 쪽이 무엇을 심어 두었는가, 그것을 어떻게 주입하는가, 맞는 것이 없을 때 무엇을 포기하는가. 규약에 편입할 내용이 정해지면 schema-v0.5.md 와 capability-table.md 로 옮긴다.

왜 이 구간이 제품 품질을 가르는가 2026-07-26 BAD 원인 분석에서 BAD 의 85% 가 렌더링 문제가 아니라 컴포넌트와 내용이 안 맞는 문제로 나왔다. judge 채점 16,819행을 집계하고 BAD 200건을 직접 읽어 분류한 결과다 (Slack).

세부 원인은 텍스트 용량 초과, 매핑과 위계 오류, 슬롯 수 불일치, 대치 실패였다. 넷 다 이 문서가 다루는 구간이다. 검색이 컴포넌트를 잘 골라도 그 다음이 정의돼 있지 않아 결과가 나빠지고 있다.

먼저 정정한다. 이 문서 초판(2026-08-01 오전)은 구간 2 와 3을 '빈칸'으로 적었다. 사실이 아니다. 이석님의 miridih/miricanvas-iui packages/comp-sign v4.3.8 가 시그니처 추출과 검색, 랭킹, 내용 대치 네 단계를 모두 구현하고 있고, semantic tag 소비도 이미 들어가 있다. 비어 있는 것은 구현이 아니라 규약이다. 아래는 실물 코드를 읽고 다시 쓴 것이다.

이 문서 훑기

절 무엇에 답하나
0 세 구간이 어떻게 이어지나
0.1 컴포넌트 블록은 무엇으로 이루어지나. type, structure, params 가 각각 무엇이고 속성이 카테고리마다 몇 개인가
1 추출 쪽이 노드에 무엇을 심어 두었나
2 comp-sign 이 이미 무엇을 하고 있나
3 맞는 것이 없을 때 무엇을 포기하나
3.4 fallback 다음 comp-sign 대치와 5단계 property 대치는 어떻게 이어지나
3.5 structure projection(P1~P8)과 property projection(T-8)의 차이
3.7 중첩과 병치가 섞인 질의에서 N1~N5가 실제로 어떻게 진행되나
3.6 같은 pool에 comp-sign / I.I.S.를 미리 bake 하는 규칙
2.5 structure, textTree, text_map 은 각각 무엇인가
4 그래서 무엇을 해야 하나

0. 세 구간#

mermaid
flowchart LR
    subgraph EX["구간 1 추출: RAT pod (류민·신영빈)"]
        E1["RLSC 노드 분류<br/>CORE / DESC / DECO / OUT"]
        E2["category별 속성<br/>sequence · rank · attach · span"]
        E3["응집·완결 판정 (ST 규칙)"]
        E1 --> E3
        E2 --> E3
    end
    subgraph SI["구간 2 색인·랭킹·대치: comp-sign v4.3.8 (이석)"]
        S1["buildCompSignatures<br/>structure_hash · text_map · text_node_roles"]
        S2["buildCompSearchData<br/>markdown → textTree + variants"]
        S3["rankCompCandidates<br/>태그 군 → 채점 → dedupe"]
        S4["buildContentReplacement<br/>대치 텍스트 · box 보정"]
        S2 --> S3 --> S4
        S1 -.comp_signs 적재.-> S3
    end
    subgraph GAP["규약에 없는 것"]
        G1["Visual Summary structure<br/>→ 이 경로의 입력은 아직 markdown"]
        G2["category 폴백 테이블"]
        G3["장식 등급 N / N+α / N+β"]
        G4["구간 1 의 속성 → comp_signs 경로"]
    end
    EX --> SI
    GAP -.막고 있음.-> SI

    style EX fill:#f2f7ee,stroke:#6a9a4a
    style SI fill:#eef6ff,stroke:#4a7fb5
    style GAP fill:#ffeeee,stroke:#c05050,stroke-width:2px

0.1 컴포넌트 하나는 무엇으로 이루어지나#

이 절은 이 문서만 따로 읽어도 아래 용어가 통하게 하려고 둔다. 정본은 규약(schema-v0.5.md 2절)이고, 여기서는 그 내용을 이 문서가 쓰는 말로 정리한다.

0.1.1 부르는 이름은 컴포넌트 블록이고, 그 안의 자리는 필드다#

사용자가 만들고 싶어 하는 화면 조각 하나를 규약은 **컴포넌트 블록(component block)**이라 부른다. Visual Summary 문서의 components[] 배열에 든 한 항목이다. type 과 structure 와 params 를 한 번에 부르는 이름이 필요하면 이것이다. 그 안의 자리 하나하나는 **필드(field)**라 부른다. 필드는 값이 아니라 값이 들어갈 자리다.

필드 (field) 우리말로 무엇을 담나 예
type 의미 카테고리 (semantic category) 이 블록을 무엇으로 그릴 것인가. 45종 중 하나다. 세는 기준은 중분류(minor category)이고, ../spec/capability-table.md 가 그 45종을 정의한다 ProcessFlow.Steps
alternatives 대안 카테고리 (alternatives) type 을 못 쓸 때 대신 쓸 카테고리를 선호 순서로. 카테고리 이름만 담는다 [Enumeration.List]
structure 내용 (structure) 화면에 실제로 들어갈 글과 수 항목 넷, 각각 제목과 설명
params 속성 (property) 내용과 무관하게 그 카테고리에만 있는 표현 값 { marker: number }
decoration 꾸밈 선호 (decoration) 장식과 배경을 선호할지 피할지 { ornament: avoid }
id 식별자 (id) 후처리가 붙인다. 생성기는 적지 않는다 c1

내용과 속성을 가르는 기준은 한 줄이다. 사용자가 쓴 글에서 나온 것이면 structure 이고, 그 글이 없어도 카테고리 때문에 존재하는 선택이면 params 다. "1단계 계정 생성" 은 글에서 나오므로 내용이고, "단계 번호를 숫자로 붙일지 점으로 붙일지" 는 글과 무관하므로 속성이다.

세는 기준을 중분류로 고정한다 (2026-08-23). 코드의 SemanticCategory 열거값은 62개이지만(대분류 21 + 결합 41), 그 62 는 세는 데 쓸 수 없는 수다. 모든 중분류가 어느 대분류 안에 있으므로 둘을 더하면 같은 것을 두 번 센다. 종수는 45종(확정 41 + 페이지 역할 4종 미정)으로 적는다.

열려 있는 것 하나. 규약은 지금 type 에 대분류만 적는 것도 허용한다(../spec/schema-v0.5.md "대분류까지만 적어도 된다": 생성기가 중분류를 못 정하면 Hierarchy 만 적고, 검색이 그 대분류 안의 중분류 전체를 후보로 본다). 이것을 계속 열어 둘지가 결정 사항이다. 닫는 쪽(45종 중 하나를 반드시 고르게 하는 쪽)의 근거는 "확신이 없다" 를 alternatives 로 이미 적을 수 있다는 것이고, 그러면 카테고리 확정이 생성 단계에서 끝나 검색이 무엇을 받았는지 분명해진다. 여는 쪽의 근거는 생성기가 억지로 하나를 고르면 틀린 중분류가 1군 태그로 나가 검색을 오히려 좁힌다는 것이다. 정하는 사람은 규약 오너다.

alternatives 에 컴포넌트 블록 전체를 담지 않는 이유. 대안마다 structure 를 다시 적으면 같은 내용을 여러 벌 복제하게 된다. structure 는 카테고리와 무관한 추상 구조로 설계돼 있고, 검색이 후보 카테고리마다 그 구조를 다르게 읽는다(../archive/projection-model.md). 그래서 대안은 이름만 있으면 되고, 생성 속도와 품질을 맞바꿀 일이 애초에 생기지 않는다.

0.1.2 속성이라는 말은 한 자리에만 쓴다#

정식 용어는 속성이다. 같은 것이 자리마다 다른 이름으로 불려서 헷갈렸을 뿐, 가리키는 대상은 하나다.

어디 그 자리의 이름 무엇
질의(사용자가 바라는 것) params 필드 "이 모양이었으면 좋겠다"
컴포넌트(이미 있는 디자인) iis_semantic_component.properties "이 컴포넌트는 이 모양이다"
규약과 코드 capability.reads.params 그 카테고리가 읽는 속성 이름 목록

결정 시점이 다르면 접두어로 가른다. 새 명사를 만들지 않는다.

말 무엇 예
추출 속성 (extracted property) 추출 쪽이 원본 디자인을 읽어 계산한 값. 컴포넌트마다 다르다 이 컴포넌트의 회전은 시계 방향이다
질의 속성 (declared property) 카테고리가 미리 정의해 두었거나 사용자가 채운 값 반시계 방향이었으면 좋겠다

"표현 선택" 은 정식 용어가 아니다. structure(내용)와 대비해 설명할 때 쓰는 평문일 뿐이고, 표와 용어 목록에는 속성만 올린다. 5단계 추출기 이름(I.I.S. Semantic Component Property)과 코드 식별자, Jira 티켓이 이미 속성이라는 말을 쓰고 있어 그쪽에 맞춘다.

0.1.3 structure 의 스키마#

structure 는 넷으로 이루어진다. 넷 중 entities 와 dataset 은 적어도 하나가 있어야 한다.

칸 무엇 언제 쓰나
entities[] 항목 목록 글로 적는 대부분의 카테고리
dataset 표나 수치 한 벌 표와 차트 계열
relations[] 항목 사이의 관계 교집합(intersects), 비교(compares)
columns[] 표 머리글 entities 로 표를 적을 때

entities[] 항목 하나가 가질 수 있는 칸은 아래와 같다. label 만 필수이고 나머지는 카테고리가 읽을 때만 채운다.

칸 무엇 칸 무엇
label 항목 이름 (필수) weight 층 높이나 비중
items[] 상세 줄 speaker 말한 사람
value 수치 author 글쓴이
unit 수치 단위 source 출처
period 기간이나 시점 avatar 사람 그림
emphasis 강조 여부 organization 소속
values[] 표 한 행의 값들 children[] 하위 항목 (계층)

entities 항목에는 위 표 밖에 id 와 components[] 두 칸이 더 있다. 둘 다 생성기가 채우지 않는다. id 는 후처리가 배열 순서로 붙이고, components[] 는 항목 안에 다른 컴포넌트를 중첩하는 자리로 예약만 되어 있다.

dataset 은 두 모양뿐이다.

모양 담는 것
measure 값 하나와 그 부속(value, unit, min, max, target, delta, thresholds[]). 게이지와 큰숫자가 쓴다
matrix 열 정의(columns[])와 행 배열(rows[]). 표와 차트가 쓴다. 첫 열이 범주 축이다

정본은 규약 2.1절(타입)과 2.2절(JSON Schema)이고, 저장소 구현은 src/lib/visual-summary/types.ts 와 schema.ts 다. 셋이 어긋나면 JSON Schema 를 따른다.

0.1.4 속성은 모두 몇 가지인가#

카테고리 41종 중 25종이 속성을 가진다. 나머지 16종은 하나도 없다(Enumeration_List, KPIKeyMetric_BigNumber, ReasoningStructure_ProblemSolution 등). 서로 다른 이름은 27개이고, 한 카테고리가 갖는 것은 1개에서 3개다.

카테고리 속성 (값의 모양)
Enumeration_ListingTable header (참/거짓)
AttributeComparison_ComparisonTable axis (짧은 글), header (참/거짓)
AttributeComparison_RadarChart maxValue (수)
RelationalComparison_SWOT order (참/거짓)
RelationalComparison_Quadrant axisLabels (x, y 키를 가진 객체)
RelationalComparison_BubbleChart sizeScale (linear, log)
Transformation_Transformation direction (forward, backward)
ProcessFlow_Steps marker (number, letter, dot), sequence (참/거짓)
Progression_Staircase direction (ascending, descending)
TimelineSchedule_Timeline orientation (horizontal, vertical)
TimelineSchedule_GanttChart unit (day, week, month)
TimelineSchedule_Calendar weekStart (Mon, Sun)
Hierarchy_HierarchicalTree orientation (vertical, horizontal)
Hierarchy_Pyramid apex (first, last)
ScopeCoverage_NestedScope direction (inward, outward. 2026-09-04 B-34 신설, 기본 inward), shape (circle, rectangle)
Cycle_Cycle center (짧은 글), rotation (clockwise, counterclockwise)
MagnitudeRanking_BarChart orientation (vertical, horizontal), sort (asc, desc, none)
ProportionComposition_StackedBarChart normalize (참/거짓), orientation (vertical, horizontal)
ProportionComposition_PieChart labelPosition (inside, outside), startAngle (수)
ProportionComposition_DonutChart centerLabel (짧은 글), labelPosition (inside, outside), startAngle (수)
ProportionComposition_PictogramChart icon (짧은 글), unitPerIcon (수)
Trend_LineAreaChart fill (참/거짓)
Trend_ComboChart axis (left, right), type (bar, line)
Conversion_Funnel conversionRate (참/거짓)
TextBasedCommunication_LongFormTextLayout title (참/거짓)

같은 이름이 여러 카테고리에 나오면 뜻도 같다. orientation 이 넷(트리, 막대, 누적 막대, 타임라인), header, axis, direction, labelPosition, startAngle 이 각각 둘이다. 나머지 21개는 한 카테고리에만 있다.

값의 모양은 다섯 갈래다. 참/거짓, 정해진 낱말 중 하나, 수, 짧은 글, 정해진 키를 가진 객체다. 이름 목록의 정본은 규약 capability-table.md 2절 "선택 속성" 열이고, 값의 모양은 저장소의 src/lib/visual-summary/iis/propertyVocabulary.ts 가 정의한다. 컴포넌트에서 속성을 뽑을 때 그 모양과 어긋난 값은 버리고 센다.


1. 구간 1: 추출 쪽이 심어 둔 것#

1.1 RLSC 노드 분류: CORE / DESC / DECO / OUT#

신영빈님이 Steps 대상으로 구현했고 류민님께 공유했다 (2026-07-31, #mordor스쿼드).

"Steps 응집 판정 + 노드 태깅 로직 구현, core component 외 요소를 RLSC 노드 ID 에 DESC·DECO·OUT 으로 태깅해 JSON 출력. 류민님께 공유함"

앞서 류민님이 규칙을 설계했다 (2026-07-27, #mordor스쿼드). 다만 이 설계는 IIS/visual-summary 쪽이 아니라 RAT pod 의 semantic tagging 검증 작업(Semantic Tagging 절차 명세 — MORDOR(RAT pod) 측, 페이지 단위 태깅이 컴포넌트로 잘 뽑히는지 검증하는 트랙)에서 나온 것이고, 이 트랙 자체가 holding 또는 deprecated 다 (2026-09-05, 정원석 확인).

"desc/deco 노드 분류 규칙 설계", "core-C 최소 정의(core vs desc 경계) 확정 → MOR-1789·MOR-1781 판정 규칙 입력"

신영빈님이 이 규칙을 Steps 응집 판정에 가져다 쓰면서 OUT 을 추가해 공유했지만, IIS 대치 계약에서 유효한 것은 아래 세 태그뿐이다 (2026-09-05, 정원석 확정).

태그 무엇인가 대치에서의 의미
CORE 컴포넌트 경계 그 자체 반드시 채워야 하는 슬롯
DESC satellite 컴포넌트 있으면 채우는 슬롯
DECO 컴포넌트 안팎을 가리지 않는 장식 요소 비우거나 제거 가능

폐기 OUT 태그는 쓰지 않는다. 원 출처(신영빈, 2026-07-31)의 "DESC·DECO·OUT" 표기는 RAT pod 트랙(holding/deprecated)에서 파생된 표기이고, IIS 쪽 경계·장식 판정과는 무관하다.

이 분류가 4-1절 장식 논의의 색인 쪽 재료다. 규약은 장식을 "스키마에 담을 자리가 없다"고 적었는데, 추출 쪽은 이미 노드 단위로 장식을 식별하고 있다.

1.2 category 별 속성: Steps 의 경우#

MOR-1856 [RAT] 반복 단위 순서 라벨링 (order-tagger) (신영빈, Draft) 이 semantic category 하나에 어떤 속성을 심는지의 첫 사례다.

속성 무엇인가
sequence 기하 좌표로 확정한 반복 단위의 순서. 그리드는 마커 기반으로 방향 설정
rank 순위 값 (1..N)
attach 연결자(화살표 등)를 앞 단위에 귀속시킨 것
span 여러 gap 을 가로지르는 연결자

달성 응집 PASS 911건 전량에 부여 (rank 3,648, attach 767, span 181). 마커 보유 540건에서 마커 순서와 기하 일치 534건 = 99% (기준 95%).

남은 문제는 셋이다. 그대로 대치 정확도의 상한이 된다.

  • 카드에 번호가 없는 371건(41%)은 순서가 맞는지 확인할 방법이 없다
  • 연결자 개수가 간극 수와 안 맞는 41건 (카드 3개면 화살표 2개여야 하는데 21건은 더 많고 10건은 카드 수만큼)
  • 원본 마커가 1 2 3 3 처럼 연속이 아닌 10건이 있다. 디자이너 실수인지 의도인지 판단할 수 없다

1.3 다른 category 의 속성은 정의돼 있지 않다#

sequence / rank / attach / span 은 Steps 를 위해 설계된 속성이다. 다른 카테고리에 무엇이 필요한지는 열거된 적이 없었고, 질의 쪽 선언은 2026-08-02 에 capability-table.md 가 41종 전량 정의했다. 남은 것은 추출 쪽 경로(어느 데이터가 그 속성을 담아 오는가)다. 성립 조건을 뒤집으면 최소한 아래가 필요하다.

semantic category 필요할 속성 현재 상태
Steps / Timeline / Staircase / Cycle sequence · rank · attach · span 정의됨 (MOR-1856)
Pyramid 층 방향(apex first/last), 층별 크기 질의 쪽은 apex(기본 last)와 weight 로 정의됨 (capability-table.md 2.3절). 추출 쪽 경로는 없다
Venn / DisjointEuler 교집합 영역 노드 식별, 교차/비교차 구분 RLSC 가 두 타입을 구분하지만 교차 영역 텍스트 슬롯 지정은 없다
Versus 대비쌍의 좌/우(또는 상/하) 귀속 v0.5 가 type 41종에 넣었고 relation compares 가 교차 텍스트를 받는다. 추출과 색인 쪽 경로는 없다
Quadrant 축 라벨, 사분면 위치 질의 쪽은 matrix(x, y)와 axisLabels 로 정의됨 (capability-table.md 2.1절). RLSC 대응 확인 남음
ListingTable / ComparisonTable 행·열 인덱스, 헤더 행 식별 질의 쪽은 matrix 와 header 로 정의됨. RLSC 대응 확인 남음
Gauge / 차트 계열 value·target·min·max 슬롯 위치, series 축 귀속 dataset 은 정의됐고 RLSC 쪽 대응 노드 지정이 없다

이 표를 채우는 일이 README.md 1절의 5단계("semantic category 별 고유 property 까지 그 node 에 보존")이다. Steps 는 4단계를 기다리지 않고 먼저 진행 중이므로 로드맵과 실제 작업이 어긋나 있다.


2. 구간 2: comp-sign 이 이미 하는 것#

packages/comp-sign v4.3.8 README 가 스스로를 "컴포넌트 매칭 파이프라인 전체"로 정의한다. 네 함수다.

# 함수 하는 일
1 buildCompSignatures 컴포넌트 → 구조 해시 · text_map · text_node_roles · 채점 feature. DB 적재용
2 buildCompSearchData markdown → 요약 트리(textTree) + 조회용 {hash, variant, demand}[]
3 rankCompCandidates 후보 채점 · 중복 제거 · 정렬
4 buildContentReplacement 대치 텍스트 · box 보정 · 형제 그룹 생성

DB 접근과 후보 fetch 는 호출자(BE) 몫이고, 이 패키지는 순수 계산만 한다.

2.1 semantic tag 는 이미 랭킹에 들어가 있다#

출처 packages/comp-sign/src/comp-match/semanticTags.ts 의 파일 머리 JSDoc 1944행("검색에서 쓰는 방식: 태그 군(tier) 3단계")이 정의이고, 적용부는 src/rank-candidates.ts 102행 주석과 163177행 정렬 코드다. TagTier 타입은 semanticTags.ts 168행, 목표치 기본값은 156~165행에 있다.

TODO.md D-1 "v7 태그를 후보 필터로 쓸지 순위 가산점으로 쓸지"의 답이 여기 있다. 둘 다 아니고 태그 군(tier)이다.

무엇을 나누는 기준인가#

군은 컴포넌트의 성질이 아니라, 그 컴포넌트를 후보로 끌어온 근거의 출처를 나눈다. 같은 컴포넌트도 검색 조건이 달라지면 다른 군에 들어간다.

군 그 컴포넌트가 후보에 들어온 이유 근거의 출처
0군 사용자가 직접 고른 분류에 속한 태그를 갖고 있다 검색 조건 UI 의 분류·태그 선택
1군 LLM 이 사용자의 글을 보고 추정한 태그를 갖고 있다 (0군과 겹치는 태그는 뺀다) 실사용에서는 LLM 이 글과 함께 내려준다. PoC 에서는 예시 markdown 에 붙은 태그
2군 0군에도 1군에도 해당하는 태그가 하나도 없다 태그와 무관하게 구조 해시만으로 걸린 후보

0군과 1군의 차이는 사용자가 명시한 의도인가, 기계가 추정한 의도인가 이다. 명시한 쪽을 항상 앞에 둔다.

어떻게 동작하는가#

후보를 채울 때 군 순서로 채우고, 목표치를 넘으면 뒤 군은 조회하지 않는다.

0군을 채운다  →  누적 >= tier0Target(기본 50)이면 종료
                아니면 1군으로 남은 자리를 채운다
                     →  누적 >= tier1Target(기본 50)이면 종료
                        아니면 2군으로 남은 자리를 채운다

한 군 안에서 여러 태그가 자리를 나눌 때는 allocateSlots 와 fillByTagQuota 의 max-min fair 분배를 쓴다. 태그마다 1/M 씩 균등하게 가져가되, 그만큼 채우지 못하는 태그의 남은 자리는 여유가 있는 태그끼리 다시 나눈다.

정렬할 때 군 순서를 점수보다 우선한다. 원문 그대로 옮기면 이렇다.

"군의 순서는 랭킹에서 절대적이라, 1군 후보가 아무리 점수가 높아도 0군 후보를 앞설 수 없다(같은 군 안에서만 점수 정렬). 그래서 태그 일치에 주는 가점 같은 건 없다 — 우선순위는 오직 군이 정한다."

구현은 tier 로 1차 정렬하고 점수로 2차 정렬한다(rank-candidates.ts 177행). 중복 제거 단계에서는 score - tier * TIER_STRIDE 로 같은 순서를 만든다.

예로 보면#

사용자가 '온보딩 4단계'를 입력하고 검색 UI 에서 분류 Process Flow 를 골랐다고 하겠다.

군 이 검색에서 무엇이 들어오나
0군 ProcessFlow_Steps 태그를 가진 컴포넌트
1군 LLM 이 글을 보고 추정한 태그, 예를 들어 TimelineSchedule_Timeline 이나 Enumeration_List 를 가진 컴포넌트 (ProcessFlow_Steps 는 0군에서 이미 썼으므로 뺀다)
2군 위 어느 태그도 없이 구조 해시만 맞는 컴포넌트

0군에서 50개가 모이면 1군과 2군은 조회하지 않는다. 0군이 12개뿐이면 1군으로 38개를 더 채운다. 그러고도 모자라면 2군으로 채운다. 최종 정렬에서는 0군 12개가 점수와 무관하게 1군 38개보다 앞에 놓인다.

규약과 어긋나는 지점#

규약 5절의 fallback 규칙은 "성립 조건을 못 채우면 후보에서 빠진다"는 이분법인데, 구현은 제외하지 않고 뒤 군으로 미룬다. 규약이 논의 중인 등급 3분(필수/선호/무시, schema-v0.4.md 4-1절)과 방향은 같지만 축이 다르다. 규약은 컴포넌트가 무엇을 소화하는지를 보고, 구현은 그 후보가 어디서 왔는지를 본다. I-5 에서 맞춰야 한다.

태그 값의 형식#

코드가 쓰는 SSoT는 {major}_{minor} 형식이다.

"식별자(id)가 유일한 정본 값이다 — Enumeration_List 처럼 {major}_{minor} 이고, DB(design_object_semantic_tags.tags v2.2 · comp_signs_v3_4.semantic_tags)에 그대로 들어 있다. minor 이름만 담던 옛 어휘(v2.0)는 버렸다 — 두 형식을 섞으면 조회가 조용히 0건이 된다."

분류 규모도 코드 쪽이 정확하다. major 23 → minor 49 이다. semantic-taxonomy-v7.md 가 일반 29 + 표/차트 23 = 52 로 센 것에서 양쪽 카탈로그에 중복 등장하는 3종(Timeline, HierarchicalTree, Flow)을 빼면 49 로 일치한다.

저장 위치는 design_object_semantic_tags 를 iui.comp_signs_v3_4.semantic_tags(text[], not null default '{}')로 denormalize 하고 GIN 인덱스를 건다. 미분류(빈 배열)는 v3.4 풀 기준 5% 안팎이다.

2.2 대치는 buildContentReplacement 가 한다#

입력은 textTree(검색에 쓴 요약 트리) + variant + text_map + text_node_roles + sheetJson 이다. RLSC 는 필요 없다. 노드 role 과 글자 용량을 text_node_roles(v36+, {role, chars})가 담는다.

box 보정 옵션이 넷이다.

옵션 하는 일 기본값
relieveTopBias 위로 쏠린 텍스트를 글씨 절반만큼 아래로 이동 true
ensureBgMargin 텍스트 박스를 배경 도형 경계 안쪽으로 clamp true
expandText 형제·부모와 겹치지 않는 선에서 가로 확장 false
tidyLayout force-directed 시뮬레이션으로 box 일괄 조정 (켜면 위 셋을 대체) false

2.3 채점 항목은 20여 종이다#

comp-match/scoringWeights.ts 의 DEFAULT_SCORING_WEIGHTS 이다. 규약이 "채점"이라고만 적은 부분의 실물이다.

areaRatio(40), expansionPenalty(5), unwrapPenalty(5), keyValueSplitBonus(5), titleFontSizePenalty(5), textDiff(4), visualSiblingSplit(3), styleMismatch(2), illustrationBonus(2), parentChildSameStyle(1), mergeChildrenPenalty(1), fontSizeBonus(0.5), placeholderPenalty, textShrinkPenalty, offCenterPenalty 외.

2.4 comp-sign 변형 3종과 N/N+α/N+β 는 쪼개는 기준이 다르다#

comp-sign 은 컴포넌트당 변형을 최대 3건 만든다: 원본 / Text:Subtitle 전부 제거 / Text:Hl 전부 제거.

comp-sign 변형 7/24 회의의 N/N+α/N+β
쪼개는 기준 텍스트 역할(Subtitle, Hl) 제거 장식 수준
목적 텍스트가 적은 입력도 매칭 (검색 리콜) 사용자가 원하는 표현 밀도
만드는 곳 comp-sign 패키지 정해지지 않았다

둘을 그대로 두면 컴포넌트당 변형이 최대 9벌이 된다. 정합 방향은 셋이다.

  1. 하나로 합친다. DECO 제거를 comp-sign 변형에 추가해 원본 / DESC 제거 / DECO 제거 로 재정의
  2. 둘을 따로 둔다. 텍스트 역할과 장식 수준을 별도 필드로 두고 조합은 검색 시점에 정한다
  3. 대치 시점에 처리한다. 하나만 색인하고 주입할 때 DECO 노드를 지우거나 남긴다

3번이 회의 원칙("장식이 있는 건 삭제할 수 있지만 없는 건 추가가 어렵다")과 맞는다. 다만 이건 이 문서의 해석이고, 이석님과 류민님께 확인해야 한다. 신영빈님이 2026-07-31 에 올린 "꾸밈/설명을 단계적으로 늘리고 줄이는 구상안"과 "Steps Capacity 확장 PoC"는 1번과 2번 방향이므로, 지금 정하지 않으면 어긋난다.

2026-08-03: 3번을 초기 결정으로 채택했다. 색인에는 장식 보존본을 한 벌 넣고, 축 선호에 맞춰 지우는 것은 대치 시점에 한다. 색인을 한 벌로 두면 변형 9벌 곱셈이 사라진다 (decoration-model.md 4.5절).

B-19 결정 (2026-09-05 정원석) — 대치 시점 처리로 옮기지 않는다. comp-sign v4.3.8 은 변형 3종을 색인 시점에 미리 계산해 저장하는 현행 방식을 그대로 유지한다.

위 표의 '만드는 곳: 정해지지 않았다' 칸도 이 결정으로 답이 난다. 장식 축은 대치 모듈이 소비하고, 색인은 보존만 한다.

2.5 대치 입력 용어: structure, textTree, text_map#

같은 "트리"라도 층이 셋이다. 문서와 코드에서 이름을 섞어 쓰면 fallback 이후 대치 계층을 따라가기 어렵다.

이름 무엇인가 만드는 곳 / 쓰는 곳
Visual Summary structure YAML의 entities, relations, dataset. 검색 질의와 structure projection(P1~P8) 의 입력 Summary 생성기, validate.ts, projection.ts
textTree comp-sign buildCompSearchData 가 markdown을 파싱해 만든 대치용 텍스트 트리 (+ variant) compSignService.ts, buildContentReplacement 입력
text_map / text_node_roles 컴포넌트 signature의 슬롯 용량·역할 (chars, lines, role). 런타임 용량 SSoT (legacy textCapacity 아님) buildCompSignatures 적재, 채점·대치
RLSC text slots PoC 자체 대치(binding.ts)가 RLSC 서브트리의 Title/Description/Marker 슬롯에 직접 넣는 경로 binding.ts, rlsc.ts

다리는 structure → markdown → buildCompSearchData → textTree 이다 (../plan/poc-guide.md 7.1절, markdown.ts). comp-sign 경로는 Visual Summary YAML을 직접 받지 않고 markdown과 textTree를 받는다.

PoC에서 두 대치 경로를 컴포넌트마다 나란히 보려면 ../plan/poc-guide.md 5.1절의 대치 경로 비교 탭(/visual-summary → "대치 경로 비교")을 쓴다.


3. 구간 3: 폴백#

3.1 규약에 있는 것#

schema-v0.5.md 5절 fallback 규칙 다섯이다.

  1. 성립 조건을 못 채우면 그 카테고리가 후보에서 빠진다
  2. 후보가 없으면 fallback 순서를 최대 두 칸 내려간다 (capability-table.md 3절)
  3. 마지막 fallback 은 셋(List, LongFormTextLayout, HighlightedMessage)이라 언제나 하나는 성립한다
  4. 못 그리는 relations 나 슬롯은 삭제하지 않고 텍스트로 흘려보낸다
  5. type 과 실제 구조가 충돌하면 구조가 우선

7/24 회의가 두 줄을 더했다. "대치 시 스텝에 필요한 property 값을 YAML 에서 꺼내 슬롯에 넣고, 나머지 영역은 일반 텍스트 쪽으로 흘려보냄", "리스트는 절대 포기하지 않고, 빅넘버는 포기 가능한 식".

3.2 폴백은 다섯 층이고 두 층만 있다#

층 무엇을 포기하나 상태
0. 복합 구조 노드가 여럿인 질의에서 어느 노드부터 포기하나 규약에 생겼다 (2026-08-28). capability-table.md 3.6절 N1~N5. 오케스트레이션 코드가 없다 (../TODO.md I-11)
1. 카테고리 Gauge 가 없으면 BigNumber 로? Steps 가 없으면 Timeline 으로? 규약에는 생겼다 (2026-08-02). capability-table.md 3절 fallback 순서. 스쿼드 검토와 구현 반영이 남았다 (TODO.md E-2, I-5)
2. 등급 N+β 가 없으면 N+α 로, 그것도 없으면 N 으로 없다. 등급 자체가 규약에 없다 (schema-v0.4.md 4-1절 원 논의, TODO.md B-1)
3. 슬롯 항목이 5개인데 컴포넌트가 3칸이면 부분적으로 있다. comp-sign 이 변형(자식 수 확장·병합·unwrap)과 expansionPenalty·mergeChildrenPenalty 로 다루고, 신영빈님의 Steps Capacity 확장 PoC 가 색인 쪽에서 다루기 시작했다
4. 구조 특수 컴포넌트가 전부 탈락하면 List 로 있다 (규약 1절 "어떤 구조든 리스트로 읽힌다"). comp-sign 의 태그 2군이 이에 해당한다

복합 구조 층은 0번이다. 노드가 여럿이면 먼저 어느 노드를 다룰지 정해야 하고, 그 결과로 남은 노드 하나하나가 1번부터 4번을 탄다. 순서와 규칙은 3.7절에서 예시로 따라간다.

카테고리 층과 등급 층이 없으면 구조 층으로 직행한다. 카테고리 층은 규약에 생겼지만(3.1절) 구현 반영 전이고, 구현은 지금 '태그 일치 아니면 태그 무관 전체'라는 계단 하나뿐이라 그 사이가 비어 있다.

README.md 3절의 분포가 이유다. 특수 컴포넌트 풀이 각 51~164건이라, 카테고리 층과 등급 층이 없으면 대부분 2군으로 떨어진다.

3.4 검색 fallback 이후 대치 계층#

카테고리 fallback(capability-table.md 3절, fallback.ts)으로 어떤 컴포넌트 종류를 고를지 정한 뒤, 무엇을 어디에 넣을지는 아래 계층을 따른다. 이 절이 그 계층의 SSoT 다.

mermaid
flowchart TB
  SUM["Summary (containment / 이종 nested 포함)"]
  EXACT["색인에서 구조·해시 exact match"]
  FB["semantic fallback + structure projection P1~P8"]
  L4["4단계 1차: comp-sign signature + textTree 대치"]
  L5["5단계 보조: property mapping + cross-type property projection (T-8)"]
  SUM --> EXACT
  EXACT -->|"후보 없음 / 불일치"| FB
  FB --> L4
  L4 -->|"용량·매핑·속성 불일치 잔존"| L5

왜 fallback 다음 계층이 필요한가. Summary가 containment 또는 이종(heterogeneous) component 구조(entity.components[], schema-v0.5.md 8.7절 Topological Relation)를 만들면, 색인에서 구조가 정확히 같은 단일 컴포넌트를 찾기 어렵다. exact match가 실패하면 capability-table fallback 그래프로 카테고리를 내려가고, structure는 P1~P8로 도착 카테고리에 맞게 접힌다.

4단계 1차 기대 (comp-sign). 고른 컴포넌트에 대해 이석님 buildContentReplacement가 {textTree, textMap, textNodeRoles, sheetJson}으로 textTree 기반 내용대치를 한다 (2.2절). PoC 실측(../plan/comp-sign-bridge.md 6.2절)에서 결함 대부분이 검색 쪽이지만, 대치 탓도 17건 있고 경로를 바꿔도 넘침이 줄지 않는다는 것은 두 대치 경로 모두 용량과 매핑 한계를 공유한다는 뜻이다.

5단계 보조 (property). 4단계만으로 Steps의 sequence, Quadrant의 axisLabels 같은 카테고리 고유 속성이 슬롯에 맞지 않으면 node-tag-and-property-poc-blueprint.md T-8 속성 기반 검색과 대치와 이종 type 간 property projection이 필요하다. T-7(추출)은 PoC에 있으나 T-8은 설계 지점만 있다.

PoC 자체 대치와의 관계. binding.ts는 comp-sign을 기다리지 않고 Visual Summary structure를 RLSC 슬롯 규칙(entity.label → Title, items → Description 등)으로 직접 넣는 규약 기반 대치다. 실서비스는 comp-sign 경로를 탄다. 두 경로가 같은 후보와 같은 용량 근거로 재야 MOR-1926/MOR-1927 판정이 성립한다.

D-7(이종중첩 템플릿 검색 풀 제외)과의 관계. 2026-08-11 ../TODO.md D-7은 색인에 이미 있는 이종중첩 템플릿을 후보에서 빼는 정책이다(전체 약 2%). Summary 질의가 nested 구조를 선언했을 때 fallback으로 단일 카테고리 컴포넌트를 고르는 흐름과 같은 문제를 다른 층에서 본다.

질의 쪽 순서는 2026-08-28에 정했다. 종전에 이 자리는 "질의 쪽 containment를 단일 type으로 flatten한 뒤 fallback하는지, entity.components[]를 유지한 채 node 태그(I.I.S.)로 대치 범위를 자르는지 아직 한 문서에 못 박지 않았다"로 열려 있었다. 답은 유지하는 쪽이다. 구조를 그대로 두고 노드마다 카테고리를 내려 보고(N2), 실패하면 최상위 부모부터 벗겨 조각마다 찾고(N3, N4), 평탄화(N5)는 마지막에만 한다. 순서와 규칙 다섯의 SSoT는 capability-table.md 3.6절이고, 한 단계씩 따라가는 예시는 아래 3.7절이다.

색인 쪽 D-7은 그대로 둔다. 질의 순서가 정해져도 색인 풀 정책은 별개다. 다만 3.6절 N1(질의 트리 전체를 담은 후보 찾기)은 D-7이 유지되는 동안 후보가 언제나 0이므로, 되돌릴지를 ../TODO.md D-8로 열어 두었다.

계층 무엇을 하나 PoC 코드 / 실서비스 상태
복합 구조 순서 (N1~N5) 노드가 여럿일 때 어느 노드부터 포기하나 없다 규약 확정(2026-08-28, capability-table.md 3.6절). 코드 미착수(I-11)
Exact structure match 해시·태그로 동일 구조 후보 comp-sign buildCompSearchData, PoC search.ts 동작. 풀 희소 시 실패 잦음
Semantic fallback 카테고리 강등 + P1~P8 fallback.ts, projection.ts 규약·코드 완료
4단계 comp-sign 대치 textTree + signature compSignService.ts, comp-sign 패키지 실물 경로. 규약 contract 미완(I절)
PoC 규약 RLSC 대치 structure → RLSC 슬롯 binding.ts PoC 비교·MOR-1926용
5단계 property 대치 cross-type property projection T-7 extractProperty.ts, T-8 미착수 추출만 부분

PoC 규약 RLSC 대치는 실서비스 경로가 아니다. binding.ts는 Visual Summary structure를 RLSC Title/Description/Marker 규칙으로 직접 넣는 MORDOR PoC 전용 베이스라인이다. 내용시각화 BE가 쓰는 대치는 comp-sign buildContentReplacement()(4단계)뿐이다. 플레이그라운드 ④와 대치 경로 비교 탭 왼쪽이 이 경로다. comp-sign과 같은 후보 pool 과 같은 용량 근거로 재야 MOR-1927 판정이 성립한다 (../plan/poc-guide.md 4.1절).

3.6 PoC 평가 pool과 사전 적재#

전 컴포넌트(로컬 수만 건, prod 17만+건)에 comp-sign 시그니처와 I.I.S. 속성을 한꺼번에 구울 수는 없다. 세 대치 경로를 공정하게 비교하려면 같은 후보 pool 위에 미리 bake 해 둔다.

bake 대상 저장소 PoC 현재 비고
comp-sign structure_hash → signature pipeline comp_signatures compSignService.ts가 메모리 즉석 빌드 T-2: pool subset은 DB 적재 후 조회로 전환
I.I.S. 경계 (4단계) iis_semantic_component T-4 배치 추출 가능 LLM 비용. pool 밖 DO는 비교 대상 아님
I.I.S. 속성 (5단계) 같은 테이블 property 컬럼 T-7 배치 추출 가능 T-8 대치 PoC 전제. 경계 없으면 속성 없음

pool 고정 규칙

  1. 기준 pool은 indexPool.ts 술어와 동일하다 (sheet_json + structure_json 둘 다 있음). 확장 색인(visual-summary-expand-index.ts)과 같은 집합을 쓴다.
  2. 평가 pool은 그중 고정 subset이다. 실사용 50건(컴포넌트 65)이 검색과 대치 입력이고, 색인 후보는 subset 전체(또는 subset + 태그 균형 샘플)로 둔다.
  3. 후보 pool 크기는 약 5,000건으로 둔다 (2026-08-23 정원석 제안). 지금 로컬 색인이 2,468건이고 실서비스는 178,095건이다. 5,000건이면 두 가지가 동시에 된다. 카테고리별로 후보가 마르지 않아 정책 비교가 pool 크기의 성질에 휘둘리지 않고, LLM 으로 경계와 속성을 굽는 비용과 시간이 하루 안에 끝난다. 실측 근거는 시그니처 빌드가 2만 건에 약 4.4분, 5만 건에 약 9.9분이고 속성 추출이 137건에 4분(입력 23만 토큰)이라는 것이다. 5,000건이면 속성 추출은 약 2.4시간, 입력 약 840만 토큰으로 어림된다. 이 값은 아직 실행으로 확인한 것이 아니라 제안이다. 굽고 나서 카테고리별 후보 수를 표로 남기고, 마르는 카테고리가 있으면 그때 올린다.
  4. comp-sign signature, I.I.S. 경계, I.I.S. 속성은 같은 design_object_id 집합에 대해 bake 한다. 한쪽만 있으면 5단계 경로 비교가 성립하지 않는다.
  5. 5단계 property 대치(T-8) 화면은 속성 bake + T-8 규칙(I-7)이 있어야 연동한다. 지금은 4단계 comp-sign vs PoC RLSC 2-way만 가능하다.

할 일 SSOT는 ../TODO.md I-8, I-9, blueprint T-2/T-8이다.

3.5 투영 두 종류#

structure projection (P1~P8) 과 property projection (5단계, T-8) 은 이름만 비슷하고 하는 일이 다르다. 두 말이 가리키는 것은 컴포넌트 블록의 서로 다른 필드다. 앞의 것은 내용(structure)을 옮기고, 뒤의 것은 속성(params)을 옮긴다. 두 필드가 각각 무엇인지는 0.1절에 적었다.

structure projection property projection
언제 같은 fallback 그래프 안, 카테고리 A→B로 semantic fallback할 때 fallback 이후에도 대치·속성이 맞지 않을 때
무엇을 옮기나 entities, matrix→entities, relation. params(속성)만 버림 sequence, orientation, apex, axisLabels 등 카테고리별 속성
type 관계 capability-table에 정의된 fallback 엣지 (주로 같은 계열) 이종 type 간 슬롯·속성 매핑 (T-8 설계 대상)
문서 capability-table.md 3.4절 node-tag-and-property-poc-blueprint.md T-8
코드 projection.ts 미구현

P1~P8만으로는 "List 안의 BigNumber"처럼 서로 다른 semantic category 사이 property를 옮기는 문제를 풀지 못한다. 그건 5단계 I.I.S. (Semantic Component Property)와 T-8의 영역이다.

3.7 복합 구조 fallback 을 한 단계씩 따라간다#

capability-table.md 3.6절이 순서와 규칙의 SSoT 다. 이 절은 그 순서를 중첩과 병치가 함께 있는 질의 하나에 끝까지 적용해 본다. 이 예시의 질의는 실측이 아니라 규약이 표현할 수 있는 모양으로 만든 것이다. 1단계 생성기가 entity.components[] 를 아직 채우지 않으므로(2.1절) 지금 나올 수 있는 값이 아니다. 색인 후보의 유무는 실측이 아니라 D-7 정책과 3.6.5절에서 따라 나오는 것이라고 밝혀 둔다.

3.7.1 출발 질의#
yaml
version: "0.5.4"
title: 2026 상반기 사업 리뷰
components:
  # A. 분기 4개. 항목마다 대형 수치와 서술 요약이 함께 들어간다
  - type: Enumeration.List
    structure:
      entities:
        - label: 1분기
          items: [신규 계약 12건]
          components:
            - type: KPIKeyMetric.BigNumber
              structure:
                entities:
                  - { label: 매출, value: 12, unit: 억원 }
            - type: TextBasedCommunication.LongFormTextLayout
              structure:
                entities:
                  - label: 요약
                    items:
                      - 신규 시장 세 곳에 진입해 계약 12건을 확보했다.
                      - 단가 인하 없이 확보한 계약이라 마진이 유지됐다.
        - label: 2분기
          items: [신규 계약 15건]
          components:
            - type: KPIKeyMetric.BigNumber
              structure:
                entities:
                  - { label: 매출, value: 15, unit: 억원 }
            - type: TextBasedCommunication.LongFormTextLayout
              structure:
                entities:
                  - label: 요약
                    items:
                      - 기존 고객 재계약이 늘어 계약 건수가 증가했다.
        # 3분기, 4분기도 같은 모양이 반복된다
  # B. 하반기 실행 계획 3단계
  - type: ProcessFlow.Steps
    structure:
      entities:
        - { label: 채널 정비 }
        - { label: 가격 개편 }
        - { label: 해외 확장 }

노드를 세면 이렇다.

자리 카테고리 개수
최상위 Enumeration.List(A), ProcessFlow.Steps(B) 2
A 의 항목마다의 묶음 카테고리 없음. Unclassified 경계다 (N-R3) 4
그 묶음 안 KPIKeyMetric.BigNumber, TextBasedCommunication.LongFormTextLayout 8

최상위 둘은 서로 독립으로 진행한다 (3.6.1절). B 는 ProcessFlow.Steps 단일 카테고리이고 성립 조건이 entities 2개 이상이라 N1 에서 바로 후보가 걸린다. 아래는 A 만 따라간다.

A 의 항목마다의 묶음이 Unclassified 인 이유. 항목 하나가 서로 다른 카테고리 둘을 담고 있어서 그 자리에 붙일 카테고리 이름이 없다. 색인 태깅 어휘에는 이 자리를 가리키는 Unclassified 가 있지만 질의 type enum 에는 대응 값이 없고 검증이 거부한다. N-R3 이 이 자리를 카테고리로 검색하지 않고 부모의 슬롯 경계로만 쓰기로 정한 근거다.

3.7.2 단계별 진행#
단계 무엇을 시도했나 결과 근거
N1 A 의 트리 전체(List 안에 BigNumber 와 LongFormTextLayout 가 항목마다 들어간 모양)를 그대로 담은 후보 후보 0 뿌리가 Unclassified 인 템플릿이 D-7 로 검색 풀에서 빠져 있다 (3.6.5절)
N2 모양을 고정한 채 노드마다 카테고리를 내린다. N-R6 순서로 합 1인 조합 셋(BigNumber→Gauge, LongFormTextLayout→List, List→ListingTable), 그다음 합 2 이상 후보 0 모양이 이종중첩인 채로 남으므로 어느 조합도 같은 이유로 풀에 후보가 없다
N3 A 의 뿌리 Enumeration.List 를 벗겨 항목 4개를 최상위로 올린다. N-R2 로 [4], [3,1], [2,2], [2,1,1], [1,1,1,1] 후보 0 조각 하나하나가 여전히 BigNumber 와 LongFormTextLayout 의 이종 묶음이다
N4 조각마다 Unclassified 경계를 벗겨 단일 카테고리 8개로 만든다. N-R2 로 [8] 부터 줄여 간다 [1,1,1,1,1,1,1,1] 에서 후보가 걸린다 아래 상자
N5 여기까지 전부 실패했을 때만 평탄화한다 반드시 성립 3.0절 마지막 fallback 셋

N4 에서 [8] 부터 [2,...] 까지가 왜 전부 실패하나. 벗긴 뒤 형제 순서는 BigNumber, LongFormTextLayout, BigNumber, LongFormTextLayout 이 반복되는 모양이다. N-R2 는 연속한 형제만 묶고 순서를 바꾸지 않으므로, 크기 2 이상의 묶음은 어떻게 잘라도 이종 묶음이 된다. 그래서 크기 1까지 내려간다.

순서를 바꿔 같은 카테고리끼리 모으면 후보가 더 쉽게 걸린다. BigNumber 4개를 한 컴포넌트로, LongFormTextLayout 4개를 다른 컴포넌트로 모으면 각각 동종이라 N4 가 [4,4] 에서 끝난다. 허용하지 않는다. 읽는 순서가 1분기 매출, 1분기 요약, 2분기 매출 순에서 매출 넷, 요약 넷 순으로 바뀌고, 그러면 어느 요약이 어느 분기의 것인지가 배치로 드러나지 않는다. 이 제약을 풀어야 한다는 실측이 나오면 N-R2 를 고친다.

3.7.3 N4 통과 시점의 결과#

최상위 컴포넌트가 9개가 된다. A 에서 나온 8개와 B 1개다. 아래는 1분기 조각 둘만 적었고 2분기부터 4분기까지 같은 모양이 반복된다.

yaml
version: "0.5.4"
title: 2026 상반기 사업 리뷰
components:
  - type: KPIKeyMetric.BigNumber
    structure:
      entities:
        - { label: 1분기 매출, value: 12, unit: 억원 }
  - type: TextBasedCommunication.LongFormTextLayout
    structure:
      entities:
        - label: 1분기 요약
          items:
            - 신규 시장 세 곳에 진입해 계약 12건을 확보했다.
            - 단가 인하 없이 확보한 계약이라 마진이 유지됐다.
            - 신규 계약 12건
  # 2분기, 3분기, 4분기 조각 여섯이 같은 모양으로 이어진다
  - type: ProcessFlow.Steps
    structure:
      entities:
        - { label: 채널 정비 }
        - { label: 가격 개편 }
        - { label: 해외 확장 }

N-R1 이 벗긴 부모의 내용을 어디로 보냈는지 보라. 뿌리 Enumeration.List 가 갖고 있던 항목 label("1분기")과 items("신규 계약 12건")는 버려지지 않았다. label 은 승격된 조각의 label 앞에 붙어 "1분기 매출", "1분기 요약"이 됐고, items 는 승격된 첫 조각의 첫 entity items 맨 뒤로 갔다.

지킨 것과 잃은 것.

무엇
지켰다 텍스트 전량. BigNumber 의 대형 수치 표현. LongFormTextLayout 의 본문 구조. Steps 의 단계 표현
잃었다 분기 하나와 그 매출, 그 요약이 한 항목 안에 함께 있다는 공간적 짝. 분기 넷을 하나의 나열로 읽게 하던 Enumeration.List 의 배치
3.7.4 N5 까지 갔을 때의 결과#

같은 질의를 평탄화하면 이렇게 된다. 비교를 위해 적어 둔다.

yaml
version: "0.5.4"
title: 2026 상반기 사업 리뷰
components:
  - type: Enumeration.List
    structure:
      entities:
        - label: 1분기
          items:
            - 신규 계약 12건
            - "매출: 12억원"
            - 신규 시장 세 곳에 진입해 계약 12건을 확보했다.
            - 단가 인하 없이 확보한 계약이라 마진이 유지됐다.
        - label: 2분기
          items:
            - 신규 계약 15건
            - "매출: 15억원"
            - 기존 고객 재계약이 늘어 계약 건수가 증가했다.
  - type: ProcessFlow.Steps
    structure:
      entities:
        - { label: 채널 정비 }
        - { label: 가격 개편 }
        - { label: 해외 확장 }

텍스트는 여기서도 하나도 안 줄었다. 대신 12억원 이 큰 숫자로 강조되던 BigNumber 의 표현이 사라지고 글머리표 한 줄이 됐다. 분기와 매출의 짝은 반대로 살아 있다.

그래서 Root-Peeling(N3)·Re-Peeling(N4)이 Content Flattening(N5) 앞에 있다. 둘 다 텍스트를 안 버리는데 Re-Peeling 쪽이 카테고리 표현을 더 남긴다. 표현을 더 남기는 쪽을 먼저 두는 것이 3.6절이 정한 순서다. 다만 위 표의 '잃었다' 칸이 보여주듯 Re-Peeling이 언제나 더 나은 결과라는 뜻은 아니다. 공간적 짝이 내용의 핵심인 질의에서는 Content Flattening이 나을 수 있다 — 대분류가 Hierarchy인 경우가 정확히 그렇다(N-R5, 2026-09-05 B-31로 확정). Root-Peeling은 한 겹만 벗겨서 깊이 3단 이상 계층에 반복 적용하면 소속 관계 자체가 사라지므로, 그 경우엔 Root-Peeling을 건너뛰고 바로 Content Flattening으로 간다.

3.7.5 이 예시가 드러낸 것 셋#

첫째, D-7 이 유지되는 동안 N1 과 N2 는 이종 구조에서 언제나 후보 0 이다. 둘 다 이종중첩인 모양 그대로 후보를 찾기 때문이고, 그런 템플릿이 풀에서 빠져 있다. 즉 이종 질의는 실질적으로 N3 에서 시작한다. 동종중첩(자식 type 이 부모와 같은 경우)은 D-7 밖이므로 N1 과 N2 가 살아 있다.

둘째, 이종 질의에서 N3 한 번으로는 끝나지 않는 경우가 흔하다. 이 예시는 뿌리를 벗겨도 조각마다 이종 묶음이 남아 N4 가 필요했다. 뿌리 아래에 Unclassified 경계가 한 층 더 있는 모양이 그렇다. 2026-08-13 표본에서 관측된 11건은 전부 병치였고(../reference/2026-08-13-이종중첩-수량-산출/README.md) 그중 컴포넌트 8183278 이 이 모양이다(그룹마다 대형 수치와 상세 박스가 함께 들어간 경우).

셋째, Root-Peeling과 Re-Peeling을 반복하면 최상위 컴포넌트 수가 늘어난다. 이 예시는 1개에서 9개가 됐다. 규약 8.6절이 최상위 원소 수에 상한을 두지 않았으므로 규약 위반은 아니다. 상한 여부는 2026-09-05에 닫혔다(B-32) — 안 둔다. 배치 엔진이 분리된 컴포넌트를 페이지마다 나눠 배치하는 정책이라 개수가 늘어도 문제되지 않는다. 다만 Re-Peeling으로 갈라진 조각들이 원래 같은 부모였다는 인접성 정보를 배치 시점에 활용해 2~3개씩 클러스터로 묶는 안이 I-11 설계 메모로 남아 있다(../TODO.md I-11).

3.3 규약에 없는 나머지#

  • structure 의 어느 필드가 어느 슬롯으로 가는가의 정식 매핑이 없다. buildCompSearchData 의 입력이 markdown 이라 Visual Summary(YAML)의 entities, relations, dataset 이 이 경로에 들어갈 자리가 아직 없다. 이것이 3단계 'structure 를 검색 질의로 바꾸는 변환기'가 하는 일이고, 소유자가 정해지지 않았다 (TODO.md D-4).
  • 구간 1의 속성이 comp_signs 로 넘어가는 경로가 없다. text_map 은 텍스트 용량, text_node_roles 는 role 을 담지만, Steps 의 sequence 와 rank, CORE/DESC/DECO/OUT 분류를 담는 컬럼이 없다. semantic_tags 처럼 denormalize 하는 방식이 선례가 된다.
  • 폴백이 발동했다는 사실을 사용자에게 알리는지 (TODO.md C-5).

4. 작업 단위#

TODO.md I절에 등록했다.

# 작업 단위 누구와 왜 필요한가
I-1 semantic category 별 필요 속성 목록 만들기: 1.3절 표를 채운다. Steps 를 템플릿 삼아 P0 카테고리부터 류민·신영빈 Steps 가 먼저 진행 중이라, 지금 열거하지 않으면 카테고리마다 다르게 설계된다
I-2 노드 분류(CORE/DESC/DECO/OUT) · 장식 등급(N/N+α/N+β) · comp-sign 변형 3종을 하나로 정렬 남은 건 노드 분류 하나뿐이다. 장식 등급은 decoration-model.md가 ornament/background 축으로 흡수했고, comp-sign 변형 3종은 B-19(2026-09-05)로 "옮기지 않는다"로 닫혔다 — 둘 다 색인을 곱하지 않아 9벌 우려는 실재하지 않았다 류민·신영빈 노드 분류가 Element role·capability 기본값과 어긋나지 않는지 맞춘다
I-3 폴백의 카테고리 층과 등급 층을 규약에 정의 정원석 → 이석·BE 없으면 특수 컴포넌트가 대부분 2군으로 떨어진다
I-4 구간 1의 속성을 comp_signs 로 넘기는 컬럼·경로 정의 정원석·이석·이강윤 4단계 node 태그의 실질 전달 경로다
I-5 comp-sign 실물을 capability-table.md 에 반영: 태그 군, 채점 항목, 대치 옵션 정원석 규약이 실제 구현보다 뒤에 있다
I-6 rankCompCandidates 위계 가중치 A/B 정원석·이석 BAD 원인 분석이 지목한 "가장 싸게 검증할 수 있는 다음 수"이다. 3.2절 등급 폴백과 계층 결정(schema-v0.4.md 4-1절 원 논의, B-1·B-4)의 실측 근거가 된다
I-7 cross-type property projection (T-8) 규칙 초안 정원석 5단계 대치 PoC(I-9) 선행. structure P1~P8과 구분 (content-binding-contract.md 3.5절)
I-8 PoC 평가 pool 고정 + comp-sign / I.I.S. 사전 적재 정원석 3-way 비교 전제 (content-binding-contract.md 3.6절, blueprint T-2 subset)
I-9 T-8 property 대치 PoC (UI / service.ts 3열) 정원석 I-7·I-8 선행

I-2 가 끝나야 4-1절의 장식과 계층 결정(B-1~B-4)을 정할 수 있다. 색인 쪽이 무엇을 만들 수 있는지 모르는 채로 질의 쪽 필드를 정하면 어긋난다.


5. 참고#

↑ 목차로 · 3부 머리로

12장I.I.S. 산출물 정합성 게이트 정본구조 대응에서 빠진 텍스트 노드를 색인 전에 거른다

원문 design/iis-consistency-gate.md GitHub 에서 보기 19 KB 원문 제목 「I.I.S. 산출물 정합성 게이트: 구조 대응에서 빠진 텍스트 노드를 색인 전에 걸러낸다」

이 문서의 역할 I.I.S. (Semantic Component Property) 산출물(iis_semantic_component)에서 경계 안 텍스트 노드가 structure(구조 대응)에 참조되지 않은 컴포넌트를 찾아 기록하는 게이트의 설계안이다. 규칙의 모양, 저장, 실행 모델, 대상 선정, 호출 위치를 정한다. 이 문서가 SSoT다. 규칙 내용(무엇을 걸러야 하는가)의 SSoT는 아니다. 그것은 허재영이 정의하는 규칙 목록이 갖는다. 오너 문혁준 · 상태 DRAFT (MOR-2447) · 최종 갱신 2026-09-15 관련 MOR-2419 상위 · MOR-2447 이 문서 · MOR-2448 구현 · content-binding-contract.md 대치 contract · ../../../../rat-pod/garbage-collector/gate-key.md GC 게이트(대조용) · 2026-09-14 Slack 합의

이 문서 훑기

절 무엇에 답하나
1 어떤 상태가 오류이고 왜 사고가 되는가
2 GC 게이트에서 무엇을 가져오고 무엇을 버리나
3 규칙 하나는 어떤 모양이고, 어떻게 추가하나. 첫 규칙은 무엇인가
4 어느 테이블에 무엇을 남기나
5 누가 언제 어떻게 돌리나
6 무엇을 판정하고, 언제 다시 판정하나
7 파이프라인 어디서 부르고, 결과는 누가 읽나
8 결정 필요 항목

1. 무엇을 거르나#

1.1 사고 모델#

I.I.S. 5단계는 경계 노드마다 두 가지를 뽑는다(../README.md 2절 표기 규칙).

컬럼 이름 담는 것
iis_semantic_component.structure 구조 대응 어느 노드가 어느 내용 자리(label, items[], value 등)인가. 값은 노드 id다
iis_semantic_component.properties 카테고리 고유 속성 이 컴포넌트가 어떻게 그려져 있나(title, direction 등)

내용 대치는 구조 대응을 읽어 노드에 사용자 내용을 넣는다. 구조 대응에 참조된 텍스트 노드는 사용자 내용으로 바뀌거나, 짝이 없으면 placeholder 문구로 바뀐다. 구조 대응에 참조되지 않은 텍스트 노드는 어느 처리도 받지 않아 템플릿 원문이 그대로 남는다. 사용자 장표에 다른 사람의 문구가 섞여 나가는 것이므로 오류다(MOR-2419 Background).

1.2 오류 정의#

경계 하나(iis_semantic_component 행 하나)를 판정 단위로 한다.

경계는 중첩된다. projected RLSC 의 root 노드 자체가 경계여야 하고(rootBoundaryMissing 검사, iisSemanticComponent.ts), 그 아래에 자식 경계가 있을 수 있다(iis_semantic_component_parent_fkey, deriveBoundaryParents, directBoundaryChildren). 어느 경계의 structure 는 자기 카테고리의 내용 자리만 가리키고 자식 경계 안의 노드는 가리키지 않는다. 그래서 경계의 부분트리 전체를 보면 root 경계는 그 컴포넌트의 모든 텍스트 노드를 미참조로 세게 되어 전량 오탐이 된다.

판정 영역은 경계의 직속 영역이다. 경계 노드의 부분트리에서 자식 경계의 부분트리를 뺀 것이다. 그 영역 안의 type === 'Text' 노드 집합에서 structure 가 참조하는 노드 id 집합을 뺀 잔여가 비어 있지 않으면 그 경계는 걸린다. 예외는 3.4절이다. 말단 경계만 판정하는 대안은 8절 결정 항목이다.

디자인 오브젝트 단위로 제외하려면 그 DO 의 경계 중 하나라도 걸렸는지 집계한다. 판정 자체는 경계 단위로만 한다. 이유는 4절이다.

1.3 이 게이트가 보지 않는 것#

형식 위반과 환각은 추출 시점에 이미 걸러진다(checkTargetBijection, validateStructureNodeReferences, extractProperty.ts. 공용 검증기는 propertyContract.ts). 그 검증은 "물어본 target 에 대해 모델이 제대로 답했나"를 본다. 이 게이트는 그 그물을 빠져나간 것, 즉 애초에 target 이 되지 않은 텍스트 노드와 검증에서 버려져 최종 구조 대응에 남지 않은 노드만 본다. 같은 검사를 두 번 두지 않는다.

properties(카테고리 고유 속성)의 누락은 이번 범위 밖이다. 그 값이 비어도 대치는 성립한다. 값의 정확도는 MOR-2381 이 담당한다.

1.4 전제: 이 게이트의 효과는 구조 대응을 읽는 대치 경로가 있을 때 나타난다#

2026-09-15 기준으로 iis_semantic_component.structure 를 읽어 대치하는 경로는 없다. 실서비스 comp-sign 은 text_map 을 쓰고, PoC 자체 대치(binding.ts)는 RLSC role 로 슬롯을 어림하며, 5단계 property 대치(T-8)는 미착수다(content-binding-contract.md 3.4절). 따라서 이 게이트가 거른 결과는 T-8 경로가 구조 대응을 소비하기 시작할 때부터 사용자 결과물에 반영된다. 그 전까지는 색인 품질 계측과 MOR-2418 공급 수 차감에 쓰인다. 이 전제는 상위 티켓에 적혀 있지 않다. 이 문서의 해석이다.


2. GC 게이트와 무엇이 다른가#

류민이 2026-09-14 에 GC 게이트(MOR-2304)를 그대로 쓰지 않기로 정했다. 오류 유형별 탐지 알고리즘이 단순해 GC 구조를 옮기면 불필요한 복잡도가 된다는 판단이다(Slack). 같은 자리에서 규칙 목록은 허재영이 정의하고 1차 구현은 RAT cell 이 하며, 이후 추가되는 규칙은 허재영이 직접 추가하고 관리하기로 했다.

GC 게이트의 것 이 게이트 이유
판정기 어댑터 (규칙 묶음을 함수 하나로 감쌈) 가져옴 규칙과 실행부의 경계. 3절
대상 선정 "아직 판정되지 않은 것" (재호출 멱등) 가져옴 6절
행 단위 커밋, 기록은 명시적 의사일 때만 가져옴 5절
게이트 키를 코드 상수로 두고 배포된 키는 개명하지 않음 가져옴 3.3절
같은 키의 running run 은 하나 (부분 유니크 인덱스) 가져옴 5.3절
원장 garbage_collector.garbage_collector_judgement_runs / garbage_collector_judgement_results 얇게 가져옴 4절. 근거(빠진 노드 id)를 남길 자리가 필요하다
등록부 gate_config / verdict_strategy + syncGateConfig 버림 판정 전략에 점수와 층이 없다. 키 하나에 규칙 목록 하나라 코드 상수로 충분하다
2층 판정 전략 (always / conditional + scoreCut) 버림 규칙 결과가 걸림/통과 둘이다
대상 선정 3모드 (unjudged / judged / run) 하나로 줄임 unjudged 하나와 rejudge 플래그. 6절
route 가 실행 본체를 await 하는 동기 HTTP 버림 5절. 요청 수명과 작업 수명을 묶지 않는다
fail-run 수습 액션 다른 것으로 대체 5.3절. worker 가 재시도 소진 시 스스로 failed 로 닫는다

3. 규칙#

3.1 규칙 interface#

규칙 하나는 경계 하나를 받아 걸림/통과와 근거를 돌려주는 순수 함수다. pg 와 openai 를 import 하지 않는다. 화면이 같은 판정을 미리 보여 줄 수 있어야 하고, 규칙을 추가하는 사람이 DB 를 몰라도 되어야 한다.

ts
// src/lib/visual-summary/iis/consistencyGate/rule.ts

/** 규칙이 받는 것. 경계 행 하나와 그 경계의 직속 영역을 자르는 데 필요한 정보 */
export interface ConsistencyRuleInput {
  readonly designObjectId: number;
  readonly nodeId: string;
  readonly categories: readonly string[];
  /**
   * 이 경계의 (노드, 카테고리) 행. store.ts loadProperties 의 모양.
   * run 의 property_extractor_version 으로 고정해 읽는다. 판을 고정하지 않으면 v5 flat 행과
   * v6 keyed 행이 섞여 읽힌다(store.ts 주석)
   */
  readonly properties: readonly StoredProperties[];
  /** 경계 노드를 루트로 하는 RLSC 부분트리 */
  readonly subtree: RlscNode;
  /** 이 경계의 직속 자식 경계 노드 id. 직속 영역을 자르는 데 쓴다(directBoundaryChildren) */
  readonly childBoundaryNodeIds: readonly string[];
}

export interface ConsistencyRuleVerdict {
  readonly ruleId: string;
  /** true = 걸림 */
  readonly violated: boolean;
  /** 표본 눈검증과 예외 후보 정리가 읽는 근거. 규칙마다 모양이 다르다 */
  readonly evidence: Readonly<Record<string, unknown>>;
}

export interface ConsistencyRule {
  /** 대문자 ASCII. 배포된 뒤 바꾸지 않는다. 원장과 컬럼에 이 값이 남는다 */
  readonly id: string;
  check(input: ConsistencyRuleInput): ConsistencyRuleVerdict;
}

StoredProperties 와 RlscNode 는 새로 만들지 않는다. store.ts 와 rlsc.ts 의 것을 그대로 쓴다. childBoundaryNodeIds 는 실행부가 directBoundaryChildren(boundaryHierarchy.ts)으로 계산해 넘긴다. 규칙은 이 값을 계산하지 않는다.

3.2 규칙 등록#

ts
// src/lib/visual-summary/iis/consistencyGate/rules/index.ts
export const CONSISTENCY_RULES: readonly ConsistencyRule[] = [
  textNodeNotInStructure,
  // 다음 규칙은 이 배열에 한 줄 추가한다
];

규칙을 추가하는 절차는 셋이다. rules/ 아래 파일 하나를 만들어 ConsistencyRule 을 구현하고, 이 배열에 넣고, 3.3절대로 게이트 키를 올린다. 실행부(judge.ts, worker, route)는 건드리지 않는다. 이 절차를 규칙을 추가하는 사람이 따라 할 수 있는지가 MOR-2448 의 검증 항목이다.

3.3 게이트 키#

ts
// src/lib/visual-summary/iis/consistencyGate/gateVersion.ts
export const GATE_VERSIONS: Readonly<Record<string, { ruleIds: readonly string[] }>> = {
  'IISv1.0': { ruleIds: ['TEXT_NODE_NOT_IN_STRUCTURE'] },
};
export const CURRENT_GATE_KEY = 'IISv1.0';

키 하나가 "어느 규칙 묶음으로 판정했나"의 이름이다. 규칙이 추가되거나 예외가 바뀌면 새 키를 추가하고 CURRENT_GATE_KEY 를 옮긴다. 배포된 키는 개명하거나 지우지 않는다. 컬럼과 원장에 그 이름으로 값이 남기 때문이다. GC 게이트의 gateVersions.ts 와 같은 규칙이고, DB 등록부 동기화는 두지 않는다.

3.4 첫 규칙: TEXT_NODE_NOT_IN_STRUCTURE#

술어 경계의 직속 영역에서 type === 'Text' 인 노드 id 집합 T, structure 가 참조하는 노드 id 집합 S, 예외 집합 E 에 대해 T − S − E ≠ ∅ 이면 걸림.

T 를 모으는 방법 childrenOf(iisSemanticComponent.ts)로 subtree 를 한 번 순회한다. 순회 중 childBoundaryNodeIds 에 있는 노드를 만나면 그 아래로 내려가지 않는다. 지나는 노드 중 type === 'Text' 인 것의 id 를 모은다. collectNodeIds 와 같은 순회 규칙이되 노드 자체가 필요하므로 순회를 한 번만 한다.

S 를 모으는 방법 StoredProperties.structure 는 StructureEntityMap[] 이고 항목 하나는 { id, fields: Record<string, string | string[]> } 다. entities[].id, entities[].fields 의 모든 값, 그리고 StoredProperties.relations[].desc(관계 자체의 내용을 담은 노드 id)를 평탄화해 모은다. 한 노드가 카테고리 여럿이면 카테고리별 행의 S 를 합집합으로 합친다. 필드 이름(label, items, value, children 등)을 규칙 코드에 적지 않는다. 허용 필드는 카테고리마다 다르고 그 정본은 capability.ts 의 reads.content 다. 이름을 열거하면 두 목록이 갈라진다.

evidence { missingNodeIds: string[], textNodeCount: number, referencedNodeCount: number, childBoundaryCount: number }.

예외 E (후보, 미확정) 아래는 실측 전 후보다. 확정은 허재영의 property 기준을 받고 dv2.4 색인에 돌린 발화율로 한다(MOR-2448).

후보 근거 판별 방법(안)
구조 대응을 뽑지 않는 카테고리의 경계 dataset 필수 카테고리는 내용이 entities 가 아니라 dataset 에 있어 usesEntityStructure 가 false 고 allowedStructureFields 가 빈 배열이다(iis/AGENTS.md). hasExtractableContent 는 params 가 있으면 true 라 이 판별에 쓸 수 없다 그 노드의 카테고리 전부가 usesEntityStructure(category) === false 면 판정하지 않고 통과
쪽 번호, 로고 글자, 장식 문구 대치 대상이 아니라 구조 대응에 없는 것이 정상이다 RLSC role 이 Element.Decoration 인 텍스트 노드 제외. 이것으로 충분한지는 실측이 답한다
nodeMissing 센티널 행 buildPropertyWriteRows(runExtraction.ts)가 빈 properties: {} 와 빈 structure: [] 로 적재하는 행이 있다. 누락인지 계약상 정상인지 허재영 확인 필요 확인 후 결정

4. 저장#

Postgres 스키마를 새로 만들지 않는다. I.I.S. 테이블 23개가 전부 public.iis_* 이고, 판정 컬럼이 그중 하나에 붙는다. GC 가 garbage_collector 스키마를 가른 것은 그 안에 테이블이 39개(2026-09-15 마이그레이션 기준. 게이트 4개와 수요 분석, 판정, what-if)라 격리가 필요했기 때문이다. 여기는 테이블 둘과 컬럼 하나다.

무엇 이름 담는 것
run 상태 public.iis_consistency_gate_run id, gate_key, data_version(필수), property_extractor_version(필수), boundary_extractor_version, status(running / completed / failed), rejudge, write_meta, targeted_count, judged_count, violated_count, created_at, completed_at. 부분 유니크 인덱스 (gate_key, data_version, property_extractor_version) where status = 'running'
결과 원장 public.iis_consistency_gate_result run_id, design_object_id, node_id, layout_id, data_version, rule_id, violated, evidence jsonb, created_at. 유니크 (run_id, design_object_id, node_id, rule_id)
현재 판정 public.iis_semantic_component.consistency_gate_meta jsonb { "IISv1.0": { "violated": true, "ruleIds": ["TEXT_NODE_NOT_IN_STRUCTURE"], "judgedAt": "...", "runId": 12 } }

run 하나는 data_version 하나와 property_extractor_version 하나를 판정한다. iis_semantic_component 에는 v5 flat 행과 v6 keyed 행이 공존하는 것이 설계고(propertyShape.ts), loadProperties 는 판 필터를 주지 않으면 판이 섞인 채로 읽힌다(store.ts 주석). 두 축을 run 에 고정해야 결과가 재현된다.

결과 원장을 두는 이유 컬럼만 두면 재판정 때 덮여 이전 판정과 비교할 수 없다. AC 의 "표본 눈검증"과 예외 후보 정리는 evidence.missingNodeIds 를 읽는다. 원장의 유니크 키는 재시도 진행 표시로도 쓴다(5.2절). GC 원장의 score, labels, not_applicable 은 점수 체계가 없으므로 두지 않는다.

컬럼을 iis_semantic_component 에 두는 이유 검색과 대치가 경계 노드 단위로 돈다(iisNodePool.ts 의 NodeBoundaryComponent). 제외도 그 단위가 맞다. 판정은 property_extractor_version 에 종속인데 그 값이 같은 행에 있어, 재추출로 행이 갱신되면 판정이 무효라는 사실이 같은 자리에서 드러난다. design_objects 에 두는 안은 GC 와 자리가 같아 소비자가 한 곳만 보는 장점이 있으나, I.I.S. 판이 바뀌어도 DO 행은 그대로라 출처가 보이지 않는다. 8절 결정 항목으로 남긴다.

data_version 을 run 과 result 양쪽에 두는 이유 I.I.S. 추출 대상 선정이 data_version 으로 좁혀지고(targets.ts), GC 게이트가 dataVersion 을 필수로 바꾼 이유와 같다. 어느 발행분을 판정했는지가 run 자신에 남아야 재현이 된다.


5. 실행 모델#

5.1 요청과 작업을 묶지 않는다#

GC 게이트는 route 가 runGcGate 를 await 한다. 24만 건 판정이 응답 하나에 매달려 20분 대기가 생겼고, route 주석은 자체 호스팅이라 요청이 죽지 않는 점을 근거로 timeBudgetMs 를 6시간까지 열어 두었다(gc-gate/route.ts). 배포로 프로세스가 재시작되면 run 은 running 으로 남고 fail-run 으로 사람이 거둬야 한다. 이 게이트는 그 구조를 쓰지 않는다. GC 게이트도 이 작업 뒤에 같은 모양으로 옮기기로 했다(2026-09-15 문혁준).

5.2 pg-boss worker#

구성 위치 하는 일
접수 POST /api/visual-summary/consistency-gate/runs body 검증(zod), 5.3절 가드, run 행 생성, 큐에 job 발행, 202 { runId } 즉시 응답
조회 GET /api/visual-summary/consistency-gate/runs/{id} run 행 반환(진행 수, 상태)
실행 src/workers/iis-consistency-gate-worker.ts 큐 iis_consistency_gate 를 소비. chunk 단위로 6절 대상을 읽어 규칙 전부를 돌리고 결과를 쓴다
판정 consistencyGate/judge.ts 경계 하나에 CONSISTENCY_RULES 전부 적용. 순수 함수
기록 consistencyGate/record.ts result 행 insert, 컬럼 갱신, run 진행 수 갱신. chunk 하나가 트랜잭션 하나

등록 자리 셋 이 저장소에서 큐 하나가 돌려면 세 곳에 함께 등록해야 한다. 큐 이름과 *_RETRY_LIMIT, *_EXPIRE_* 상수는 src/workers/constants.ts 에, 큐 생성은 src/lib/pgboss.ts 의 createQueue 등록부에, 실행은 package.json 의 worker:<name>:dev|build|start 스크립트 셋과 worker:dev:all, worker:build:all 목록, 그리고 ecosystem.config.cjs 의 pm2 항목에 더한다. 하나라도 빠지면 worker 는 기동되는데 job 이 잡히지 않는다.

이 저장소의 배치 worker 는 valid-component-meta-worker 를 포함해 21개가 pg-boss 로 돈다(package.json 의 worker:dev:all, ecosystem.config.cjs, 2026-09-15 기준). 프로세스 안에서 응답 뒤에 이어 도는 방식은 배포 한 번에 판정이 사라지고 재시도가 없어 택하지 않는다.

가볍게 유지하는 조건 I.I.S. 추출 worker 의 chunk 발급, reconciler, retry-failed, authz(extractionJob/ 20개 파일)를 복제하지 않는다. run 하나가 job 하나다. worker 가 chunk 를 순서대로 돌며 진행 수를 갱신한다. chunk 는 (design_object_id, node_id) 정렬로 500행이다. 시간 예산은 두지 않는다. worker 에는 요청 수명이 없다.

재시도 진행 표시는 원장이다. 죽으면 pg-boss 재시도가 같은 job 을 다시 집는다. 이때 그 run 의 iis_consistency_gate_result 에 이미 있는 (design_object_id, node_id) 를 대상에서 뺀다(6.1절 SQL 의 not exists). 컬럼(consistency_gate_meta)을 진행 표시로 쓰지 않는다. 드라이런(writeMeta: false)은 컬럼을 쓰지 않으므로, 컬럼을 진행 표시로 두면 GC 게이트가 겪은 "드라이런은 재호출이 같은 대상을 재선정한다"(runGcGate.ts 주석)가 그대로 재현된다.

기록은 명시적 의사다 writeMeta(기본 false)가 true 일 때만 컬럼을 갱신한다. 드라이런은 원장만 남긴다. GC 게이트와 같다.

5.3 run 가드와 실패 수습#

  • 동시 run 하나 같은 (gate_key, data_version, property_extractor_version) 에 running run 은 하나다. 4절의 부분 유니크 인덱스가 강제하고, POST .../runs 는 충돌 시 409 로 거절한다. 7.1절의 자동 발행과 수동 실행이 겹치는 것을 여기서 막는다
  • 실패 수습 pg-boss retryLimit 을 소진한 job 은 dead letter 큐 iis_consistency_gate_dead_letter 로 간다. iis_boundary_eval, iis_property_eval 두 큐의 선례와 같다(src/lib/pgboss.ts). dead letter 를 받는 handler 가 run 을 failed 로 닫는다. GC 의 fail-run 처럼 사람이 거두는 액션은 두지 않는다

6. 대상 선정과 재판정#

6.1 대상#

sql
select c.design_object_id, c.node_id, d.layout_id
from public.iis_semantic_component c
join public.design_objects d on d.id = c.design_object_id
join public.layouts l on l.id = d.layout_id
where l.data_version = $1                                 -- run 의 발행분
  and c.property_extractor_version = $2                   -- run 의 I.I.S. 판
  and (
    $3::boolean                                            -- rejudge 면 키 조건을 무시
    or not coalesce(c.consistency_gate_meta ? $4, false)   -- 이 키로 아직 판정 안 된 행
  )
  and not exists (                                         -- 이 run 이 이미 판정한 행 (재시도 진행 표시)
    select 1 from public.iis_consistency_gate_result r
    where r.run_id = $5 and r.design_object_id = c.design_object_id and r.node_id = c.node_id
  )
order by c.design_object_id, c.node_id

design_objects.layout_id 는 nullable 이고(20251218011102 마이그레이션), layout_id 가 null 인 DO 는 발행분을 모르므로 이 inner join 에서 빠진다. targets.ts 가 and d.layout_id is not null 로 명시하는 것과 같은 처리다.

2026-09-14 prod 실측(필터 없이 iis_semantic_component 전량을 property_extractor_version 으로 group by)으로 property-v6 경계 행 103,243(DO 96,412)이 있고 property_extractor_version 이 NULL 인 행 8,986 은 속성 단계가 돌지 않은 것이라 대상 밖이다. structure 가 NULL 이거나 빈 행은 0 이었다. 즉 행 단위로는 누락이 보이지 않고 노드 단위 대조가 필요하다.

6.2 재판정 축은 둘이다#

GC 게이트는 data_version 하나를 축으로 삼았다. 이 게이트는 I.I.S. 판이 축 하나 더다. 같은 발행분이라도 property-v6 에서 v7 로 재추출되면 구조 대응이 바뀌므로 판정을 다시 해야 한다.

다시 판정할 때 어떻게
규칙이나 예외가 바뀜 새 게이트 키 배포. 새 키로는 전량이 "아직 판정 안 된 행"이라 6.1 조회가 그대로 잡는다
I.I.S. 판이 바뀜(재추출) structure 를 바꾸는 경로 둘에서 consistency_gate_meta 를 비운다. replaceProperties 가 속성 칸을 null 로 비우고 다시 쓸 때, 그리고 replaceBoundaries 가 카테고리 변경으로 속성 키를 덜어 내거나 비울 때다(store.ts). replaceProperties 는 행을 지우지 않고 UPDATE 하므로 컬럼이 저절로 사라지지 않는다. 이 연결은 MOR-2448 구현 항목이다
특정 대상을 다시 돌림 POST .../runs 에 rejudge: true. 6.1 의 키 조건을 무시하고 전량 선정. 컬럼은 같은 키를 덮어써 현재 판정만 남고, 이전 판정은 원장의 이전 run 에만 남는다. 이것이 의도다

7. 호출 위치와 소비자#

7.1 호출#

상위 티켓은 "파이프라인의 가장 마지막, 백엔드 전달 직전"으로 적었다. 그 자리는 2026-09-15 기준으로 존재하지 않는다. 추출 파이프라인 11 step 은 assemble-upload-inputs 가 아티팩트 6종을 모아 upload-to-s3 로 BE 에 넘기는데(../../../../pipelines/extraction-pipeline-flowchart.md), 그 6종에 iis_semantic_component 는 없다. I.I.S. 색인이 BE 로 넘어가는 경로는 이 저장소에 없다. design_object_semantic_tags 가 "파이프라인 밖에서 채워지고 assemble 이 읽어 가는" 방식인데, I.I.S. 색인은 그 점선조차 아직 없다.

그래서 이 게이트는 "직전"에 매달리지 않는다. I.I.S. 추출 작업이 완료될 때 run 을 발행한다. 훅 지점은 extractionJob/storeItems.ts 의 finalizeJob 이 running → completed 전이를 확정한 자리다. 그 함수는 실제로 전이가 일어난 트랜잭션에서만 이벤트를 남기므로(?reconcile=1 조회 경로로 다시 돌아도 중복 발행이 없다) 같은 조건으로 run 을 발행한다. 발행 조건은 둘이다. job 의 kind 가 property 또는 both 여야 하고(boundary 만 돌린 job 은 판정할 새 속성 행이 없다), target_metadata.dataVersion 이 있어야 한다. ids 모드 job 은 dataVersion 을 비우므로(targets.ts 의 toTargetMetadata) 자동 발행 대상이 아니다. run 의 property_extractor_version 은 그 job 이 쓴 판이다. 수동 실행은 POST .../runs 로 언제든 가능하다.

7.2 소비자#

판정 결과를 읽어 제외를 실행하는 주체는 이 게이트의 범위 밖이다. BE 전달 경로가 생길 때 그 경로가 consistency_gate_meta 의 현행 키를 보고 걸린 경계를 빼면 된다. GC 게이트가 "서빙 소비처 반영은 별도 티켓"으로 뺀 것과 같다. I.I.S. 색인이 BE 로 어떤 경로로 넘어가는지(assemble 아티팩트 추가인지 별도 경로인지)는 류민과 이석에게 확인이 필요하다.

7.3 GC 게이트와의 선후#

둘은 독립이다. GC 는 design_objects 단위로 품질을 보고 garbage_collector_meta 에 남긴다. 이 게이트는 경계 단위로 I.I.S. 산출물의 정합성을 보고 consistency_gate_meta 에 남긴다. 판정 대상도 다르다. I.I.S. 추출 대상은 data_version, 최신 valid_component_meta 판정, 자산 3종(design_object_sheet_json, structure_json, thumbnail_url)으로 좁혀지고 GC 결과를 보지 않는다(targets.ts). 소비자가 두 컬럼의 교집합을 읽으면 되므로 실행 순서를 정할 필요가 없다. GC 가 파이프라인에 연결되어 I.I.S. 추출 모집단을 GC 통과분으로 좁힐지는 MOR-2304 쪽 결정이다.


8. 아직 정하지 않은 것#

# 결정 갈림길 누가, 무엇으로
1 예외 집합 E 3.4절 후보 셋의 채택 여부와 판별 방법 허재영 property 기준 수령 후, dv2.4 발화율 실측(MOR-2448)
2 판정 영역 직속 영역(이 문서의 안) 대 말단 경계만 판정. 후자는 상위 경계의 제목 같은 직속 텍스트를 못 본다 문혁준. 실측에서 상위 경계 발화 분포를 보고 확정
3 판정 컬럼 위치 iis_semantic_component(이 문서의 안) 대 design_objects 문혁준. 소비자 경로가 정해지면 확정
4 규칙 2종과 3종 허재영이 리스트업하는 목록 허재영. 수령 후 MOR-2419 하위 티켓 분리
5 I.I.S. 색인의 BE 전달 경로 assemble 아티팩트 추가인지 별도 경로인지 류민, 이석
6 nodeMissing 센티널 행의 처리 누락으로 세는지 정상으로 보는지 허재영

9. References#

개정 이력#

  • 2026-09-15 문혁준: 최초 작성. 오류 정의(경계 단위, 직속 영역의 구조 대응 미참조 텍스트 노드), GC 게이트 대조표, 규칙 interface 와 등록 절차, 저장(테이블 2 + 컬럼 1, public.iis_*, run 은 dv 하나 + I.I.S. 판 하나), pg-boss worker 실행 모델(원장을 재시도 진행 표시로, 동시 run 가드, dead letter 수습), 재판정 축 둘, 호출 위치(finalizeJob 전이 시 발행)와 소비자 미정, 결정 필요 6건. 같은 날 critic 검토로 중첩 경계 오탐, 드라이런 진행 표시, 예외 판별 함수, 판 축 누락을 교정. MOR-2447

↑ 목차로 · 3부 머리로

13장꾸밈구조 정본등급 하나에서 축 여럿으로

원문 design/decoration-model.md GitHub 에서 보기 28 KB 원문 제목 「꾸밈구조: 등급 하나에서 축 여럿으로」

이 문서의 역할 꾸밈을 어떻게 선언하고 매칭할지의 설계 의도를 정한다. 왜 등급 하나로는 안 되는가, 7/24 회의의 N / N+α / N+β 에서 무엇을 차용하고 무엇을 버리는가, 초기 결정이 무엇인가를 담는다. 오너 정원석 (MORDOR) · 상태 DRAFT (초기 결정) · 최종 갱신 2026-09-05 (B-21 전량 닫힘 — Separator는 ornament에 흡수, PageHeader/PageFooter는 스코프 밖, 카테고리 층은 보류) 관련 ../spec/schema-v0.5.md 규약 · ../spec/capability-table.md 색인 쪽 선언 · content-binding-contract.md 내용 대치 contract · ../TODO.md 할 일

이 문서가 꾸밈구조 설계 의도의 SSoT이다. 축과 값의 확정 표기는 규약(../spec/schema-v0.5.md)이 갖고, 할 일은 ../TODO.md 가 갖는다.

이 문서 훑기

절 무엇에 답하나
0 무엇을 무엇이라 부르나
1 어떤 결정이 뒤집혔나
2 등급 하나로 두면 왜 확장성이 없나
3 N / N+α / N+β 와 옆 축 둘에서 무엇을 가져오나
4 지금 정한 것. 머리에 '어디까지 지금 하나'가 있다
5 형 정의, JSON Schema, 해석 순서, 예시 4종, 안티패턴
6 무엇이 남았나

0. 용어 (확정)#

확정 (B-23, 2026-09-05 정원석). 이미 문서 전체에서 이 표기로 일관되게 쓰이고 있어 제안 상태를 유지할 이유가 없었다. 용어를 고정하는 이유는 WRITING.md 5.4 다.

이름 넷이 나온다. 한 예시에 다 들어 있다.

yaml
decoration:            # 꾸밈구조 하나 (component.decoration)
  ornament: prefer     # 꾸밈 축 하나 + 선호값 하나
  background: avoid    # 꾸밈 축 하나 + 선호값 하나

꾸밈구조가 통이고 꾸밈 축이 그 안의 항목이다. 축은 늘어날 수 있고(4.1절), 축마다 선호값 하나를 갖는다.

이름 영어 무엇인가 어디서 왔나
내용구조 structure 사용자 입력의 내용 쪽 규약 2절이 이미 이 키다. "structure 는 내용, params 는 표현 선택"
꾸밈구조 decoration 사용자 입력의 꾸밈 쪽. 축들을 담는다 이 문서에서 새로 만든다. 이름 근거는 아래
꾸밈 축 decoration facet 꾸밈구조 안의 항목 하나. RLSC role 하나에 대응한다 4.1절
선호값 preference 축 하나에 담기는 값. 그 꾸밈을 얼마나 원하는지 prefer, indifferent, avoid 셋 (4.2절)

내용 쪽과 꾸밈 쪽을 나란히 둔다. 규약이 이미 내용 쪽을 structure 로 갖고 있으므로, 꾸밈 쪽을 그 형제로 두면 둘이 같은 층에서 읽힌다.

이름을 이렇게 고른 근거#

WRITING.md 3.5절 순서를 따랐다.

순서 판정 이 건에 적용
1 코드, DB, API 에 그 이름으로 존재하면 원어 RLSC 에 Role.Element.Decoration 과 Role.LayoutContainer.Decoration 이 있다. 그러므로 원어 계열을 쓴다
3 팀이 실제로 부르는 이름 신영빈님이 **'꾸밈'**으로 쓴다. 2026-07-31 "꾸밈/설명을 단계적으로 늘리고 줄이는 구상안", "오염자(꾸밈 요소) 추적·제거" (content-binding-contract.md 1.1절, 2.4절). 그래서 한국어는 꾸밈이다

Decoration 이라는 말이 두 곳에 나온다. 문맥으로 구분한다 (2026-08-03 정원석).

어디 무엇인가 어떻게 적나
RLSC 노드의 role 값 RLSC role Decoration, Element.Decoration, LayoutContainer.Decoration
Visual Summary 컴포넌트에 붙는 꾸밈구조 component.decoration, page.decoration

둘이 이름을 다투지 않는다. 꾸밈 축 이름이 ornament 와 background 여서, decoration 안에 decoration 이 들어가는 일이 없다. 다른 스키마의 다른 자리이므로 앞에 무엇이 붙는지로 구분된다.

한국어로는 '꾸밈구조'다. '장식'은 Element.Decoration 의 한국어 이름(장식 요소)이라 축 하나를 가리킨다. 그래서 축들을 묶을 때는 '꾸밈'을 쓴다.

이전 문서에서 쓰던 말과의 관계#

이전 말 어디 있나 이 문서에서는
traits.decoration, traits.hierarchy ../archive/schema-v0.4.md 4-1절 제안. 규약 0절 6번이 "도입하지 않는다"로 적었다가 2026-08-03 에 뒤집혔다 traits 라는 묶음을 두지 않는다. 꾸밈은 decoration, 계층은 entity.components[] 로 따로 간다 (1절)
장식 등급, 장식 수준, N / N+α / N+β 7/24 회의와 v0.4 4-1절 등급으로 두지 않는다 (2절). N 과 N+β 는 문서 어휘로만 남고 N+α 는 사라졌다 (4.3절)
장식 Element.Decoration 의 한국어 이름 축 하나(ornament)를 가리킬 때만 쓴다. 축들을 묶을 때는 '꾸밈'이다
style, 스타일 features/style-replacement 쓰지 않는다. 스타일 대치는 색상과 폰트 대치를 가리키는 기존 기능이다

대치 셋을 구분한다#

세 가지가 다른 것이고, 이 문서는 그중 요소 대치와만 관계한다 (4.6절, 4.7절).

한국어 영어 무엇을 바꾸나
내용 대치 content replacement 컴포넌트 슬롯에 텍스트와 데이터를 넣는다 (features/content-replacement)
요소 대치 element replacement 요소 자체를 교체한다. 2026-07-23 회의의 주제다
스타일 대치 style replacement 색상과 폰트를 템플릿 톤앤매너에 맞춘다 (features/style-replacement)

1. 2026-08-03 에 무엇이 바뀌었나#

B-2 와 B-3 이 뒤집혔다. 2026-08-02 결정은 장식과 계층 모두 "질의 쪽 선언 필드를 두지 않는다"였다 (../TODO.md B-2, B-3). 2026-08-03 정원석 판단으로 양쪽 다 적는 방향이 됐다.

항목 2026-08-02 2026-08-03
계층 structure 에 선언하지 않는다. type 의 대분류가 Hierarchy 인지로 판단한다 컴포넌트를 nested 구조로 두어 해결한다
장식 질의 쪽 필드를 두지 않는다. 색인 쪽에서 변형을 여러 벌 만들어 대응한다 꾸밈구조를 둔다. 다만 1단계는 시스템 전역 기준만 잡고 Summary 는 생성하지 않는다 (4절 머리)

뒤집은 이유가 둘이다. 첫째, 8/2 결정이 판정 책임을 색인 쪽으로 넘겼는데 색인 쪽에 그것을 받을 선언이 없었다. ../spec/capability-table.md 에 장식 언급이 0건이다. 둘째, 그 결정의 근거표가 보존 전용 문서에만 있었다 (../archive/projection-model.md 6절 규칙 1~4). 허브가 "v0.5 는 이 철학을 따르지 않는다"고 적어 둔 문서다.

이 문서가 그 자리를 만든다. 다만 아래는 초기 결정이고, 규약 표기 확정은 남아 있다 (6절).


2. 왜 등급 하나로는 안 되나#

7/24 회의의 N / N+α / N+β 는 누적 등급이다. N ⊂ N+α ⊂ N+β 로 뒤 등급이 앞을 포함한다 (../archive/schema-v0.4.md 4-1절). 이 모양을 그대로 규약에 넣으면 확장성이 없다. 이유가 넷이다.

2.1 한 등급 안에 서로 다른 것이 섞여 있다#

회의 원문이 N+β 를 "요소 배치와 장식까지 원하는 사용자용"이라고 적는다. 요소 배치와 그림 장식은 다른 것이다. 배치가 정교한데 그림이 없는 컴포넌트가 있고, 그림은 많은데 배치가 단순한 컴포넌트가 있다. 한 칸에 넣으면 둘을 구분해 요구할 수 없다.

N 과 N+α 의 경계는 더 나쁘다. 내용과 표현 선택을 한 축에 섞는다. 타이틀과 디스크립션이 있는지는 내용이고, 장식이 얼마나 붙는지는 표현 선택이다. 이 저장소는 둘을 섞지 않는 원칙을 이미 갖고 있다. fallback 할 때 내용은 옮기고 표현 선택만 버리는 규칙이 그것이다 (../spec/capability-table.md 2절, 3.4절). 이 문제의 결론은 3.2절이다.

2.2 누적이라서 표현할 수 없는 조합이 있다#

누적 등급은 '앞 등급을 다 채우고 다음으로 간다'만 표현한다. 그래서 '꾸밈은 많지만 디스크립션은 없는 것'을 적을 수 없다. 등급으로는 그 조합에 이름이 없다. 축이 여럿이면 조합은 등급 셋으로 덮이지 않는다.

2.3 축이 늘 때 끼워 넣을 자리가 없다#

등급에는 순서만 있고 이름이 없다. 배경 여부, 테두리, 그림자, 아이콘 같은 것이 새로 필요해지면 'N+β 안쪽'이라고 말할 수밖에 없다. 어느 등급에 넣을지가 매번 판단이 되고, 판단이 쌓이면 등급의 뜻이 문서마다 달라진다.

축으로 두면 이 문제가 없다. 새 축을 추가하고 기본값을 indifferent 로 두면 기존 동작이 바뀌지 않는다. 규약 1절 "선언되지 않은 것은 무시한다"와 같은 방향이다.

2.4 채점 실물이 이미 축별로 되어 있다#

comp-sign DEFAULT_SCORING_WEIGHTS 가 illustrationBonus, titleFontSizePenalty, styleMismatch, visualSiblingSplit 처럼 항목별로 나뉘어 있다 (content-binding-contract.md 2.3절). 등급으로 뭉치면 선호와 채점 항목이 1:1로 대응하지 않아, 'rich 를 요구했는데 왜 이 점수인가'를 설명할 수 없다.

2.5 N 은 애초에 꾸밈 축이 아니다#

v0.4 4-1절이 이미 짚어 뒀다. N 의 '최소 필요 조건'은 capability 테이블의 성립 조건과 같은 대상을 가리킨다. 그래서 N 을 장식 등급의 최하단으로 두면 성립 조건이 꾸밈 축에 한 번 더 적히게 된다. 같은 사실이 두 곳에 있으면 어긋난다 (WRITING.md 5.1).


3. 현행 개념 셋과 차용가치#

장식을 다루는 개념이 지금 셋이다. 셋이 서로 다른 기준으로 같은 문제를 풀고 있고, 이것이 ../TODO.md I-2 다.

개념 무엇으로 쪼개나 어디 있나
N / N+α / N+β 장식 수준 (누적 등급 3단) 7/24 회의. 정의는 ../archive/schema-v0.4.md 4-1절
CORE / DESC / DECO / OUT RLSC 노드의 역할 content-binding-contract.md 1.1절. 신영빈, 류민
comp-sign 변형 3종 텍스트 역할 제거 (원본, Text:Subtitle 제거, Text:Hl 제거) comp-sign v4.3.8 실물

3.1 셋의 관계#

노드 분류가 가장 아래에 있다. 등급의 α 는 DESC 를 살리는 것이고 β 는 DECO 를 살리는 것에 가깝다. 즉 등급은 노드 분류의 파생물이지 독립 개념이 아니다. 노드 분류를 축의 근거로 삼으면 등급을 따로 정의할 필요가 없다.

comp-sign 변형은 축의 일부를 이미 구현해 두었다. Text:Subtitle 제거가 설명 쪽이고 Text:Hl 제거가 강조 쪽이다. 다만 목적이 다르다. comp-sign 변형은 검색 리콜을 위한 것이고(텍스트가 적은 입력도 매칭), 등급은 사용자가 원하는 표현 밀도를 위한 것이다.

3.2 DESC 는 꾸밈 축이 아니다#

2026-08-03 정원석 지적으로 정리됐다. DESC 는 nested component 로 해결되는 부분이고, 전용 속성이 필요한 것은 DECO 뿐이다.

판정 기준이 하나로 나온다. 대치가 내용으로 채우는 노드는 꾸밈구조 선언이 필요 없다. 내용이 이미 답이다. 대치가 건드리지 않는 노드만 선호가 필요하다.

RLSC v2.0 role 정의가 이 경계를 그대로 그어 두었다 (../../../../rlsc-onboard/role-v2.md Role.LayoutContainer 과 Role.Element 절).

RLSC role 대치가 채우나 그래서
Element.Title, Element.Subtitle, Element.Description 채운다 입력에 제목과 설명이 있는지가 답을 정한다. entity.label 과 entity.components[](nested component)가 담는다. items 는 중첩을 못 읽을 때의 평탄화 결과이므로 판정 근거가 아니다 (아래 정정). 꾸밈 축 불필요
Element.Highlight 채운다 무엇을 강조할지는 내용이다. entity.emphasis(boolean)가 이미 있다. 꾸밈 축 불필요
Element.Decoration 안 채운다. role 정의가 "내용 대치 시 변경 불필요" 입력에 대응물이 없다. 선호로만 정할 수 있다. 꾸밈 축 필요
Element.Background 안 채운다. role 정의가 "Parent position 거의 가득 채움" 위와 같다. 꾸밈 축 필요

이 판정이 v0.4 의 α 를 없앤다. N+α 는 "타이틀과 디스크립션까지 살린 버전"인데, 그것은 표현 밀도가 아니라 내용 유무다. ../archive/projection-model.md 6절 규칙 3 이 이미 "구조에서 파생한다"고 적어 두었다. 그 결론은 유효하고, 이 문서가 되살린다.

다만 규칙 3 이 든 수단은 정정한다 (2026-08-03 정원석 지적). 원문은 파생 근거를 "entities 의 label 과 items 보유 여부"로 적었다. label 은 맞다. Element.Title 에 대응하고 entity.label 은 필수 필드다. items 가 틀렸다. items 는 평탄화 결과물이다.

규약이 그렇게 정의한다. entity.items 는 string[] 이고, entity.components[] 를 못 읽는 컴포넌트에서 자식 entities 가 부모 items 로 펴진다 (규약 1절 "중첩은 평탄화된다", 8.7절). 그래서 items 보유 여부로 디스크립션을 판정하면 설명 문단과 하위 항목 목록과 평탄화된 중첩 컴포넌트를 구별할 수 없다. 손실된 형태를 근거로 삼는 것이다.

정확한 수단은 nested component 다. 타이틀과 본문 설명이 짝을 이루는 것은 그 자체로 의미 블록이므로 entity.components[] 가 담는다. items 는 그것을 못 읽는 컴포넌트에서의 강등 결과다.

계층과 DESC 가 같은 수단을 쓴다. 2026-08-03 에 계층도 "컴포넌트를 nested 구조로 두어 해결"로 정했다(1절). 둘이 entity.components[] 하나를 쓰므로 새로 만들 것이 없다. 대신 생성기가 entity.components[] 를 채워야 계층과 DESC 가 둘 다 동작한다 (../TODO.md B-20). 그때까지는 둘 다 items 로 평탄화된 채 읽힌다.

뒤집히는 것은 규칙 4 하나다. 규칙 4 는 "그림과 도형 장식은 질의 쪽에 적지 않는다. 입력에 대응물이 없다"였다. 전제는 맞지만 결론이 틀렸다. 입력에 대응물이 없다는 것이 바로 선언이 필요한 이유다. 내용에서 유도할 수 없으므로 취향이나 정책으로 정해야 하고, 그래서 시스템 전역 기본값이 필요하다(4.4절).

그래서 축을 새로 만드는 것이 아니라, DECO 쪽에만 이름을 붙이는 일이다.

3.3 차용가치 판정#

개념 판정 근거
비대칭 원칙 차용한다 "장식이 있는 건 삭제할 수 있지만 없는 건 추가가 어렵다"(7/24). 이것이 색인을 몇 벌 만들지를 정한다. 4.5절
이름 minimal 과 rich 문서 어휘로만 차용한다 회의록과 기존 문서가 이 이름을 쓴다. 다만 스키마 값으로 두지 않는다. 4.3절
노드 분류 CORE / DESC / DECO / OUT 축의 근거로 차용한다. 다만 DECO 쪽만 축이 role 에 대응해야 요소 대치가 어느 노드를 지울지 정할 수 있다. DESC 는 내용이라 축이 아니다 (3.2절)
누적 등급 3단 차용하지 않는다 2절
N 을 장식 최하단으로 두는 것 차용하지 않는다 성립 조건과 같은 대상이다. 2.5절
N+α (타이틀과 디스크립션 등급) 차용하지 않는다 표현 밀도가 아니라 내용 유무다. structure 와 nested component 가 담는다. 3.2절
등급 3분(필수 / 선호 / 무시) 차용하지 않는다 2026-08-02 에 B-1 로 이미 기각됐다. 성립 조건을 최소로 줄이고 나머지를 선택 속성으로 옮기는 방식을 택했다 (../spec/capability-table.md 1절)

4. 초기 결정#

2026-08-03 정원석. 아래는 방향을 고정하는 초기 결정이고, 표기와 값의 확정은 6절에 남겼다.

먼저 정할 것은 '어디까지 지금 하나'다 (2026-08-03 정원석). decoration 와 그 하위 축을 어디까지 고려할지를 판단하지 않으면, 아래 설계 전체가 지금 필요한 것보다 커진다.

1단계는 시스템 전역 기준만 잡는다. Visual Summary 는 생성하지 않는다.

층 1단계 (지금) 요소 대치 착수 후
시스템 전역 기본 여기만 잡는다. 예를 들어 '꾸밈이 많은 것을 우선한다' 유지
카테고리 기본 미정 41종에 채운다
Visual Summary (질의) 생성하지 않는다. 스키마에 optional 로 자리만 연다 필요하면 생성기가 적는다

이렇게 나누는 근거가 다섯이다.

  1. 지금 소비할 주체가 없다. 요소 대치가 아직 동작하지 않고(4.7절), 내용 대치는 꾸밈에 관여하지 않는다(4.6절). 적어 두어도 읽는 곳이 없다.
  2. 색인이 대조할 대상을 아직 못 받는다. role 표식을 색인으로 넘기는 경로가 없다 (../TODO.md B-22). 질의 쪽만 채워도 대조가 성립하지 않는다.
  3. 지금 필요한 것은 전역 기준 하나로 된다. '꾸밈 많은 것을 우선'은 입력별 판단이 아니라 정책이다. 검색 순위에만 쓰이므로(4.6절) 전역 기본값이면 충분하다.
  4. 생성기에 필드를 늘리는 비용이 있다. 토큰이 늘고 생성 실패 자리가 하나 늘어난다. 이 저장소가 entity.id 를 생성물에서 뺀 이유와 같은 논리다. 그때 근거가 토큰 5.9% 증가와 재현성 지표 오염이었다 (../TODO.md G.1절).
  5. 선례가 있다. entity.components[] 와 page 수준 relations 가 모두 '스키마에 열어 두고 생성기는 채우지 않는다'로 되어 있다 (규약 1절, 8.7절). decoration 도 같은 방식으로 연다.

그래서 아래 4.1절부터 4.5절은 '자리와 뜻을 정해 두는 것'이고, 실제로 채우는 것은 전역 기본값 하나다. 요소 대치를 만들 때 참고할 값이 미리 정의돼 있어야 하므로 설계는 지금 해 두고, 생성은 미룬다.

4.1 꾸밈 축은 DECO 계열에만 둔다#

DESC 와 강조는 내용이므로 structure 와 entity.components[] 와 entity.emphasis 가 담는다 (3.2절).

축은 서로 독립이고, 각 축은 RLSC role 하나에 대응한다. 초기 축은 둘이다.

축 대응 RLSC role role 정의의 근거
ornament Element.Decoration, LayoutContainer.Decoration, Element.Separator, LayoutContainer.Separator "시각적 꾸밈 목적, 내용 대치 시 변경 불필요" — Separator 도 순서·내용을 안 담아 같은 근거를 공유한다 (2026-09-05 정원석 확정, B-21)
background Element.Background, LayoutContainer.Background "Parent position 거의 가득 채움, 크기 늘림 선호"

축 하나가 role '군'에 대응할 수 있다 (2026-09-05 갱신). 원래는 축 하나 = role 하나였지만, Separator를 별도 축으로 세 개(ornament/background/separator) 두는 대신 ornament의 세부 유형으로 흡수했다 — 둘 다 "순서도 내용도 담지 않는 시각적 요소"라는 같은 판정 기준(3.2절)에 걸리기 때문이다. 축이 늘어나는 게 아니라 기존 ornament가 커버하는 role 집합이 넓어지는 것이다. 첫째, 추출이 그 role 을 이미 붙이므로 표식을 새로 만들지 않는다. 둘째, 요소 대치가 지울 노드를 role 로 특정할 수 있다. 셋째, 새 꾸밈 role 이 생기면 축을 하나 더 열면 된다.

축 이름을 illustration 이 아니라 ornament 로 뒀다. illustration 은 comp-sign illustrationBonus 에서 온 말인데, 그쪽은 일러스트 이미지만 가리켜 Element.Decoration role 보다 좁다. ornament 는 role 이 담는 범위(도형과 이미지 등 꾸밈 요소 전반)에 맞는다.

축에서 뺀 것 셋을 적어 둔다. 아래 근거로 축이 아니라고 판정했다.

뺀 축 왜 뺐나
description 내용이다. 3.2절
highlight 내용이고 entity.emphasis 가 이미 담는다. 3.2절
arrangement 노드가 아니다. 배치는 노드에 붙는 성질이 아니라 노드를 놓는 방식이라 role 대응이 없다. 그리고 배치를 보는 채점 항목이 이미 있다 (areaRatio, offCenterPenalty, visualSiblingSplit)

Element.Separator 와 Element.Marker 는 처음에 판정을 미뤘다. 둘 다 꾸밈으로 보이지만 role 제약에 "필수"가 적혀 있어서, 카테고리에 따라 성립 조건일 수 있다고 봤다.

2026-08-04 에 role 표를 다시 읽고 정정한다. 그 '필수'는 성립 조건이 아니라 RLSC 안의 정합 규칙이다. LayoutContainer.Marker 의 "Marker 요소 (필수)" 는 "Marker 컨테이너가 있으면 그 안에 Marker 요소가 있어야 한다"이고, Element.Separator 의 "LayoutContainer.Separator 내 1개 이상 필수" 도 같은 모양이다 (../../../../rlsc-onboard/role-v2.md Role.LayoutContainer·Role.Element 절). 컴포넌트가 분리선이나 마커를 반드시 가져야 한다는 뜻이 아니다. 그래서 미룬 근거는 틀렸고, 둘을 다시 판정한다.

role 판정 근거
Element.Marker 축이 아니다 근거가 '필수' 표기가 아니라 순서다. 마커는 내용에 걸려 있다. capability 2.2절이 Steps 의 marker 를 선택 속성으로 갖고, order-tagger 가 마커 순서로 sequence 를 확정해 마커 보유 540건 중 534건(99%)이 기하 순서와 일치했다 (content-binding-contract.md 1.2절). 3.2절 판정 기준 그대로 "대치가 내용으로 채우는 노드"에 해당한다
Element.Separator ornament 에 흡수한다 (2026-09-05 정원석 확정, B-21) 분리선은 순서도 내용도 담지 않는다 — Element.Decoration과 같은 판정 기준에 걸린다. 별도 축(separator)을 새로 만들지 않고 ornament가 Element.Separator/LayoutContainer.Separator까지 대응 role로 흡수한다

4.2 값은 셋이고 require 를 두지 않는다#

값 뜻
prefer 있으면 가점. 없어도 후보로 남는다
indifferent 보지 않는다. 기본값
avoid 있으면 감점. 있어도 후보로 남는다

꾸밈 축에는 require 를 두지 않는다. 검색 풀 분포가 근거다. VennDiagram, HierarchicalTree, NestedScope, Pyramid 는 각 164건 이하이고 데이터 차트는 각 51건 이하다 (../README.md 3절). 여기에 꾸밈까지 필수로 걸면 후보가 0이 되어 전부 List 로 강등된다. 꾸밈이 없어도 그 컴포넌트를 쓸 수 있다.

성립 조건과 겹치지 않는다. 없으면 배치가 불가능한 것은 capability 성립 조건이 갖는다 (../spec/capability-table.md 1절). 꾸밈 축은 그 위에서 순위만 움직인다.

4.3 회의 용어는 문서 어휘로만 남기고 스키마에 두지 않는다#

N 과 N+β 를 minimal 과 rich 로 부를 수 있다. 다만 스키마 값으로 두지 않는다. 축 조합에 붙인 별명이고, 문서와 회의에서만 쓴다.

회의 용어 축으로 쓰면 어디 쓰나
N { ornament: avoid, background: avoid } 문서와 회의
N+β { ornament: prefer, background: prefer } 문서와 회의
N+α 없다. 내용이라 축이 아니다 3.2절

스키마에 프리셋 값을 두지 않는 이유가 셋이다.

  1. 두 모양을 허용하면 소비자가 분기한다. 문자열이면 프리셋, 객체면 축으로 읽어야 한다. B-5 가 '최상위가 structure(단일) 또는 components[](다중)' 둘을 "항상 components[]" 하나로 줄인 이유와 같다. 그때 근거가 "소비자 분기가 하나로 줄고 중첩 깊이 어긋남이 없어진다"였다 (../TODO.md G절).
  2. 정보를 담지 않는다. 프리셋은 축으로 펼쳐지므로 축이 표현하지 못하는 것을 표현하지 못한다. 순수한 축약이다.
  3. 축이 늘 때 뜻이 바뀐다. rich 를 '전 축 prefer' 로 정의하면 축이 하나 늘 때마다 rich 가 가리키는 범위가 달라진다. 2.3절이 등급을 기각한 것과 같은 결함이다. 별명을 스키마에 넣으면 축 집합으로 옮긴 효과가 줄어든다.

아껴지는 것도 없다. 1단계에서 생성기가 decoration 를 채우지 않으므로(4절 머리) 토큰 절감이 논점이 아니다. 전역 기본값은 설정이라 길이가 문제되지 않는다. type 을 문자열 하나로 합칠 때는 토큰 4.6% 절감이라는 실측이 있었지만, 여기에는 그런 근거가 없다.

4.4 선언 층이 셋이고 구체적인 쪽을 우선한다#

아래 표는 구체적인 쪽부터 적었다. 5.4절 해석 순서와 같은 순서다.

층 무엇을 정하나 어디 두나 1단계
Summary (질의) 그 입력에 대한 선호 생성기가 적는다 생성하지 않는다
카테고리 기본 카테고리별 예외. 예를 들어 'List 는 background: prefer' capability 상수 미정
시스템 전역 기본 아무도 안 적었을 때의 값. 예를 들어 '꾸밈이 많은 것을 우선한다' 검색 설정 여기만 잡는다

세 층으로 나눈 것은 2026-08-03 논의의 두 예시가 층이 다르기 때문이다. '꾸밈 많은 것 우선'은 전역이고 'List 는 배경'은 카테고리별이다.

1단계에서 위 두 층이 비어 있어도 동작한다. 구체적인 쪽이 없으면 다음 층으로 내려가므로, 전역 기본값 하나만 있으면 모든 질의가 그 값으로 판정된다. 층을 지금 정해 두는 이유는 나중에 채울 자리를 미리 정하는 것이다 (4절 머리).

이 세 층 구분은 이 문서의 해석이다. 원 논의는 전역과 Summary 둘만 말했다.

보류 (2026-09-05 정원석, B-21). decoration(ornament/background) 자체가 지금 단계에서 실사용 비중이 낮아, 카테고리 층 여부를 포함한 구체화는 나중에 실제로 쓸 때 다시 정한다. 지금은 시스템 전역 기본값 하나로 충분히 동작한다 (4절 머리).

4.5 색인은 보존본을 한 벌 둔다#

7/24 비대칭 원칙을 그대로 따른다. 장식은 삭제할 수 있어도 없는 것을 추가하기는 어렵다. 그래서 색인에는 장식 보존본을 한 벌 넣는다. 등급별로 여러 벌 만들지 않는다.

색인 벌 수가 곱으로 늘지 않는다. 꾸밈을 등급 3단으로 두고 등급마다 색인에 넣으면 comp-sign 변형 3종과 곱해져 컴포넌트당 최대 9벌이 된다. 보존본을 한 벌로 두면 comp-sign 변형 3벌에서 그친다.

comp-sign 변형 3종은 이 결정과 무관하다. 그쪽은 Text:Subtitle 과 Text:Hl 제거이고 목적이 검색 리콜이다. 꾸밈 축(ornament, background)과 대상 role 이 겹치지 않는다. 변형을 대치 시점 처리로 옮길지는 B-19였는데, 2026-09-05에 '옮기지 않는다'로 닫혔다 — comp-sign은 색인 시점에 3벌을 미리 계산해 저장하는 현행 방식을 유지한다.

4.6 내용 대치는 꾸밈에 관여하지 않는다#

2026-08-03 정원석. 꾸밈구조는 내용 대치가 관여할 바가 아니다. 앞으로 요소 대치가 동작할 때 참고하는 값이다.

대치 종류 무엇을 바꾸나 꾸밈구조와의 관계
내용 대치 컴포넌트 슬롯에 텍스트와 데이터를 넣는다 관여하지 않는다. Decoration 과 Background role 은 애초에 내용 대치가 채우지 않는 노드다 (3.2절)
요소 대치 요소 자체를 교체한다 꾸밈 축을 참고값으로 소비한다. 아직 동작하지 않는다 (4.7절)

그래서 지금 단계에서 꾸밈 축이 쓰이는 곳은 검색 순위 하나다. '배경 있는 List 를 우선'은 순위에만 반영된다. 꾸밈 노드를 지우거나 넣는 것은 요소 대치가 생긴 뒤에 할 수 있다. 축을 지금 정해 두는 이유가 여기 있다. 요소 대치를 만들 때 참고할 값이 미리 있어야 한다.

7/23 회의 기록과 대조해야 한다. 2026.07.23 요소 검색과 대치 요구사항 정렬이 "컴포넌트 개성 유지를 위해 장식 요소는 유지하고 내용 관련 요소만 대치"로 적었다 (Slack). 꾸밈 요소를 유지한다는 방침과 꾸밈 축을 참고해 조정한다는 것이 어디까지 양립하는지 확인이 필요하다. 유지가 기본이고 축이 그 수준을 정하는 것으로 읽었으나 이것은 이 문서의 해석이다 (6절).

4.7 요소 대치가 꾸밈구조를 소비한다#

요소 대치는 신동한님 쪽 모듈(AI 꾸미기)을 참고해 구현하기로 했다. 2026-07-23 회의에서 정렬됐고 후속 회의 인원은 허재영, 김수현, 신동한, 정원석이다 (Slack, PRD 2026q2 내용시각화 이미지 검색과 대치).

AI 꾸미기의 단계는 셋이다. 검색 쿼리, 스타일 통일된 요소 검색, 배치 (2026-06-01 김수현님이 신동한님께 확인). 1번부터 3번까지 그대로 받아쓸지 2번만 받아쓸지가 그 시점의 열린 선택이었다.

이상적인 요소 대치 흐름#

2026-08-03 정원석. 네 단계다.

# 단계 무엇을 만드나
1 reference design 을 참고한다 맞출 대상의 스타일 기준. 단위는 template, page, component 셋이다
2 교체 대상 target 을 AI 가 식별한다 그 스타일에 맞추려면 어느 요소를 바꿔야 하는가
3 target 별로 query 나 embedding 을 생성한다 그 자리에 들어올 것을 다시 서술한 검색 입력
4 deterministic constraints 를 얹는다 size 같은 확정 제약. AI 판단이 아니라 계산으로 나오는 것

2번과 3번이 7/23 회의가 '중요'로 꼽은 대목과 같다. 원문이 "비슷한 요소 찾기 사용. 다만, 교체 타겟 결정 및 타겟별 query 생성이 중요"로 적었다. 즉 검색 자체보다 무엇을 바꿀지 정하고 그것을 어떻게 서술할지가 난점이다.

꾸밈구조가 들어가는 자리#
요소 대치 입력 어디서 오나
스타일 기준 reference design (1번)
교체 대상 AI 식별 (2번)
검색 서술 query 나 embedding (3번)
확정 제약 size 등 계산값 (4번)
선호 꾸밈구조. Summary 가 적거나 카테고리 기본값이나 시스템 전역 기본값에서 온다 (4.4절)

꾸밈구조는 위 표에서 유일하게 '못 지켜도 되는' 입력이다. 나머지 넷은 지키지 못하면 그 후보가 빠지거나 검색이 성립하지 않는다. 꾸밈구조는 순위만 움직인다 (4.2절에서 require 를 두지 않은 이유와 같다).

정하지 않은 것: reference design 과 겹치는가#

reference design 이 꾸밈 수준을 이미 정해 버릴 수 있다. 사용자 템플릿이 reference 이고 그것이 꾸밈이 많다면, 요소 대치는 거기 맞추면 되므로 꾸밈구조를 따로 볼 이유가 없다. 그러면 꾸밈구조는 요소 대치에는 쓰이지 않고 검색 단계에만 쓰이는 값이 된다.

반대로 reference 와 다르게 가고 싶은 경우가 있다면 꾸밈구조가 그것을 덮어쓰는 자리가 된다.

어느 쪽인지 정하지 않았다. 이것을 정해야 답이 나오는 질문이 둘이다.

질문 왜 답이 갈리나
꾸밈구조가 요소 대치의 입력인가, 검색만의 입력인가 reference 가 꾸밈 수준을 정한다면 요소 대치는 꾸밈구조를 안 읽는다. 그러면 위 표에서 '선호' 행이 빠진다
reference 에서 꾸밈구조를 유도할지 유도한다면 생성기가 적을 이유가 더 줄어든다. 1단계에서 생성하지 않기로 한 판단과 같은 방향이다 (4절 머리)

요소 대치 착수 때 정한다 (6절).

일정과 모델 (7/23 기준)#

이 문서의 결정과 직접 관계는 없으나 요소 대치가 언제 동작하는지가 꾸밈구조의 실사용 시점을 정하므로 옮겨 둔다.

  • proprietary model 로 데이터를 누적하고 MLLM as a judge 로 refining 한다.
  • 4주 내 proprietary model 버전을 선배포한다.
  • Runpod 을 활용해 target 과 query 추론 모델을 학습한다. 내용시각화 스쿼드 안에서 진행할 수 있다.
  • 그 뒤 2주면 자체 모델로 대치해 수십 개 컴포넌트 동시 converting 의 비용과 속도가 크게 낮아질 것으로 본다.

4.8 다섯 구간이 모두 대응해야 한다#

2026-08-03 정원석 지적이다. 꾸밈구조는 Visual Summary 한 곳만 받아서는 동작하지 않는다. 추출과 색인과 검색과 대치까지 다섯이 함께 받아야 한다. 축이 role 에 대응하므로 각 구간이 할 일이 정해진다.

구간 무엇을 하나 지금 상태
추출 RLSC 노드에 Decoration 과 Background role 을 붙이고 산출물에 남긴다 role 자체는 이미 붙는다. RLSC v2.0 role 체계에 둘 다 있다. 남은 것은 그 표식을 색인이 읽을 수 있게 넘기는 경로다
색인 컴포넌트가 그 role 을 얼마나 가졌는지를 색인에 담는다 경로는 확정, 구현은 미착수 (B-22, 2026-09-05). 새 메커니즘을 만들지 않고 semantic_tags denormalize 컬럼 패턴을 그대로 따른다. 구체적 컬럼 설계는 ../TODO.md I-4
검색 카테고리 기본값과 Summary 선호를 대조해 순위를 움직인다 capability 에 꾸밈 축 열이 없다 (../spec/capability-table.md 8절). 41종 기본값을 채워야 한다
Visual Summary (질의) 그 입력에 대한 선호를 적는다 표기 미정 (5절 초안, ../TODO.md B-18)
대치 선호에 맞춰 그 role 노드를 지우거나 남긴다 요소 대치의 일이고 아직 동작하지 않는다 (4.6절, 4.7절)

색인 경로가 먼저 풀려야 나머지가 동작한다. 추출이 role 을 붙여도 색인이 못 받으면 검색이 대조할 대상이 없다. I-4 가 그 컬럼을 정하는 항목이고, semantic_tags denormalize 방식이 선례다.


5. 형과 예시 (초안)#

2026-08-04 에 확정돼 규약으로 갔다. 키 이름은 decoration 이고 page 수준도 연다. 확정 표기는 ../spec/schema-v0.5.md 2.1절, 2.2절, 2.7절이 갖는다 (../TODO.md B-18 닫힘). 아래는 그 표기를 고르기까지의 설계 근거로 남긴다.

5.1 어디에 붙나#

params 도 structure 도 아니다. params 는 카테고리별 표현 선택이고(규약 2.4절) 꾸밈구조는 카테고리와 무관하다. structure 는 내용이고 꾸밈구조는 내용이 아니다. 그래서 structure 의 형제로 열고, page 수준에도 같은 키를 연다.

5.2 형 정의#

ts
// 꾸밈 축에 담기는 값. 이 셋이 전부다
export type Preference = 'prefer' | 'indifferent' | 'avoid';

// 꾸밈구조. 모양은 하나다. 축 하나가 RLSC role 군 하나에 대응한다 (4.1절)
// minimal 이나 rich 같은 프리셋 문자열은 받지 않는다 (4.3절)
export interface Decoration {
  ornament?: Preference;    // Element.Decoration, LayoutContainer.Decoration, Element.Separator, LayoutContainer.Separator
  background?: Preference;  // Element.Background, LayoutContainer.Background
}

export interface VisualSummary {
  version: '0.5';
  title: string;
  components: Component[];
  relations?: Relation[];
  decoration?: Decoration;   // page 수준. 아래 컴포넌트의 기본값이 된다
}

export interface Component {
  id?: string;
  type?: SemanticCategory;
  alternatives?: SemanticCategory[];
  params?: Record<string, unknown>;
  structure: Structure;
  decoration?: Decoration;   // 이 컴포넌트에만 적용된다
}

값이 정확히 셋인 이유.

값 뜻 왜 있나
prefer 그 role 노드를 가진 후보를 앞에 둔다 탈락시키지 않는다. require 를 두지 않은 이유는 4.2절
avoid 그 role 노드를 가진 후보를 뒤로 보낸다 prefer 의 대칭이다. '꾸밈 없는 깨끗한 것'을 요구하는 자리다
indifferent 보지 않는다. 기본값 안 적은 것과 같은 뜻이지만 명시할 수 있어야 한다. 위 층에서 물려받은 값을 취소하려면 이 값이 필요하다 (5.4절 예시 3)

값의 모양은 셋으로 갈라 판정한다 (2026-08-29 확정, 정원석).

모양 판정
enum (prefer·indifferent·avoid) 4단계와 5단계가 쓰는 것이 이것이다. 지금 규약에 있는 형태다
객체 (축 아래에 구체적인 값을 담는 것) 나중 단계의 자리다. 기각이 아니라 4·5단계 범위가 아닐 뿐이고, 그 단계에서 무엇을 담을지는 정하지 않았다
수치 점수 (ornament: 0.8 같은 가중치) 앞으로도 두지 않는다. 값에 점수를 매기는 것이 의미가 없다고 판단했다. 채점 코드를 건드리지 않기로 한 결정과 어긋나고(../spec/capability-table.md 1절) entity.weight(number)가 다른 뜻으로 이미 있다는 종전 근거도 그대로다

그래서 "prefer 가 몇 점인가"는 물음 자체가 성립하지 않는다. 남는 것은 점수가 아닌 형태로 검색이 이 선호를 어떻게 반영하는가다 (6절).

5.3 JSON Schema 조각#

규약 2.2절 $defs 에 더할 형태다.

json
{
  "decoration": { "$ref": "#/$defs/decoration" },

  "$defs": {
    "decoration": {
      "type": "object",
      "additionalProperties": false,
      "minProperties": 1,
      "properties": {
        "ornament":   { "$ref": "#/$defs/preference" },
        "background": { "$ref": "#/$defs/preference" }
      }
    },
    "preference": {
      "type": "string",
      "enum": ["prefer", "indifferent", "avoid"],
      "default": "indifferent"
    }
  }
}

additionalProperties: false 라서 없는 축 이름을 적으면 거부된다. 축을 늘릴 때 스키마를 함께 고쳐야 한다는 뜻이고, 이것은 의도한 것이다. 오타로 적은 축이 무시되면 적었는데 적용되지 않는 사고가 난다.

5.4 해석 순서#

축마다 따로 해석한다. 앞 순서를 우선한다. 축 단위로 병합하므로 위 층의 일부 축만 덮을 수 있다.

순서 어디 누가 채우나
1 component.decoration 생성기 (1단계에서는 안 채운다)
2 page.decoration 생성기 (1단계에서는 안 채운다)
3 카테고리 기본값 capability 상수
4 시스템 전역 기본값 검색 설정. 1단계에서는 여기만 채운다
5 indifferent 규약 기본값

병합이 축 단위인 것이 중요하다. 층 전체를 통째로 갈아치우면 'page 는 꾸밈을 다 원하는데 이 컴포넌트만 배경 빼고'를 표현할 수 없다 (예시 2).

예시 1. 아무것도 안 적은 경우 (1단계 기본 동작)

yaml
version: '0.5'
title: 분기 실적
components:
  - type: Enumeration.List
    structure:
      entities: [ { label: 신규 계약, items: [12건] } ]

decoration 가 없으므로 전역 기본값이 그대로 적용된다. 전역이 '꾸밈 많은 것 우선'이면 두 축이 prefer 로 해석된다.

예시 2. page 값을 컴포넌트가 축 단위로 덮는다

yaml
decoration:                      # page 수준. 회의 용어로는 rich 에 해당한다
  ornament: prefer
  background: prefer
components:
  - type: Enumeration.List  # 물려받는다. 둘 다 prefer
    structure: { entities: [ ... ] }
  - type: ProcessFlow.Steps
    decoration:
      background: avoid     # 이 축만 덮는다
    structure: { entities: [ ... ] }

두 번째 컴포넌트의 해석 결과는 ornament: prefer(page 에서 물려받음)와 background: avoid(자기 값)다. ornament 를 다시 적지 않아도 유지된다.

예시 3. 물려받은 값을 취소한다

yaml
decoration:                      # 회의 용어로는 minimal
  ornament: avoid
  background: avoid
components:
  - type: Enumeration.List
    decoration:
      background: indifferent   # 배경만 중립으로 되돌린다
    structure: { entities: [ ... ] }

결과는 ornament: avoid, background: indifferent 다. indifferent 를 명시적 값으로 둔 이유가 이 자리다. 축을 빼면 page 의 avoid 를 그대로 물려받는다.

예시 4. 카테고리 기본값과 만난다

yaml
components:
  - type: Enumeration.List    # 카테고리 기본값이 background: prefer 라고 가정
    structure: { entities: [ ... ] }
  - type: Enumeration.List
    decoration: { background: avoid }
    structure: { entities: [ ... ] }

첫째는 카테고리 기본값을 받아 background: prefer 다. 둘째는 자기 값이 있으므로 avoid 를 우선한다. 41종 기본값은 아직 안 채웠다 (6절).

5.5 안티패턴#

이렇게 쓰지 않는다 왜
decoration: { ornament: require } require 는 없는 값이다. 장식을 필수로 걸면 후보가 0이 되어 전부 List 로 강등된다 (4.2절)
decoration: { ornament: 0.8 } 수치 점수를 두지 않는다. 앞으로도 두지 않는다 (5.2절)
decoration: { description: prefer } description 은 축이 아니다. 설명 텍스트는 내용이고 structure 가 담는다 (3.2절)
decoration: rich, decoration: minimal 프리셋 문자열을 받지 않는다. minimal 과 rich 는 문서와 회의에서 쓰는 별명이고 스키마 값이 아니다. 축으로 적는다 (4.3절)
decoration: { ... }, style: { ... } 키 이름이 다르다. decoration 은 RLSC role 과 겹치고 style 은 스타일 대치가 쓴다 (0절)
생성기가 decoration 를 채우는 것 1단계에서는 안 채운다 (4절 머리)

5.6 1단계에서 이 필드는 optional 이고 생성기가 채우지 않는다#

스키마에 자리만 열고 값은 시스템 전역 기본값에서 온다 (4절 머리). 위 예시는 '적을 수 있다'를 보인 것이지 '생성기가 이렇게 쓴다'가 아니다.

선례가 둘 있다. entity.components[] 와 page 수준 relations 가 같은 방식으로 열려 있다. 규약 1절이 "중첩은 평탄화된다"로, 8.7절이 "표현 가능성만 열어 두고 켜는 시점은 생성기 정책이 정한다"로 적어 두었다. decoration 도 그 문장을 그대로 따른다.


6. 아직 정하지 않은 것#

항목은 ../TODO.md 가 SSoT다. 여기에는 무엇을 정해야 하는지만 적는다.

무엇 왜 열려 있나
규약 표기 확정 2026-08-04 에 닫혔다. ../spec/schema-v0.5.md 2.7절
Element.Separator 를 세 번째 축으로 열지 닫혔다 (2026-09-05 정원석, B-21). 세 번째 축을 새로 만들지 않고 ornament가 Element.Separator/LayoutContainer.Separator까지 흡수한다 (4.1절)
장식 role 이 둘뿐인지 2026-08-04 에 대조했다. Element role 은 정확히 8종이고 3.2절 판정 기준으로 대치가 안 채우는 것은 Decoration, Background, Separator, Marker 넷이다. 둘인 것은 role 이 둘이어서가 아니라 둘을 미뤘기 때문이다 (4.1절)
LayoutContainer.PageHeader 와 PageFooter 를 어떻게 볼지 닫혔다 (2026-09-05 정원석, B-21). 축 후보가 아니라 애초에 decoration 모델의 관할 밖이다. 둘 다 컴포넌트도 위성(DESC)도 아니라 페이지 프레임(슬라이드 마스터 격, 섹션명·문서명·페이지 번호 같은 반복 정보)이라 Visual Summary 스코프 자체에 안 들어온다. 축은 role 단위 정책값이지 node를 하나하나 지목하는 게 아닌데, PageHeader/PageFooter는 축이 필요한지 이전에 다룰 대상이 아니다
DECO 노드 분류와 role 의 관계 무효화됨 (2026-09-05). 신영빈님의 CORE/DESC/DECO 노드 분류는 IIS가 아니라 RAT pod 트랙(holding/deprecated)의 파생물로 확인됐다 — 대조 대상이 아니다 (content-binding-contract.md 1.1절)
카테고리 층을 둘지 보류 (2026-09-05 정원석). decoration 자체가 지금 실사용 비중이 낮아 구체화 시점에 다시 정한다 (4.4절)
41종의 카테고리 기본값 이월 (2026-09-05). 카테고리 층 여부가 정해져야 채울 수 있어 decoration 구체화 시점으로 미룬다
색인이 role 표식을 받는 경로 4.8절. 노드 분류를 담는 컬럼이 없다 (../TODO.md I-4). 이것이 먼저 풀려야 검색이 대조할 수 있다
검색이 어떻게 반영하나 점수가 아닌 형태로 prefer 와 avoid 를 순위에 어떻게 반영하는지, fallback 보다 먼저 보는지 나중에 보는지. 선호를 못 채운 후보와 fallback 후보의 순서가 이것으로 정해진다. 수치 점수는 쓰지 않는다(5.2절). 이석님
comp-sign 변형을 대치 시점으로 옮길지 4.5절 주석. 이석님 확인 전에는 확정이 아니다
I-2 세 축 정렬과의 관계 이 문서가 세 축 중 둘(등급, 노드 분류)에 답을 냈다. comp-sign 변형까지 합치는 것이 I-2 의 남은 절반이다

7. 참고#

↑ 목차로 · 3부 머리로

4부

실측 — evaluation/

새 경로를 도입할지 판단할 때 쓰는 숫자. 가설을 세우고, 재고, 틀린 기록까지 남긴다.

14장실측 자료 색인 정본무엇을 넣고 무엇을 넣지 않나

원문 evaluation/README.md GitHub 에서 보기 3 KB 원문 제목 「`evaluation/` — 새 경로 도입 판단에 쓰는 실측 자료」

이 폴더의 역할 Visual Summary 새 경로(comp-frame)를 도입할지, 도입하면 무엇이 얼마나 나아지는지를 실측으로 가늠한 문서를 모은다. 오너 정원석 · 상태 DRAFT · 최종 갱신 2026-09-20

spec/ 은 규약, design/ 은 설계 의도, reference/ 는 외부 사실의 사본, plan/ 은 계획이다. 이 폴더는 그 넷과 다른 장르다. 바꾸기 전과 바꾼 뒤를 재서 도입 여부를 정하는 자료다.

네 가지를 다룬다.

쟁점 결론 근거
⚠️ 전제 새 경로가 자리째 비는 것은 설계가 아니라 버그이고 MOR-2504 로 고친다. 아래 새 경로 수치는 전부 그 전 상태에서 잰 것이라, 고치면 judge 쪽 결론이 바뀔 수 있다 placeholder-effect.md 8절 6번
새 경로가 지금 없던 문제를 만드는가 안 만든다. 내용이 대치되지 않고 임시 문구나 원본 글이 그대로 남는 Placeholder Text Slot 의 발생 빈도는 운영 18.9%, 새 경로 16.6% 로 비슷하다. 다만 생겼을 때 그 개수가 1.39개에서 6.34개로 는다 placeholder-effect.md 7.4절
기존 문제 중 고치는 범위 judge BAD의 76.0%를 결정하는 텍스트 가독성 축 중 겹침·넘침 계열을 재순위 규칙이 거른다 placeholder-effect.md 4.4절, 7.5절
개선 폭 공통 873 입력에 입력당 1행으로 맞춰 재면 운영 경로 BAD 28.4% → 새 경로+V5 23.9%(−4.5%p). Placeholder Text Slot 최소화 규칙이 약 7%p 추가 개선(재판정 잡음 4%p 제외). 앞서 보고된 −11.4%p는 집계 단위가 한쪽에만 유리해 나온 값이라 철회했다 rerank-rule-rounds-26-27.md, placeholder-effect.md 7.3절, 9.3절
제품 지표 환산 Placeholder Text Slot 을 전부 없앴다고 두면 카드 채택률 상대 +6.2%, 상위 10위 안에서는 상대 +12.1%. 상한선이고, 부분만 줄일 때의 값이 아니다. 운영 대시보드의 약 40%는 하위검색 채택률이라 분모가 다르다. 운영 경로에서 잰 값이라 새 경로에는 그대로 옮길 수 없다 placeholder-effect.md 5.1절, 9.4절
그 손실이 컴포넌트 부적합 때문은 아닌가 아니다. 글 넘침을 따로 재서 성향점수로 보정해도 odds ratio 0.688 → 0.663 으로 거의 안 움직인다 placeholder-effect.md 5.4절

문서#

파일 측정 대상
placeholder-effect-brief.md 스쿼드와 Confluence용 요약. 결과 숫자와 결정만 담는다. 분석 리포트 페이지에 올릴 본문이다
placeholder-effect.md 대치 결과에 Placeholder Text Slot이 judge 판정과 사용자 채택, 다운로드에 주는 효과. 운영 경로와 새 경로를 갈라 쟀다. 도입 판단은 9절. 수치 SSoT
rerank-rule-rounds-26-27.md 재순위 규칙 회차 26 과 27 이 정반대 결과를 낸 원인. 결함 분류 체계의 공백을 코드로 확인했다
judge-rerun-0927.md 같은 운영 입력 100건으로 운영 markdown summary 방식과 Visual Summary(dev 배포판)의 judge BAD 비율을 비교했다. BAD 비율은 운영 방식이 32.0%, Visual Summary가 30.9%이고, 통계적으로 의미 있는 차이는 없다. 재현 코드는 prisonbreak의 scripts/judge-loop/mor2477/에 있다
judge-rerun-0927-brief.md 위 문서를 스쿼드와 Confluence에 공유하려고 줄인 요약

재현#

조회와 집계 코드를 문서 옆에 둔다. scripts/README.md 가 네 단계 절차와 중간 파일을 저장소에 두지 않는 이유를 적는다.

파일 역할
scripts/placeholder-adoption-funnel.sql Databricks 조회 네 가지
scripts/analyze-placeholder-effect.py 보고서 4절, 5.1절, 6.3절, 9.4절의 수치 집계

이 폴더에 넣는 것과 넣지 않는 것#

넣는다. 도입 전후 비교, 효과 크기 추정, 제품 지표 환산, 그 판단에 쓴 측정 방법과 한계, 그리고 수치를 다시 만드는 조회와 집계 코드(scripts/).

넣지 않는다. 사용자 입력 원문이 든 중간 파일, 규약(spec/), 설계 의도(design/), 계획(plan/), judge 자체의 운영 이력 (../../../monitoring/llm-as-a-judge-history.md), 판정 루프를 돌리는 절차 (mordor-wiki monitoring/judge-loop/README.md).

↑ 목차로 · 4부 머리로

15장Placeholder Text Slot 효과 · 분석 보고서 정본judge 는 안 움직이고 사용자 채택만 떨어진다

원문 evaluation/placeholder-effect.md GitHub 에서 보기 43 KB 원문 제목 「Placeholder Text Slot이 judge 와 사용자 행동에 주는 효과: 분석 보고서」

이 문서의 역할 대치 결과에 Placeholder Text Slot이 judge 판정과 사용자 채택, 다운로드에 주는 효과를 실측으로 재고, 그것으로 새 경로 도입 판단에 필요한 네 가지를 답한다. 새 경로가 없던 문제를 만드는가, 기존 문제의 어느 면을 고치는가, 얼마나 고치는가, 제품 지표로 환산하면 얼마인가. 오너 정원석 · 상태 DRAFT · 최종 갱신 2026-09-20 이 문서가 SSoT이다. 측정 방법, 결과, 판정, 도입 판단이 모두 여기 있다. 스쿼드와 Confluence용 숫자만 모은 요약은 placeholder-effect-brief.md 다. 요약에 없는 수치를 다른 곳에 두지 않는다. 재현 조회와 집계 코드는 scripts/ 에 있다. 처치 변수(Placeholder Text Slot 수)만은 저장된 값이 없어 대치를 재현해야 나온다. 재현 엔진 src/lib/demand/logic-replace 는 저장소 main 브랜치에 있으므로, 작업 브랜치에 없으면 git worktree 로 origin/main 을 꺼내 돌린다(부록 B.3) 관련 같은 폴더 rerank-rule-rounds-26-27.md · 발단 Slack 류민 2026-09-20 · judge 운영 이력 ../../../../monitoring/llm-as-a-judge-history.md

먼저, Placeholder Text Slot 이 무엇인가#

visual summary 는 사용자가 쓴 글을 템플릿 컴포넌트의 텍스트 자리에 끼워 넣어 한 장의 그림을 만든다. 이 끼워 넣기를 대치라고 부른다. 자리가 글보다 많으면 채우지 못한 자리가 남는다. 그 남은 자리가 Placeholder Text Slot 이다.

남은 자리는 지금 두 경로에서 다르게 보인다. 이 차이가 이 문서 전체를 가른다.

경로 남은 자리가 화면에 어떻게 보이나
운영 경로(comp-sign) "제목을 입력해주세요." 같은 임시 문구로 덮인다. 또는 템플릿의 원래 글이 그대로 남는다
새 경로(comp-frame) 자리가 통째로 빈다. 아무것도 안 들어간 칸이 보인다

그래서 같은 "남은 자리" 라도 judge 는 새 경로에서만 감점한다. 글자가 있으면 채점기가 넘어가기 때문이다. 반대로 사용자는 운영 경로에서도 덜 고른다. 임시 문구가 보이는 카드를 피한다. 이 어긋남이 이 문서의 출발점이다.

⚠️ 새 경로가 자리째 비는 것은 설계가 아니라 버그이고, 고치기로 정해져 있다. 2026-09-18 에 홍지혜, 류민, 정원석이 구두로 확인했고 MOR-2504 로 등록됐다(2026-09-20 기준 Draft, 미해결). 고치면 새 경로도 미대치 텍스트 전량이 임시 문구로 바뀐다. 운영 경로에 이미 있던 규약을 따르는 것이고, 원본 글이 그대로 남는 경우는 VS, 대, 1, 2 같은 특수 내용으로 한정된다.

그래서 이 문서를 읽을 때 경로 차이를 항구적인 성질로 읽으면 안 된다. 지금 시점의 상태다. MOR-2504 가 닫히면 두 경로의 증상이 같아지고, 그때 다시 봐야 하는 것을 8절에 적었다.

세는 방법은 판정문이 아니라 대치 자체에서 온다. 임시 문구 15종과 문자열이 같은 자리를 센다. LLM 도 렌더링도 판정도 거치지 않는다(2절).

읽을 사람은 visual summary 의 대치 품질을 정하는 사람이다. 이 문서를 읽고 나면 Placeholder Text Slot을 줄이는 일에 자원을 얼마나 넣을지를 정할 수 있다. 그 근거가 judge 점수인지 채택률인지도 정할 수 있다.

처음 읽는다면 1.1절과 1.2절을 먼저 본다. 이 문서는 운영 경로와 새 경로를 오간다. '회차' 라는 말이 가리키는 판정 기록이 두 군데다. 그 둘을 가르지 않으면 4절부터 수치가 섞여 읽힌다.


0. 요약#

물음 답 근거
운영 대치 결과에 Placeholder Text Slot이 얼마나 생기나 사용자에게 노출된 결과의 18.9% 에 Placeholder Text Slot이 1개 이상 있다. 있을 때 평균 1.39개다 3.1절
Placeholder Text Slot 개수가 judge BAD 비율을 올리나 운영 경로에서는 올리지 않는다. odds ratio 1.012, 95% CI 0.879~1.166, permutation test p = 0.878 4절
Placeholder Text Slot이 사용자 채택률을 떨어뜨리나 떨어뜨린다. 이 숫자는 카드 채택률이라 3.96% 대 2.74%다. 대시보드의 약 40%는 하위검색 채택률이라 분모가 다르다. odds ratio 0.688, 95% CI 0.487~0.972, permutation test p = 0.022 5.1절
그 손실이 컴포넌트 부적합(글 넘침) 때문은 아닌가 아니다. 넘침을 따로 재서 성향점수로 보정해도 odds ratio 0.663(95% CI 0.402~0.981)으로 거의 안 움직인다 5.4절
Placeholder Text Slot이 디자인 다운로드와 컴포넌트 잔존을 떨어뜨리나 사용자가 한 번 고른 뒤로는 추가 손실이 관측되지 않는다. odds ratio 0.908, 95% CI 0.572~1.440 5.2절
회차 27(새 경로의 재순위 실험, 1.3절)이 낸 judge BAD 11%p 개선은 무엇이었나 규칙이 실제로 낸 몫은 약 7%p 다. 나머지 4%p 는 같은 그림을 다시 채점할 때 생기는 잡음이다. 새 경로에 한정된 효과다 7.3절
새 경로를 도입하면 Placeholder Text Slot 문제가 더 생기나 빈도는 비슷하다(운영 18.9%, 새 경로 16.6%). 다만 생겼을 때 개수가 1.39개에서 6.34개로 는다 9.1절
Placeholder Text Slot을 없애면 제품 지표가 얼마나 오르나 Placeholder Text Slot을 전부 없앴다고 두면 채택률이 상대 +6.2%, 상위 10위 안에서는 상대 +12.1% 다. 상한선이고, 부분만 줄일 때의 값이 아니다 9.4절
새 경로로 바꾸면 judge BAD 가 얼마나 내려가나 운영 경로 28.4% 에서 새 경로 + V5 23.9% 로 4.5%p 다. 앞서 보고한 11.4%p 는 집계 단위가 한쪽에만 유리해 나온 값이라 철회했다 9.3절

세 줄로 적으면 이렇다.

  1. 운영 경로에서 Placeholder Text Slot은 judge BAD 비율을 바꾸지 않는다. 못 채운 자리를 임시 문구로 덮기 때문이다.
  2. 같은 Placeholder Text Slot이 사용자 채택률은 떨어뜨린다. 손실은 고르는 단계에서 생기고 그 뒤로는 더 생기지 않는다.
  3. 새 경로에서는 Placeholder Text Slot이 자리째 비어 judge BAD 비율도 함께 움직인다. 단, 자리째 비는 것은 버그이고 MOR-2504 로 고친다. 고치면 3번이 1번처럼 바뀔 수 있다(7.5절, 8절 6번).

0.1 이 보고서가 쓰는 통계 용어#

용어 뜻 이 보고서에서
odds 일어날 확률을 일어나지 않을 확률로 나눈 값 카드 채택률 4% 면 odds 는 0.04 / 0.96 = 0.0417 이다
odds ratio 두 집단의 odds 를 나눈 값. 1 이면 차이가 없고, 0.7 이면 한쪽이 30% 낮다 처치군(Placeholder Text Slot 있음)을 대조군(없음)으로 나눈다
95% CI 신뢰구간. 같은 조사를 100번 하면 95번은 이 구간 안에 참값이 든다 구간이 1 을 포함하면 차이가 있다고 말하지 않는다
permutation test 처치 라벨을 무작위로 섞어 귀무분포를 직접 만드는 검정 정규 근사를 쓰지 않으므로 표본이 작아도 쓸 수 있다
minimum detectable effect (MDE) 검정력 80% 로 잡아낼 수 있는 가장 작은 차이 이 값보다 작은 효과는 없다고 말할 수 없다
Mantel-Haenszel 층마다 따로 비교한 뒤 하나로 합치는 방법 순위나 요약을 층으로 둬서 교란을 뺀다

odds ratio 를 쓰는 이유는 층마다 기저율이 다르기 때문이다. 채택률은 0위에서 9.39% 이고 20위 밖에서 2.53% 다. 차이(%p)로 합치면 기저율이 높은 층의 값이 합에 더 크게 반영된다. odds ratio 는 그 영향을 받지 않는다. 읽기 쉬우라고 차이(%p)도 함께 적는다.


1. 배경과 가설#

1.1 이 문서가 다루는 기능#

visual summary 는 사용자가 입력한 글을 템플릿 컴포넌트에 넣어 한 장의 그림으로 만든다. 단계는 넷이다.

사용자 원문 → 요약(markdown 또는 YAML) → 절 단위로 검색 → 대치 → 검색 결과 카드로 노출
                                                                      ↓
                                                        사용자가 고른다(채택) → 편집 → 다운로드

'대치' 는 검색으로 고른 컴포넌트의 텍스트 자리에 요약 글을 끼워 넣는 일이다.

자리가 글보다 많으면 Placeholder Text Slot이 생긴다. 이 문서는 그 Placeholder Text Slot이 품질 판정과 사용자 행동에 무엇을 하는지를 잰다.

검색과 대치를 하는 코드가 지금 두 벌이다. 이 문서는 두 경로를 끝까지 가른다.

이름 무엇인가 Placeholder Text Slot을 어떻게 처리하나
운영 경로(comp-sign) 지금 사용자에게 나가고 있는 것 못 채운 자리를 "설명을 입력해주세요." 같은 고정 문구로 덮는다
새 경로(comp-frame) 교체하려고 만들고 있는 것. 아직 사용자에게 안 나간다 유닛(카드)과 항목이 통째로 빈다

1.2 이 문서가 쓰는 말#

말 뜻
절 요약을 최상위 # 제목으로 쪼갠 조각. 운영은 절마다 따로 검색한다
단위 판정과 집계의 한 칸. 운영 경로에서는 절 하나, 새 경로에서는 YAML 의 component 하나다
후보 한 단위의 검색이 돌려준 컴포넌트 목록. 제품은 그중 1위를 쓴다
재순위 검색이 돌려준 상위 k 개 안에서 규칙을 걸어 다시 고르는 것. 검색 순위 코드는 건드리지 않는다
회차 judge 에 A/B 한 쌍을 올려 채점한 판정 한 번. 번호는 Eregion 판정 기록의 것이다(#21, #26, #27)
대조군, 처치군 한 회차 안에서 짝으로 채점하는 두 쪽. 대조군은 규칙을 걸지 않은 쪽이고, 처치군은 재순위 규칙을 건 쪽이다
V5 재순위 규칙 하나의 이름. 채움 하한 0.10 과 원래 폭 줄 넘침 최소를 건다. Placeholder Text Slot 규칙이 아니다
Placeholder Text Slot (unauthored_count) 대치 후에도 사용자 글이 아닌 자리의 수. 2.1절에서 정의한다
통째로 비는 자리 (blank) 유닛이나 항목이 통째로 빈 것. 지금은 새 경로에만 있다. 버그이고 MOR-2504 로 고치면 없어진다
카드 채택률 노출된 컴포넌트 카드 한 장이 골라졌는지. 분모는 (검색 × 컴포넌트) 노출 쌍이다. 이 보고서 5절의 3~4%가 이것이다
하위검색 채택률 그 하위검색에서 카드를 하나라도 골랐는지. 운영 Databricks 대시보드와 DA Report 01 의 약 40%가 이것이다

회차 번호를 매기는 판정 기록이 둘이다. 위의 '회차' 는 Eregion 판정 A/B 기록의 번호다. 3절의 0831 은 다른 판정 기록(/judge-stats 화면이 읽는 public.judge_run 표)의 회차 이름이다. 번호 체계가 겹치지 않는다. 이 문서는 앞의 것을 '회차 27' 로, 뒤의 것을 회차 0831 로 적어 가른다.

3절의 표본 이름(판정 표본, 노출 표본, 채택 표본)은 이 표의 대조군, 처치군과 다른 축이다. 섞지 않는다. A 는 judge 판정 표본, B 는 운영 노출 표본, C 는 채택 이후 표본이다.

1.3 분석의 출발점#

2026-09-20 에 류민님이 새 경로의 재순위 실험 결과를 스쿼드에 공유했다 (Slack).

그 가운데 회차 27 은 "Placeholder Text Slot 이 가장 적은 후보를 고르는 규칙"을 처치군으로 둔 판정이다. 대조군은 제품 순위 그대로다. judge BAD 비율이 대조군 27% 에서 처치군 16% 로 내려갔다.

여기서 정원석님이 물은 것이 이 분석의 출발점이다. judge 가 감점하는 것과 사용자가 피하는 것이 같은지를 갈라야 한다. 같지 않다면, Placeholder Text Slot을 없애는 일이 제품 개선인지 판정 점수 맞추기인지가 달라진다.

1.4 가설#

가설 귀무가설 판정 어디서
Hypothesis 1 대치 결과에 Placeholder Text Slot이 많을수록 judge 가 매기는 BAD 비율이 오른다 Placeholder Text Slot 개수와 judge BAD 비율은 무관하다 기각 4절
Hypothesis 2 대치 결과에 Placeholder Text Slot이 많을수록 사용자 채택률이 내려간다 Placeholder Text Slot 개수와 사용자 채택률은 무관하다 채택 5절
Hypothesis 3 회차 27 이 낸 judge BAD 11%p 개선은 Placeholder Text Slot을 줄인 결과다 다른 원인이다 조건부 채택 7절

2. Placeholder Text Slot을 어떻게 쟀나#

2.1 Placeholder Text Slot이란 무엇인가#

Placeholder Text Slot은 대치 후에도 사용자 글이 들어가지 않은 텍스트 자리다. 운영 경로(1.1절)에서 두 종류로 갈린다.

이름 무엇인가 화면에 어떻게 보이나
임시 문구 시그니처가 다루는 자리인데 채울 글이 없어 고정 문구로 덮인 것 "설명을 입력해주세요." 같은 글이 보인다
원본 글 잔존 시그니처가 다루지 않아 템플릿 원문이 그대로 남은 것 사용자 글이 아닌 글이 보인다

임시 문구는 role 5종과 길이 3단을 곱한 15개 문자열로 닫혀 있다. SSoT 는 comp-sign 의 PLACEHOLDERS 표이다. 큰따옴표까지 값의 일부다. aippt-prisonbreak 의 scripts/probe-visual-summary-placeholder.ts 실행으로 확인했다.

role S M L
Title "제목" "제목 입력" "제목을 입력해주세요."
Subtitle "부제목" "부제목 입력" "부제목을 입력해주세요."
Desc "설명" "설명 입력" "설명을 입력해주세요."
Hl "강조" "강조 입력" "강조할 내용을 입력해주세요."
default "내용" "내용 입력" "내용을 입력해주세요."

2.2 Placeholder Text Slot을 세는 절차#

운영 검색 기록 표의 요약과 운영 readonly 의 시그니처, 시트를 쓴다. 운영과 같은 엔진(comp-sign 2.5.0)으로 대치를 재현한다. 그 결과에서 센다. LLM 호출과 그림 렌더링, judge 판정은 없다.

운영 요약 → prepareSearchQuery → pickReplacementMatch → buildContentReplacement
                                                              ↓
   임시 문구 = nodeTextMap 의 값 중 2.1절 15개와 문자열이 같은 자리 수
   원본 글 잔존 = 시트의 텍스트 노드 중 nodeTextMap 에 없고 글이 비어 있지 않은 자리 수

이 방법을 고르기까지 여섯 가지를 견줬다. 비교는 부록 A 에 있다.

2.3 폐기한 측정 방법과 그 이유#

처음에는 aippt-prisonbreak 의 scripts/judge-run/export_judge_run.py 의 has_residue() 처럼, judge 판정문에서 임시 문구를 찾는 방법을 썼다. 2026-09-20 에 폐기했다. 그 방법은 디자인이 아니라 판정문의 성질을 재기 때문이다. 실측 근거는 셋이다.

문제 실측
검출률이 판정문 길이에 끌려간다 길이 1분위 1.45.3%, 5분위 16.351.4% 로 10배 차이
처치가 결과와 같은 문장에서 나온다 잔존으로 잡힌 PERFECT 행의 77.5% 가 '감점 대상 아님' 문장을 동반한다. 비잔존 행은 26.6%
개수를 세지 못한다 판정문에서는 몇 번 언급했는지만 센다

이 편향은 결과와 얽혀 있어 보정할 수 없다. 그래서 2.2절의 절차로 바꿨다.


3. 어떤 표본을 썼나#

모집단은 셋이다. 표의 '풀' 이 서로 다르므로, 세 표본의 수치를 직접 견주지 않는다.

판정 표본 노출 표본
목적 Hypothesis 1 검증 Hypothesis 2 의 채택 부분 검증
원천 판정 회차 0831 (디벨롭 sigv2 검증). 1.2절이 가른 다른 판정 기록이다 GA view_component, action_component
기간 2026-08 판정 2026-09-01 ~ 09-14
풀 dv2.4, 유효키 v3.6 dv2.0/2.1, 유효키 v3.4 (실서빙)
표집 회차 전량 crc32(search_id) % 100 < 8, 검색 8%
단위 (요약 × 후보) 판정 행 (검색 × 컴포넌트) 노출 쌍
원 자료 7,901행 20,377쌍. 검색 1,126개 중 절이 하나인 1,009개
대치 재현 성공 7,238행, 91.6% 7,535쌍, 37.0%
결과 변수 judge 종합 등급 BAD GA 채택 여부, 281건

다운로드와 컴포넌트 잔존은 사건이 드물어 표본을 따로 뽑았다.

채택 표본
목적 Hypothesis 2 의 다운로드와 컴포넌트 잔존 부분 검증
원천 GA action_component, design_download_resource
기간 채택 2026-08-11 ~ 09-14. 다운로드 관측은 09-21 까지
표집 crc32(search_id) % 100 < 15, 검색 15%
단위 (검색 × 컴포넌트) 채택 쌍
원 자료 3,601쌍. 절이 하나인 검색만 남겨 3,208쌍
대치 재현 성공 963쌍, 30.0%
결과 변수 7일 내 디자인 다운로드 1,293건, 컴포넌트 잔존 517건

component_key 는 길이 100 인 행이 다음 행으로 이어지는 분할 적재다. 컴포넌트 잔존 판정은 (design_idx, chunk_uuid) 로 묶어 이어 붙인 뒤 uuid 포함 여부로 한다.

노출 표본과 채택 표본의 재현 성공률이 낮다. 6.1절에서 편향 여부를 따로 검증한다.

3.1 표본별 Placeholder Text Slot 개수 분포#

Placeholder Text Slot 개수 판정 표본 노출 표본
0개 70.1% 81.1%
1개 19.5% 14.9%
2개 3.8% 1.9%
3개 2.7% 1.4%
4개 이상 3.8% 0.7%
1개 이상 29.9% 18.9%
있을 때 평균 1.82개 1.39개
컴포넌트 텍스트 자리 수 중앙값 6개 5개

임시 문구와 원본 글 잔존은 서로 다른 사건이다. 판정 표본 기준으로 임시 문구만 21.1%, 원본 글 잔존만 19.6%, 둘 다 8.8%, 둘 다 없음 50.5% 다.


4. Hypothesis 1 검증: Placeholder Text Slot 개수는 judge BAD 비율을 바꾸지 않는다#

4.1 같은 요약에 딸린 후보끼리만 견준다#

한 요약에 후보 약 10개가 함께 판정됐다. 요약을 층으로 둔다. 층 안에서는 입력과 요약, 판정 시점이 모두 같다. 달라지는 것은 컴포넌트뿐이다.

4.2 Placeholder Text Slot 개수별 judge BAD 비율#

Placeholder Text Slot 0개 1개 2개 3개 4개 이상
n 5,077 1,414 275 197 275
BAD 27.02% 26.45% 25.82% 22.34% 26.18%

아래는 요약 층화 Mantel-Haenszel odds ratio 다. 분산은 Robins-Breslow-Greenland 로 구했다.

처치 odds ratio 95% CI z
임시 문구 1개 이상 1.012 0.879 ~ 1.166 +0.17
원본 글 잔존 1개 이상 1.082 0.947 ~ 1.236 +1.16

층 안에서 처치 라벨을 1,000회 무작위 재배정한 permutation test에서, 관측 log-OR 이 +0.0119 이다. 귀무분포의 2.5~97.5% 구간이 −0.146 ~ +0.136 이다. 양측 p 는 0.878 이다.

4.3 판정: 효과를 못 찾은 것이 아니라 효과가 없다#

95% 구간 0.879 ~ 1.166 이 equivalence bounds [0.80, 1.25] 안에 완전히 들어간다. BAD odds 를 17% 이상 올리는 효과는 배제된다. 12% 이상 내리는 효과도 배제된다. 검정력 부족으로 못 찾은 것이 아니라 효과가 없다.

축별로도 차이가 모두 2%p 안이다.

축 Placeholder Text Slot 있음 없음 차이
구조 적합성 3.29% 3.51% −0.22%p
내용 대치 정확성 9.12% 9.34% −0.22%p
시각적 계층 구조 12.36% 12.15% +0.20%p
텍스트 가독성 19.39% 20.17% −0.78%p
디자인 완결성 19.11% 20.88% −1.77%p

'내용 대치 정확성' 은 담당 영역에 placeholder 를 명시한 축이다 (src/lib/judge-eval/registry.ts 의 agent_2). 그 축도 반응하지 않는다.

이 회차에서 BAD 를 실제로 결정하는 축은 따로 있다. 텍스트 가독성이 BAD 의 76.0% 를 단독으로 결정한다. 그 축 하나만 고치면 BAD 가 27.3% 에서 10.2% 로 내려간다. 내용 대치 정확성까지 고치면 3.4% 다 (회차 0831 의 stats.residual 실측).

4.4 judge BAD 를 실제로 결정하는 축은 텍스트 가독성이고, 그 축은 무엇으로 이루어져 있나#

4.3절에서 텍스트 가독성을 BAD 의 주 동인으로 적었다. 그래서 그 축이 무엇을 담는지 갈라 둔다.

명세. 축 프롬프트(src/lib/judge-eval/prompts/agent_5_text_readability.md)가 담당 영역을 다섯으로 적는다. "보이는 글자를 눈으로 읽어낼 수 있는가"만 본다. 내용과 슬롯 위치는 다른 축으로 넘긴다.

# 하위 결함 감점 기준
1 글자 겹침 겹쳐서 읽기 어려우면 BAD
2 물리적 잘림 경계선에서 글자 형태가 사라지면 BAD
3 오버플로우와 컨테이너 영역 이탈 한 줄 이상이 상자 밖으로 나가면 BAD. 글자가 읽혀도 BAD 다
4 이웃 요소 침범 텍스트나 그 배경 상자가 옆 카드 위로 겹쳐 얹히면 BAD
5 글자 크기와 색 대비 지나치게 작거나 대비가 매우 낮아 판독이 사실상 불가능하면 BAD
6 단어 내부 분절 한 단어가 두 줄로 쪼개지는 것. 한 군데는 ACCEPTABLE, 두 군데 이상이면 BAD

프롬프트가 명시적으로 빼는 것이 둘이다. 임시 문구와 더미는 평가에서 제외한다. 단어 경계에서의 줄바꿈은 정상이므로 감점하지 않는다.

실측. 회차 0831 에서 이 축이 BAD 인 1,636행의 판정 근거를 증상 태그로 갈랐다. 한 행에 여러 증상이 붙으므로 합이 100% 를 넘는다.

증상 축 BAD 1,636건 중 종합 BAD 2,154건 중 이 축이 단독 사유인 행
텍스트 겹침 50.1% (820) 38.1% 265건
넘침, 잘림 29.8% (488) 22.7% 209건
강제 줄바꿈 27.7% (453) 21.0% 226건
요소에 가림 24.7% (404) 18.8% 162건
색상 대비 7.2% (118) 5.5% 29건
글자 너무 작음 2.0% (33) 1.5% 6건
(정규식이 못 잡음) 7.5% (122)

마지막 열은 그 증상을 없앴을 때 종합 BAD 에서 확실히 빠지는 행의 수다. 다른 축도 BAD 인 행은 세지 않아 하한이다. 축 전체를 고쳤을 때의 17.1%p 가 상한이다.

증상이 한 행에 하나만 붙는 경우는 48.5% 뿐이다. 둘 이상이 35.5%, 셋 이상이 8.6% 다. 상위 넷 증상의 조합은 아래와 같다.

조합 축 BAD 중
텍스트 겹침 하나만 23.4%
(상위 넷이 하나도 없음) 12.8%
넘침, 잘림 하나만 11.4%
강제 줄바꿈 + 텍스트 겹침 9.0%
요소에 가림 + 텍스트 겹침 8.9%
요소에 가림 하나만 7.0%
강제 줄바꿈 + 넘침, 잘림 6.7%
강제 줄바꿈 하나만 6.3%

읽는 법이 둘이다.

첫째, 겹침 계열(텍스트 겹침, 요소에 가림)은 공간 부족에서 온다. 넘침 계열(넘침, 잘림, 강제 줄바꿈)은 글 분량 초과에서 온다. 그래서 둘은 같은 원인의 다른 증상일 수 있다. 실제로 함께 붙는 행이 많다.

둘째, 단독 사유 기준으로는 넷이 2.0에서 3.4%p 씩 고르게 나눠 갖는다. 하나를 고쳐 목표선에 닿는 구조가 아니다.

주의할 점이 둘이다. 증상 태그는 근거 문장에 정규식을 건 추정치이다. 7.5% 는 아예 못 잡았다. 회차 0831 은 5축에 gemini3.5 판이라, 위 명세 표(현행 10축 agent_5)와 세부 기준이 다를 수 있다. 축 이름과 담당 영역은 같다.

Placeholder Text Slot 개수와 judge 가 매기는 BAD 비율 사이에 상관이 없다. Hypothesis 1 을 기각한다. 이 판정은 운영 경로(comp-sign)에 한정된다. 새 경로는 7.4절에서 따로 본다.


5. Hypothesis 2 검증: Placeholder Text Slot이 있으면 사용자가 덜 고른다. 고른 뒤에는 차이가 없다#

5.1 Placeholder Text Slot과 사용자 채택률#

이 절의 채택률은 카드 채택률이다. 분모는 노출된 카드 한 장이다 (view_component와 action_component를 같은 search_id와 컴포넌트 uuid로 이은 쌍).

운영 Databricks 대시보드와 DA Report 01 의 약 40%는 하위검색 채택률이다. 분모는 그 하위검색에서 카드를 하나라도 골랐는가다. 2026-09-01부터 2026-09-15까지 하위검색 20,377건 중 채택이 있는 검색이 8,271건(40.59%)이고, 같은 창의 노출은 361,917건이라 하위검색당 카드가 평균 17.8장이다. 검색의 40.59%가 무언가를 골라도 카드 한 장 기준으로는 단자리가 된다.

노출 순위 Placeholder Text Slot 있음 없음 차이
0~2위 4.10% (8/195) 9.39% (75/799) −5.28%p
3~9위 1.96% (8/409) 3.84% (61/1,588) −1.89%p
10~19위 1.97% (7/356) 3.26% (52/1,594) −1.30%p
20위 밖 3.46% (16/462) 2.53% (54/2,132) +0.93%p
전체 2.74% (39/1,422) 3.96% (242/6,113) −1.22%p
상위 10위 안 2.65% (16/604) 5.70% (136/2,387) −3.05%p, z = −3.05

순위가 채택률을 세 배 넘게 가른다. 순위 한 칸을 한 층으로 둔 Mantel-Haenszel 로 보정했다.

odds ratio 0.688, 95% CI 0.487 ~ 0.972, z = −2.12. Placeholder Text Slot이 있으면 채택 odds 가 31% 낮다.

순위 층 안에서 처치 라벨을 2,000회 재배정한 permutation test에서, 관측 log-OR 이 −0.3742 이다. 귀무분포의 2.5~97.5% 구간이 −0.340 ~ +0.290 이다. 양측 p 는 0.0215 다.

같은 검색 안에서만 비교한 값은 5.08% 대 6.88% 이고, 차이가 −1.80%p, z = −1.77 이다. 정보를 주는 층이 153개뿐이다. 그 설계의 MDE가 2.84%p 다. 방향은 같고 유의 경계에 있다.

5.2 Placeholder Text Slot과 디자인 다운로드, 컴포넌트 잔존#

채택 다음 단계를 둘로 나눠 본다. 디자인 다운로드는 채택한 디자인이 7일 안에 내려받아졌는지다. 컴포넌트 잔존은 그 다운로드본에 그 컴포넌트가 아직 들어 있었는지다. 컴포넌트 잔존이 더 엄격한 기준이다. 사용자가 그 컴포넌트를 지웠거나 다른 것으로 바꿨으면 잔존하지 않는다.

표본은 3절의 '채택 표본' 이다. 채택 3,208쌍 중 대치 재현에 성공한 963쌍을 본다.

결과 Placeholder Text Slot 있음 없음 차이 odds ratio (95% CI) MDE
디자인 다운로드 7일 40.59% (69/170) 39.34% (312/793) +1.24%p 1.055 (0.753 ~ 1.477) 11.57%p
컴포넌트 잔존 7일 14.71% (25/170) 16.14% (128/793) −1.44%p 0.908 (0.572 ~ 1.440) 8.65%p

개수별 잔존율은 0개 16.14%(793건), 1개 15.62%(128건), 2개 이상 11.90%(42건)이다.

채택 이후 단계에서는 추가 손실이 관측되지 않는다. 다만 두 구간이 넓다. 컴포넌트 잔존의 구간 0.572~1.440 은 잔존 odds 를 43% 내리는 효과부터 44% 올리는 효과까지 포함한다. 4절처럼 동등성을 주장할 수는 없다. 있다고도 없다고도 말하지 않는 것이 정확하다.

노출을 분모로 둔 전체 효과도 같이 적는다. 순위 층화 Mantel-Haenszel 이고 표본은 3절의 '노출 표본' 이다.

결과 Placeholder Text Slot 있음 없음 차이 odds ratio (95% CI)
채택 2.74% 3.96% −1.22%p 0.688 (0.487 ~ 0.972)
디자인 다운로드 1.27% 1.52% −0.26%p 0.838 (0.502 ~ 1.397)
컴포넌트 잔존 0.49% 0.57% −0.08%p 0.883 (0.389 ~ 2.005)

노출 7,535건 가운데 컴포넌트 잔존까지 간 것이 42건, 0.56% 다. 이 기저율에서 이 표본으로 잡아낼 수 있는 최소 효과가 0.62%p, 상대 110% 다. 전체 효과의 점추정이 0.688 에서 0.883 으로 1 에 가까워진다. 손실이 사라져서가 아니라, 사건 수가 줄어 정밀도가 떨어졌기 때문이다.

Placeholder Text Slot이 주는 손실은 채택 단계에서 생긴다. 일단 사용자가 고르고 나면, Placeholder Text Slot이 있어도 다운로드와 컴포넌트 잔존이 더 나빠지지는 않는다. 사용자가 편집기에서 채워 넣거나 지우고 그대로 쓰는 것으로 보인다. 확인이 필요하다.

5.3 어느 role 의 자리가 남았을 때 채택률이 더 낮은가#

남은 자리의 role n 채택률
Desc 248 2.42%
Hl 618 2.59%
Subtitle 585 3.08%
(전체) 7,535 3.73%

Title 과 default 는 표본이 없어 판정하지 않는다.

5.4 성향점수로 다시 재면 같은 답이 나온다#

5.1절의 odds ratio 0.688 은 노출 순위 하나만 보정한 값이다. 그래서 남는 의심이 있었다. Placeholder Text Slot 이 생기는 결과는 애초에 그 입력에 덜 맞는 컴포넌트가 아닌가. 덜 맞아서 글이 넘치고, 넘쳐서 사용자가 안 고른 것이라면, 범인은 남은 자리가 아니라 부적합이다.

그래서 글 넘침을 따로 재서 공변량에 넣고 성향점수로 균형을 맞춘 뒤 다시 봤다(2026-09-20).

넘침을 재는 방법이 중요하다. judge 판정문에서 뽑으면 안 되고(2.3절), slots.lineSurplus 도 쓸 수 없다. 이름과 달리 placeholder + 비움 개수라(audit.ts) 처치와 같은 값이기 때문이다. 대신 시그니처의 용량(lines, charsPerLine)과 대치 후 실제 글을 predictLines 로 맞대 직접 셌다. 둘은 실제로 다른 변량이다. 아래 표본 8,398행에서 이렇게 갈린다.

n 비율
넘침만 있다 5,435 64.7%
남은 자리만 있다 237 2.8%
둘 다 있다 1,296 15.4%
둘 다 없다 1,430 17.0%

표본은 demand_exposure_events 와 demand_units(2026-08-01 ~ 09-01)에서 뽑았다. 노출 21,924행 가운데 대치를 재현한 8,398행, 검색 437개, 채택 283건이다.

방법 보정한 것 odds ratio 95% CI
보정 없음 없음 0.665 0.469 ~ 0.944
5.1절 Mantel-Haenszel 노출 순위 0.688 0.487 ~ 0.972
IPTW (성향점수) 순위, 줄 넘침, 글자 넘침, 자리 수, 글 길이 0.663 0.402 ~ 0.981 (검색 군집 bootstrap)
1:1 매칭 (caliper 0.2 SD) 같음 0.719 0.443 ~ 1.166

넘침을 보정해도 값이 거의 움직이지 않는다(0.688 → 0.663). 부적합으로 설명되는 효과가 아니다. 공통 지지구간은 0.002 ~ 0.935 로 겹침이 넉넉하고, IPTW 뒤 SMD 는 여덟 공변량 가운데 일곱이 0.1 미만이다(가장 큰 것이 요약 줄 수 −0.158).

공변량 보정 전 SMD IPTW 후
노출 순위 −0.060 +0.043
줄 넘침 여부 +0.140 −0.016
줄 넘침 크기 +0.234 −0.061
글자 넘침 여부 +0.136 −0.032
글자 넘침 크기 +0.199 −0.027
텍스트 자리 수 +0.662 +0.057
요약 글자 수 +0.129 −0.092
요약 줄 수 +0.097 −0.158

1:1 매칭은 구간이 1 을 포함한다. 처치 1,533행 가운데 1,267행이 짝을 찾아, 분석에 쓰는 행이 8,398행에서 2,534행으로 줄기 때문이다. 대조군을 한 번씩만 쓰므로 정보가 버려진다. 방향은 같다. IPTW 를 주 추정으로 읽고 매칭은 방향 확인으로 읽는다.

⚠️ 공변량에 넣으면 안 되는 것 둘을 실측으로 확인했다. 둘 다 처치의 결정론적 함수라 positivity 를 위반한다. 넣으면 성향점수가 완전분리되어 공통 지지구간이 사라진다.

넣으면 안 되는 것 왜
채움 비율(filled / text_nodes) filled 가 placeholder 를 빼고 세므로 처치를 그대로 되읽는다
자리 수 − 요약 줄 수 이 값이 0 보다 큰 115건이 전부 처치군이다(100%). 대조군이 존재할 수 없다

⚠️ 기간이 5.1절과 다르다. 5.1절은 2026-09-01 ~ 09-14 이고 이 절은 2026-08-01 ~ 09-01 이다. 같은 기간으로 맞춰 다시 뽑으면 더 단단해진다. ../TODO.md 의 E2 절에 남긴다.

재현은 scripts/analyze-placeholder-propensity.py 이고, 처치와 공변량을 만드는 placeholder-overflow-count.ts 는 대치 엔진을 부르므로 앱 저장소에 둔다 (scripts/README.md).


Placeholder Text Slot이 있는 결과는 사용자가 덜 고른다. Hypothesis 2 를 채택한다. 다만 사용자가 한 번 고른 뒤의 다운로드와 컴포넌트 잔존에서는 추가 손실이 보이지 않는다(5.2절). 순위만 보정한 값과 넘침까지 보정한 값이 같으므로(5.4절), 이 손실은 컴포넌트 부적합이 아니라 남은 자리 자체에 붙어 있다고 읽는다.


6. Hypothesis 2 의 결과가 가짜가 아닌지 검증#

Placeholder Text Slot이 judge 에는 효과가 없고 채택에는 있다. 결론이 가설과 반대 방향이라 Hypothesis 2 를 넷으로 따로 검증했다.

6.1 대치 재현에 실패한 쌍이 결과와 상관되는가#

대치 재현에 실패한 쌍이 결과와 상관되면 노출 표본의 추정이 편향된다.

집단 n 채택률 평균 순위
재현 성공 7,535 3.73% 16.2
no-structure-match 7,843 3.74% 14.7
no-signature-in-pool 4,999 4.60% 12.5

가장 큰 실패 집단의 채택률이 성공 집단과 0.01%p 차이다. 두 번째 실패 집단은 0.87%p 높지만 순위도 앞선다.

채택 표본에서도 같은 점검을 했다.

집단 n 디자인 다운로드 컴포넌트 잔존
재현 성공 963 39.56% 15.89%
재현 실패 2,245 40.62% 16.21%

두 결과 모두 차이가 0.4%p 안이다. 결과 축에서의 선택 편향은 작다.

6.2 Placeholder Text Slot이 노출 순위에 쏠려 있는가#

순위 구간 Placeholder Text Slot 1개 이상 비율
0~2위 19.62%
3~9위 20.48%
10~19위 18.26%
20위 밖 17.81%

처치 비율이 순위 구간에 걸쳐 17.81~20.48% 로 평평하다. 순위는 이 자료에서 큰 교란이 아니다. 보정 전 −1.22%p 와 보정 후 odds ratio 0.688 에서 같은 결론을 읽을 수 있다.

6.3 negative control: 무관해야 할 결과에서도 효과가 나오는가#

결과 변수를 '그 검색이 다른 컴포넌트를 채택했는가' 로 바꿔 같은 분석을 돌렸다. Placeholder Text Slot이 남의 채택을 바꿀 이유가 없으므로 odds ratio 가 1 이어야 한다.

negative control odds ratio 0.965, 95% CI 0.859 ~ 1.084. 1 을 포함한다. 설계에서 가짜 연관이 나오지 않는다.

6.4 처치와 결과가 서로 다른 원천에서 나오는가#

처치는 대치 결과의 문자열에서 나온다. 결과는 GA 이벤트에서 나온다. 원천이 다르므로, 2.3절에서 폐기한 판정문 기반 측정과 달리 얽힐 경로가 없다.


7. Hypothesis 3 검증: 회차 27 의 judge BAD 개선은 실재하되 보고된 크기보다 작다#

4절에서 Hypothesis 1 을 기각했으므로 회차 27 의 개선을 다시 따져야 한다. 운영 경로에서 효과가 없는 처치가 새 경로에서 11%p 를 냈다면 둘 중 하나가 틀렸거나, 두 경로가 실제로 다르다.

7.0 이 절이 쓰는 회차가 무엇과 무엇을 견준 실험인가#

여기서 다루는 회차는 모두 판정 루프의 회차다. 판정 루프는 visual summary 결과를 운영 judge 와 같은 채점 조건으로 재현해, 코드를 한 군데 고칠 때마다 BAD 비율이 어떻게 변하는지 재는 반복 실험이다. 루프 코드는 aippt-prisonbreak main 의 scripts/judge-loop/ 에 있고, 절차는 mordor-wiki monitoring/judge-loop/README.md 가 적는다.

이 회차들은 새 경로(comp-frame)를 잰다. 4절과 5절의 측정은 운영 경로(comp-sign)다. 경로가 다르므로 수치를 직접 옮겨 견주지 않는다.

출처. 회차별 처치군 규칙은 aippt-prisonbreak main 의 scripts/judge-loop/rerank.ts, pickRank 함수가 받는 RerankSpec 옵션이다. 어느 회차가 어느 옵션을 썼는지는 production DB garbage_collector.judge_upload_runs 의 note 칼럼에 적혀 있다. 규칙 둘을 코드까지 따라간 기록은 rerank-rule-rounds-26-27.md 2절과 3절이고, 회차 목록의 출처는 류민님이 올린 Slack 스레드(2026-09-20)다.

회차 모수 대조군 처치군 규칙 RerankSpec 옵션 이 문서에서 쓰는 곳
26 원본 PERFECT 100건 V5 Placeholder Text Slot이 생기는 후보를 거른다(이진 배제) avoid_defects: ['slots.lineSurplus'] 7.3절
27 원본 PERFECT 100건 V5 Placeholder Text Slot이 가장 적은 후보를 고른다(상대 순위) min_unauthored: true 7.3절, 7.5절
30 일반 100건 V5 27 과 같은 규칙 min_unauthored: true 7.3절, 7.5절
21 1,000건 고정 세트 제품 순위 그대로 (처치군을 쓰지 않는다. 대조군만 7.4절의 기준선으로 쓴다) 해당 없음 7.4절, 9.3절

26 과 27 은 서로를 비교하는 A/B 가 아니다. 둘 다 대조군이 V5 이고 처치군만 다른 독립된 두 실험이다 (judge_upload_runs 의 note 로 확인했다. 26 은 A=V5, B=w3, 27 은 A=V5, B=w2). 두 회차의 기준선 숫자가 20% 와 27% 로 다른 것도 같은 100건을 두 번 따로 채점해서다.

회차 28 은 대조군이 V5 가 아니라 회차 27 의 규칙(w2) 자체다. 거기에 한 가지를 더 건 것(w4)이 처치군이다. 그래서 28 은 "27 규칙 위에 더 얹으면 더 나아지나" 를 묻는 실험이고, 7.3절과 같은 방식으로 세면 더 나아지지 않는다.

회차 28 n 대조군(w2) BAD 처치군(w4) BAD
컴포넌트 바뀜 27 5 6
그대로 73 14 14

컴포넌트가 바뀐 27건에서 5 대 6 이고, 그대로인 73건은 14 대 14 다. 회차 27 규칙 위에 얹은 기준은 이 모수에서 아무것도 더하지 않는다. 그래서 9.3절의 누적 계산에 28 을 넣지 않는다.

7.1 회차별 원시 판정 결과를 어디서 읽었나#

2026-09-20 에 회차별 원시 판정 결과가 production DB 의 garbage_collector 스키마에 있다는 것을 확인했다. 표 셋을 쓴다.

표 담긴 것 이 문서가 쓰는 칼럼
judge_upload_runs 회차 한 건당 한 행. 어떤 규칙을 A 와 B 에 걸었는지 note(7.0절의 옵션 대조)
judge_upload_rows 회차 안의 입력 한 건당 한 행 입력 식별자, 고른 컴포넌트(7.3절의 "바뀜" 판정)
judge_upload_results 행 하나에 대한 판정 결과 종합 등급(BAD 여부)

HTTP API 는 인증이 걸려 있지만 운영 readonly 접속으로는 읽힌다. 위치를 찾은 경위는 rerank-rule-rounds-26-27.md 4절에서 7절까지에 있다.

조회문은 scripts/judge-rounds-rerank.sql 에 있다. 2026-09-20 에 복원하면서 7.3절 표 여섯 줄을 전부 다시 만들어 값이 같은 것을 확인했다.

⚠️ ref 로 짝지을 때 함정이 있다. 회차 26, 27, 28, 30 은 A 와 B 의 같은 row_idx 가 같은 입력을 가리킨다(각각 100/100, 100/100, 100/100, 90/90 확인). 그러나 회차 22 와 23 은 그렇지 않다. 회차 22 는 437행 가운데 일치가 0건이고 회차 23 은 436행 가운데 13건뿐이다. 그 둘은 row_idx 가 아니라 case_key 로만 묶어야 한다. 같은 SQL 의 ⑤ 가 이 확인을 돌린다.

여기서 Slack 보고를 한 가지 정정한다. 회차 26 과 27 의 대조군은 제품 순위가 아니라 V5 다. 그리고 모수가 1,000건이 아니라 원본 컴포넌트가 PERFECT 인 100건이다(7.0절 표).

7.2 철회: 회차 27 에 건 산술 상한은 모수가 달랐다#

2026-09-20 에 이 문서는 "회차 27 규칙이 930건 중 47건(5.1%)의 1위만 바꾸므로 11%p 를 만들 수 없다"고 적었다. 철회한다. 그 계산은 모수가 다르다. 나는 일반 입력 1,000건에 제품 순위를 대조군으로 두고 쟀는데, 회차 27 은 원본 PERFECT 100건에 V5 를 대조군으로 둔다.

같은 회차의 원시 결과에서 직접 세면 회차 27 규칙은 100건 중 30건(30%)의 컴포넌트를 바꾼다. 5.1% 가 아니다. 상한 논증이 성립하지 않는다.

7.3 대조군과 처치군을 행 단위로 짝지으면 규칙의 몫과 잡음의 몫이 갈린다#

세 회차 모두 같은 입력에 대조군과 처치군을 짝으로 채점했다. 행마다 컴포넌트가 바뀌었는지로 갈라 본다. 컴포넌트가 그대로인 행에서 등급이 달라졌다면 그것은 규칙이 아니라 재판정 잡음이다.

회차 묶음 n 대조군 BAD 처치군 BAD 대조군만 BAD 처치군만 BAD
27 컴포넌트 바뀜 30 11 4 10 3
27 그대로 70 16 12 4 0
30 컴포넌트 바뀜 35 15 9 12 6
30 그대로 55 15 12 4 1
26 컴포넌트 바뀜 43 9 10 7 8
26 그대로 57 11 16 0 5

여기서 셋을 읽는다.

첫째, 회차 27 의 개선은 실재한다. 컴포넌트가 바뀐 30건에서 BAD 가 11 에서 4 로 줄었다. 바뀐 행만 놓고 McNemar 검정을 하면 10 대 3 이고 p 는 0.052 다. 회차 30 의 바뀐 행 12 대 6 과 합치면 22 대 9 이고 p 는 0.020 이다. 같은 방향이고 유의하다.

둘째, 보고된 11%p 가운데 4%p 는 규칙이 아니다. 회차 27 에서 컴포넌트가 그대로인 70건인데도 BAD 가 16 에서 12 로 줄었다. 같은 그림을 두 번 채점해 4건이 한 방향으로 뒤집힌 것이다. 규칙이 실제로 기여한 몫은 11건이 아니라 7건, 곧 7%p 로 보는 것이 맞다.

셋째, 회차 26 의 악화는 규칙 탓이 아니다. 컴포넌트가 바뀐 43건에서는 9 대 10 으로 거의 같다. 악화분은 컴포넌트가 그대로인 57건에서 11 에서 16 으로 늘어난 것이고, 이쪽은 대조군만 BAD 가 0건, B 만 BAD 가 5건이다. 같은 그림이 다섯 번 한 방향으로 뒤집혔다. 회차 26 과 27 의 부호가 반대였던 이유가 여기 있다. 규칙의 차이가 아니라 재판정 잡음이다.

재판정 잡음의 크기도 이 표에서 바로 나온다. 컴포넌트가 그대로인 행에서 등급이 뒤집힌 비율이 회차 27 은 4/70(5.7%), 회차 30 은 4/55 에서 1/55 를 뺀 3건(5.5%), 회차 26 은 5/57(8.8%) 이다. 류민님이 적은 1에서 4%p 보다 크다.

7.4 새 경로에서 Placeholder Text Slot과 judge BAD 의 관계: 방향은 맞고 확증은 못 했다#

회차 21 의 대조군은 1,000건 고정 세트에 제품 순위를 그대로 쓴 것이다. 그 세트로 comp-frame 검색을 재현해 단위마다 blank 와 unauthored_count 를 세고, 회차 21 의 대조군 판정 등급과 맞댔다.

재현이 정확히 맞는다. 926건 전부에서 내 재현이 고른 컴포넌트가 운영 판정이 쓴 컴포넌트와 같다 (926/926, 100%). BAD 비율도 32.1% 로 일치한다.

처치 있음 없음 차이 odds ratio (95% CI) MDE
Placeholder Text Slot 1개 이상 35.76% (54/151) 31.35% (243/775) +4.41%p 1.222 (0.849 ~ 1.760) 11.63%p
통째로 비는 자리 1개 이상 37.93% (22/58) 31.68% (275/868) +6.25%p 1.328 (0.770 ~ 2.289) 17.72%p

방향이 4절과 반대다. 운영 경로에서는 odds ratio 가 1.012 이고 equivalence bounds 안에 들어갔는데, 새 경로에서는 1.222 와 1.328 로 1 보다 크다. 다만 처치군이 151건과 58건뿐이라 신뢰구간이 넓고 유의하지 않다. 확증하려면 처치군이 지금의 다섯 배는 되어야 한다.

7.5 Hypothesis 3 판정#

Hypothesis 3 을 조건부로 채택한다. 회차 27 의 개선은 실재하고, Placeholder Text Slot을 줄인 것이 그 원인으로 보인다. 다만 세 가지를 함께 적는다.

  1. 크기가 보고된 11%p 가 아니라 약 7%p 다. 나머지 4%p 는 재판정 잡음이다(7.3절).
  2. 새 경로에 한정되고, 그 한정이 한시적일 수 있다. 운영 경로에서는 같은 처치가 judge 를 움직이지 않는다(4절). 1.1절이 가른 대로 새 경로는 자리가 통째로 비고 운영 경로는 문구로 덮이기 때문으로 본다. 7.4절의 방향이 이 해석과 맞지만 확증은 못 했다. ⚠️ 이 해석이 맞다면 MOR-2504 가 닫히는 순간 이 7%p 는 사라진다. 그 티켓이 새 경로의 미대치 텍스트를 전부 임시 문구로 바꾸기 때문이다. 문구로 덮이면 judge 가 넘어가고(4절이 운영 경로에서 실측한 것이 그것이다), 그러면 자리를 줄여도 judge BAD 는 안 내려간다. judge 점수를 근거로 이 규칙에 자원을 넣기로 정했다면, MOR-2504 이후 다시 재야 한다. 다만 5절이 보였듯 사용자 채택 쪽 이득은 남는다. 그쪽은 문구로 덮여도 손실이 생긴다.
  3. 모수가 원본 컴포넌트가 PERFECT 인 100건이다. 원본 품질에서 오는 BAD 를 걷어낸 집합이라 대치 단계의 효과가 크게 보이는 쪽이다. 일반 입력 100건인 회차 30 에서는 바뀐 행 15 에서 9 로 회차 27 보다 약하다.

V5 와의 관계도 정리해 둔다. 1,000건 기준선에서 before 대비 4.5%p 를 낸 것은 V5 이고, V5 의 규칙은 채움 하한 0.10 과 원래 폭 줄 넘침 최소다. Placeholder Text Slot 규칙이 아니다. 회차 27 은 그 V5 위에 Placeholder Text Slot 규칙을 얹어 7%p 를 더 얻은 것이다. 둘은 경쟁이 아니라 누적이다. (이 4.5%p 는 2026-09-20 에 고친 값이다. 앞서 적은 11.4%p 는 집계 단위 오류였다. 9.3절을 본다.)

8. 이 분석이 측정하지 않은 것#

이 보고서가 답하지 않는 것을 적는다.

  1. 다운로드와 잔존의 정밀도. 5.2절에서 측정했으나 구간이 넓다. 채택 조건부 잔존의 MDE가 8.65%p 이고, 노출 기준 전체 효과는 상대 110% 다. 있다고도 없다고도 말할 수 없는 구간이다. 표집을 15% 에서 50% 로, 기간을 5주에서 10주로 늘리면 채택 쌍이 약 10배가 된다.
  2. 새 경로에서 Placeholder Text Slot과 judge BAD 의 관계. 7.4절에서 쟀으나 처치군이 151건뿐이라 신뢰구간이 1 을 포함한다. 방향만 보이고 확증하지 못했다.
  3. 새 경로에서의 사용자 행동. 새 경로는 아직 사용자에게 나가지 않아 노출과 채택이 없다. 5절의 채택 수치는 전부 운영 경로에서 잰 것이다. 9.4절의 환산도 그 값을 쓴다.
  4. 절이 여럿인 요약. 검색의 10% 다. 절과 sub_search_id 의 귀속을 붙이지 않아 제외했다.
  5. 현행 운영 judge 와의 차이. 판정 표본이 쓴 회차 0831 은 2026-08 판이고 gemini3.5 에 5축이다. 현행 운영 judge 는 10축이다.
  6. MOR-2504 이후의 새 경로. 이 보고서의 새 경로 측정은 전부 자리가 통째로 비는 지금 상태에서 잰 것이다. 그 티켓이 미대치 텍스트를 전부 임시 문구로 바꾸면 새 경로의 증상이 운영 경로와 같아진다. 그때 다시 재야 하는 것이 셋이다. (a) 7절의 judge BAD 개선 약 7%p 가 남는가. 4절이 운영 경로에서 본 대로라면 사라질 수 있다(7.5절). (b) 7.4절의 odds ratio 1.222 가 어디로 가는가. (c) 임시 문구가 평균 6.34개 붙은 카드를 사용자가 어떻게 보는가. 운영 경로의 평균은 1.39개다(9.1절). 지금은 이 셋에 답할 자료가 없다.

9. 도입 판단: 새 경로를 쓰면 무엇이 얼마나 나아지나#

이 절은 앞 절들의 수치를 도입 판단에 쓰는 형태로 모은 것이다. 새로 잰 것은 없다.

9.1 새 경로가 없던 문제를 만드는가#

만들지 않는다. Placeholder Text Slot이 생기는 빈도는 두 경로가 비슷하다.

Placeholder Text Slot이 1개 이상인 비율 있을 때 평균
운영 경로, 사용자에게 노출된 결과 18.9% 1.39개
새 경로, 제품 순위 1위 16.6% 6.34개

다만 증상이 다르다. 운영 경로는 임시 문구로 덮고 새 경로는 자리째 빈다(1.1절). 그래서 같은 빈도라도 judge 는 새 경로에서만 감점한다(4절과 7.4절). 사용자 쪽은 운영 경로에서만 쟀다. 새 경로에서 사용자가 빈 카드를 어떻게 보는지는 재지 않았다. 8절에 적었다.

한 가지는 나빠지는 방향이다. Placeholder Text Slot이 있을 때의 개수가 새 경로에서 1.39개에서 6.34개로 는다. 자리째 비는 것이라 한 번 어긋나면 여러 자리가 함께 빈다.

⚠️ 이 절의 "증상이 다르다" 는 지금 상태이지 두 경로의 성질이 아니다. 새 경로가 자리째 비는 것은 버그이고 MOR-2504 로 고친다. 고치고 나면 새 경로도 임시 문구로 덮이므로 judge 쪽 차이는 사라질 것으로 본다. 빈도(16.6%)와 개수(6.34개)는 남는다. 다만 6.34개가 "빈 칸 6.34개" 가 아니라 "임시 문구 6.34개" 가 되므로, 사용자에게 보이는 모습은 운영 경로와 같아지고 개수만 네 배 많아진다. 그 상태를 사용자가 어떻게 보는지는 아무도 재지 않았다. 도입 판단에서 가장 큰 미지수다.

9.2 기존 문제의 어느 면을 고치나#

judge BAD 를 실제로 결정하는 축은 텍스트 가독성이고, 그 축이 BAD 의 76.0% 를 단독으로 결정한다(4.3절). 그 축은 겹침 계열과 넘침 계열로 갈린다(4.4절). 새 경로의 재순위 규칙이 고치는 것이 이 두 계열이다.

규칙 무엇을 거르나 어느 축을 고치나
V5 의 채움 하한 0.10 빈 페이지 디자인 완결성
V5 의 원래 폭 줄 넘침 최소 넘치는 글 텍스트 가독성
회차 27 의 Placeholder Text Slot 최소 자리째 비는 후보 내용 대치 정확성, 구조 적합성

앞의 둘이 1,000건 기준선에서 텍스트 가독성 BAD 를 180 에서 71 로, 디자인 완결성을 202 에서 119 로 줄였다.

9.3 얼마나 나아지나#

judge BAD 비율로 본 값이다. 모수와 대조군이 회차마다 다르므로 같은 회차 안의 차이만 읽는다.

운영 경로와 새 경로 + V5 의 비교는 회차 22 와 23 이다(1,000건을 둘로 나눈 것). 조회는 scripts/judge-rounds-rerank.sql ③ 이다.

무엇 모수 값
운영 경로(before) 공통 873 입력, 입력당 1행 28.4%
새 경로 + V5 같은 873 입력 23.9%
새 경로, 제품 순위 그대로 회차 21, 925 입력 32.1%
새 경로 + V5 (회차 21 안에서) 같은 925 입력 26.4%
새 경로 + V5 + Placeholder Text Slot 최소 원본 PERFECT 100건 위 대비 약 7%p 추가 개선(7.3절)
원본 컴포넌트 자체의 BAD (하한선) 100건 13.6%

운영 경로 28.4% 에서 새 경로 + V5 23.9% 로 4.5%p 다. 거기에 Placeholder Text Slot 규칙이 약 7%p 를 더한다. 둘은 서로 다른 것을 고치므로 누적으로 본다(7.5절). 목표선인 BAD 5% 미만에는 아직 닿지 않는다. 원본 컴포넌트 자체가 13.6% 이므로, 대치 단계를 완벽히 고쳐도 그 아래로는 내려가지 않는다.

⚠️ 이 표는 2026-09-20 에 고쳤다. 앞서 적은 "운영 경로 35.3% 에서 23.9% 로 11.4%p" 를 철회한다. 35.3% 는 실재하는 값이지만 집계 단위가 한쪽에만 유리했다. 운영 경로는 입력 하나에 판정행이 평균 1.496개이고 새 경로 + V5 는 1.000개다(같은 SQL 의 ④). "입력 안에 BAD 가 하나라도 있으면 BAD" 로 묶으면 운영 경로만 BAD 로 찍힐 기회를 1.5배 받는다. 그렇게 세면 35.3% 대 23.9% 가 되고, 양쪽을 입력당 1행으로 맞추면 28.4% 대 23.9% 다. 개선 폭은 11.4%p 가 아니라 4.5%p 다. 두 회차의 모수도 "같은 1,000건" 이 아니었다. 32.1% 는 회차 21(925 입력)에서 나온 값이고 23.9% 는 회차 22 와 23(873 입력)에서 나온 값이라 한 열에 세울 수 없다.

9.4 제품 지표로 환산하면 얼마인가#

노출 표본 7,535건에서 Placeholder Text Slot이 있는 1,422건(18.9%)의 카드 채택률이 2.74% 이고 없는 쪽이 3.96% 다. Placeholder Text Slot을 전부 없앴다고 두면 채택이 281건에서 298건이 된다. 이 환산은 카드 채택률이다. 하위검색 채택률 약 40%와 더하거나 바꾸어 읽지 않는다.

구간 지금 채택률 Placeholder Text Slot을 없앴을 때 차이
노출 전체 3.73% 3.96% +0.23%p, 상대 +6.2%
상위 10위 안 5.08% 5.70% +0.62%p, 상대 +12.1%

⚠️ 이 환산에 세 가지 단서를 단다.

  1. 관측 자료의 반사실이다. Placeholder Text Slot이 있는 결과와 없는 결과를 비교한 것이지, 같은 결과에서 Placeholder Text Slot만 없앤 것이 아니다. Placeholder Text Slot이 없는 컴포넌트는 애초에 그 입력에 더 잘 맞은 것일 수 있다. 순위를 층으로 두어 보정한 odds ratio 0.688 로 환산해도 처치군 기대 채택률이 2.76% 로 실측 2.74% 와 거의 같지만, 관측되지 않은 교란까지 걷어낸 값은 아니다.
  2. 운영 경로에서 잰 값이다. 새 경로에서 사용자 채택을 잰 적이 없다. 새 경로는 Placeholder Text Slot이 자리째 비어 더 눈에 띄므로 손실이 이보다 클 수 있다.
  3. 채택 이후 단계에는 효과가 없다(5.2절). 그래서 위 환산이 그대로 다운로드 증가로 이어지지는 않는다.

10. 남은 일#

항목은 ../TODO.md 의 E2 절에 등록했다. 할 일의 SSoT 는 그쪽이다(WRITING.md 5.3절). 여기에는 왜 필요한지와 비용만 적는다.

# 할 일 닫히는 것 비용
1 새 경로의 Placeholder Text Slot과 judge BAD 관계를 처치군 다섯 배로 다시 잰다 7.4절의 방향을 확증한다 중간. 판정 비용이 든다
2 회차 27 의 rerank.json 확인 채움 하한 같은 다른 문이 함께 걸렸는지 낮다
3 노출 표본을 2주 전수로 확대. 채택 3,500건 규모 검색 고정효과 설계 확정, 잔존 측정 중간이다
4 노출 표본의 재현 실패 37% 원인 규명 남은 선택 편향 중간이다

4번의 후보는 셋이다. 절 텍스트를 요약 전문으로 대신 쓴 것, 2026-09 이후 시그니처 재적재, MAX_MERGE_STEP = 1 이다.


11. 결론과 권고#

  1. judge 를 위해 Placeholder Text Slot을 없애자는 안은 근거를 잃었다. judge 는 Placeholder Text Slot에 반응하지 않는다 (odds ratio 1.012, 동등성 구간 안). 그렇게 해서 얻을 judge 점수가 없고 사용자 쪽에서는 잃을 것이 있다.
  2. 운영 경로에서 Placeholder Text Slot을 줄이는 근거는 judge 점수가 아니라 채택률이다. 상위 10위 안에서 5.70% 가 2.65% 로 내려간다. 손실이 채택 단계에서만 생기므로(5.2절), 고칠 자리도 검색 결과 카드가 보이는 시점이다. 편집기 쪽이 아니다.
  3. judge BAD 목표선 5% 미만은 Placeholder Text Slot으로 가는 길이 아니다. 텍스트 가독성이 BAD 의 76.0% 를 단독으로 결정한다. 그 축 하나로 27.3% 가 10.2% 가 된다. 내용 대치 정확성까지 고치면 3.4% 다. comp-frame 쪽도 같은 축을 가리킨다. 제품 1위의 가장 흔한 예측 결함이 slots.lineDeficit 41.0% 이다. 위키가 그 개선을 채움 하한과 원래 폭 기준에 귀속한다(7.5절). 다만 위키가 적은 크기 11.4%p 는 집계 단위 오류라 4.5%p 로 고쳤다(9.3절).
  4. judge 판정문에서 파생한 지표를 근거로 인용하지 않는다. 증상 태그와 residue 가 여기 해당한다. 검출률이 판정문 길이에 10배 끌려간다. 처치가 결과와 같은 문장에서 나온다(2.3절).
  5. 새 경로에 관한 권고는 MOR-2504 전까지만 유효하다. 이 보고서의 새 경로 측정은 전부 자리가 통째로 비는 지금 상태에서 잰 것이다. 그 티켓이 닫히면 새 경로도 임시 문구로 덮이고, 1번이 운영 경로에서 보인 대로 judge 가 자리에 반응하지 않게 된다. 7절이 잰 약 7%p 가 그때도 남는지 다시 재야 한다(8절 6번). 반대로 2번(채택률 근거)은 남는다. 문구로 덮여도 사용자 손실은 생긴다.

부록 A. Placeholder Text Slot을 재는 여섯 가지 방법을 견준 기록#

2.2절의 방법을 고르기까지 여섯 가지를 견줬다(2026-09-20). 다음에 비슷한 것을 잴 때 다시 본다.

# 경로 정확도 비용 판정
1 GA 이벤트 로그 , 0 없다
2 운영 검색 기록 표 component_search_request_log , 0 없다
3 운영 대치 재현 + 임시 문구 세기 높음(운영과 같은 엔진) 운영 readonly 조회만 주 경로
4 색인 표의 입력 무관 성분 하한만 조회 한 번 보조, 검산
5 judge 증상 태그 낮음(편향) 0 (이미 있다) 교차검증 전용
6 다운로드본에서 임시 문구 직접 검출 가장 높음 접근 권한 문제 열어 둘 것

A.1 GA 이벤트 로그에는 Placeholder Text Slot이 기록되지 않는다#

view_component 이벤트 69건을 통째로 받아 425개 칼럼 중 값이 있는 것을 전부 열거했다. 다음 22개가 전부다.

account_id · design_idx · design_page_idx · plan_type · session_engaged · source_template · team_idx
view_item_key · view_item_type · view_load_num · view_load_seq · p_date · up_user_id · up_web_version
sub_search_id · search_id · color_replacement_available · structure_filter_num
view_item_has_image · is_depth2 · is_split_applied · image_replacement_available

슬롯과 채움, 임시 문구를 담은 칼럼은 없다. elements_info, initial_element_count, current_element_count 는 전부 null 이고, 그 칼럼들은 꾸미기(view_decoration)와 다운로드(design_download_resource) 이벤트의 자리였다.

structure_filter_num 은 한때 "요약이 요구하는 자리 수"로 쓸 수 있을까 싶었으나 아니었다, 3일치 실측에서 70%가 null 이고 값이 있는 것의 대부분이 2 다(사용자가 고르는 구조 필터다). 요구량으로 못 쓴다.

A.2 운영 검색 기록 표에도 Placeholder Text Slot이 기록되지 않는다#

bronze.miridih_miricanvas_designcontent_template.component_search_request_log 의 칼럼은 idx · search_key · input_text · summary · created_date · updated_date · account_id · team_idx · search_date · status 열 개뿐이다. 원문과 요약은 남지만 어떤 컴포넌트에 어떻게 대치했는지는 남지 않는다.

A.3 운영 대치를 재현해 임시 문구를 센다: 이 보고서가 쓴 방법#

앞선 계획에서는 이 단계를 "judge-loop 를 돌리는 일"로 적었는데, 그보다 훨씬 가볍다. 판정도 그림도 필요 없고 필요한 것은 함수 하나다.

buildContentReplacement({ textTree, variant, textMap, textNodeRoles, sheetJson })
  → { sheetJson, nodeTextMap, nodeBboxMap, siblingGroups }

@miri-unicorn/miricanvas-iui-comp-sign (2.5.0, 이 저장소에 이미 설치돼 있다) 의 dist/build-replacement.d.ts 가 정본이다. 운영이 쓰는 그 엔진이고, 반환값 nodeTextMap 이 "어느 노드에 어떤 글이 들어갔나"다. 여기서 위 15개 문구와 같은 값을 세면 끝이다.

재료도 전부 이미 있는 길로 닿는다.

재료 어디서 상태
요약 트리 textTree · 검색 변형 variant 운영 요약을 prepareSearchQuery 에 통과 (src/lib/demand/search-sim.ts) ✅ 운영과 같은 경로
textMap · textNodeRoles 운영 readonly public.comp_signatures ✅ CANDIDATE_SQL 이 이미 읽는다
sheetJson 운영 readonly design_objects.design_object_sheet_json ✅ 노출 컴포넌트 13,682종만 받으면 된다
원문·요약 component_search_request_log ✅ 실측 조회 성공
노출·순위·채택·다운로드 GA ✅ 0단계

비용이 이전 추정보다 훨씬 작다. LLM 호출 0(요약을 만들지 않고 운영이 만든 것을 가져오므로), 그림 렌더링 0, 판정 0. 무거운 것은 sheetJson 13,682건 조회 하나뿐이고 이것은 한 번 받아 캐시하면 된다. 그래서 표본 3,000검색이 아니라 2주 전수 304,164 노출을 전부 재는 것도 현실적이다.

한 가지 성질을 이용하면 더 줄일 수도 있다. Placeholder Text Slot은 (컴포넌트 × 요약 단위)의 함수이고 순위나 사용자와 무관하다. 노출 304,164건에 들어 있는 서로 다른 (컴포넌트, 절) 쌍의 수만큼만 계산하면 된다.

⚠️ 검증을 먼저 통과해야 한다. 재현한 nodeTextMap 이 운영 화면과 같은지 100건을 눈으로 대조한다. comp-sign 시그니처는 DO 하나가 여러 벌을 가질 수 있고(실서빙 표본의 16%), 운영이 어느 벌로 판정했는지에 따라 임시 문구 자리가 달라진다(search-sim.ts 주석의 run 9 seq 69 와 70 사례). 벌 선택 규칙이 운영과 같은지가 검증의 핵심이다.

A.4 색인 표에 적힌 입력 무관 성분을 쓴다: 하한을 주는 보조 방법#

Placeholder Text Slot unauthored_count 는 세 성분의 합이다, ⑴ 빈 슬롯 ⑵ 경계 안 잔여 ⑶ 경계 밖 원문. 이 가운데 ⑵⑶ 은 입력과 무관한 컴포넌트의 상수이고, 색인 표에 이미 숫자로 적혀 있다.

iui.comp_frames (운영 readonly, packages/comp-frame-index/sql/build-comp-frames.sql 이 스펙 정본):

칼럼 뜻
inside_unbound_count 경계 안인데 어떤 슬롯으로도 물리지 않아 대치되지 않는 Text 노드 수, "화면이 임시 문구로 채우는 자리"
outside_text_count 부분 매치에서 경계 밖에 남는 Text 노드 수
cap_lines_arr 슬롯 전위 순서의 용량 배열 → 길이가 곧 슬롯 수
unit_count · items_per_unit_min/max 유닛 수와 유닛당 항목 슬롯 수
preserve_text_nodes 원문이 vs·SWOT·숫자+단위 등이라 일부러 대치하지 않는 자리

inside_unbound_count + outside_text_count 는 조회 한 번으로 전수에 붙는, 입력을 보지 않아도 정해지는 Placeholder Text Slot의 하한이다. 부록 A.3 의 검산으로 쓰고, 부록 A.3 이 막히면 이것만으로도 처치 변이가 만들어진다.

⚠️ 단 comp_frames 는 새 경로(comp-frame) 의 색인이다. 운영(comp-sign)이 같은 자리를 같게 남기는지는 확인해야 한다. 그리고 GC 게이트 통과 + IIS 추출된 72,768개만 덮으므로, 운영 노출 컴포넌트 13,682종 중 몇 종이 덮이는지 먼저 세야 한다.

A.5 judge 증상 태그를 쓴다: 편향이 있어 교차검증으로만 쓴다#

src/lib/judge-upload/symptoms.ts 의 태그 14종 가운데 셋이 정확히 이 문제다.

태그 정규식이 잡는 말
빈 슬롯 방치 빈 (슬롯|칸|자리) · 비어 있는 (슬롯|칸) · 방치 · 채워지지 (않|못)
더미 문구 잔존 더미 · 플레이스홀더 · placeholder · 입력해 주세요 · 강조 입력 · 설명 입력 · 부제목 입력
슬롯 부족 슬롯이 (부족|없|전혀) · 수용력 · 자리가 모자

그리고 judge 축 자체가 이것을 본다, agent_2 내용 대치 정확성 의 담당 영역이 summary 내용이 올바른 슬롯·위치에 매핑되었는지(누락·오배치·placeholder) 이고, agent_1 구조 적합성 이 슬롯의 수용력(과부족) 이다(src/lib/judge-eval/registry.ts).

그런데 처치 변수로 쓰면 안 된다. 증상 태그는 BAD 로 찍힌 축의 근거 문장에서만 뽑는다 (symptomTagsOf). Placeholder Text Slot이 있어도 judge 가 GOOD 을 주면 태그가 안 붙는다. 즉 이 태그는 "Placeholder Text Slot이 있다"가 아니라 "Placeholder Text Slot 때문에 BAD 를 받았다"를 재는 것이고, 결과 변수와 기계적으로 엮인다. 파일 자체가 "원인 분류가 아니라 증상 태그이고 비율을 결론으로 인용하지 않는다"고 못 박고 있다.

쓸 자리는 하나다, 부록 A.3 의 재현이 맞는지 검산. 재현이 "Placeholder Text Slot 3개"라고 한 결과에 judge 가 더미 문구 잔존 태그를 붙였는지 대조한다.

한편 이미 돌아간 자료가 있을 수 있다. aippt-prisonbreak 의 scripts/judge-agreement-sample.ts (MOR-2334) 는 수요 run 의 실측 이벤트에서 채택 / 노출-미채택 / 다운로드 잔존 / 채택-미다운로드 네 집단을 층화 표집해 judge 에 먹이도록 만들어졌고, judge-ingest.ts --meta-cols 가 rank · adopted · downloaded 를 행 메타로 함께 적재한다. 그 회차가 이미 있으면, 집단별 빈 슬롯 방치 와 더미 문구 잔존 태그 비율을 오늘 바로 볼 수 있다. 표본이 작고 위의 편향이 그대로라 결론은 못 내지만, 방향과 효과 크기의 눈대중은 준다. 판정 기록은 garbage_collector.judge_* 표에 있고 화면은 10.122.9.74:3000 이다(연결은 되고 인증이 필요하다, 실측 확인).

A.6 사용자가 다운로드한 디자인에서 임시 문구를 직접 찾는다: 아직 열어 둔 방법#

가장 좋은 자료다. 임시 문구가 닫힌 15개 문자열이므로, 사용자가 다운로드한 디자인의 텍스트에 "제목 입력" 같은 값이 남아 있는지를 보면 대치 재현조차 필요 없이

  • 그 컴포넌트에 Placeholder Text Slot이 있었는가 (그리고 몇 개였는가)
  • 사용자가 그것을 지웠는가, 채웠는가, 그냥 두고 냈는가

를 한 번에 관측한다. 마지막 항목은 어떤 경로로도 대신 못 얻는 값이고, "빈칸을 날려 버려도 되는가"라는 원래 질문에 가장 가깝게 답한다.

막는 것은 자료 접근이다. 이 저장소가 Databricks 에서 쓰는 표는 여덟 개뿐이고 (events_except_miridih, designresource_file, template, template_item, template_page, template_type_link, component_search_request_log, components), 사용자 디자인 콘텐츠는 그 안에 없다. 디자인 서비스 쪽에 물어야 한다. 물어볼 가치가 큰 질문이다.


부록 B. 분석 설계를 고른 이유와 기각한 안#

B.1 성향점수보다 검색 절 고정효과를 주 설계로 둔 이유#

위 교란 ①~④ 가운데 ②③④ 는 전부 검색 단위의 성질이다. 한 검색 안에서는 원문도, 요약도, 사용자도, 시점도 전부 같다. 그러니 성향점수로 맞출 것 없이 같은 검색 안에서 보여 준 컴포넌트끼리만 비교하면 그 셋이 통째로 사라진다. 남는 것은 순위 하나뿐이고, 순위는 관측된다.

방법은 검색 절(sub_search_id)을 층으로 둔 조건부 로지스틱 회귀다. 층 안에서 채택이 적어도 하나 있고 전부는 아닌 층만 정보를 준다. 실측했다.

항목 2주 실측
검색 절(층) 18,381개 · 절당 평균 16.5개 노출 · 평균 채택 0.62개
정보를 주는 층 7,258개
그 층 안의 노출 행 155,263행

15만 행이면 충분하다. 이쪽을 주 설계로 삼고, 성향점수는 확인용 보조 설계로 둔다. 둘이 같은 답을 주면 결론이 훨씬 단단해지고, 갈리면 검색 단위 교란이 실제로 컸다는 뜻이라 조건부 로지스틱 쪽을 믿는다.

조건부 로지스틱이 못 하는 것도 분명하다. 잔존 분석에는 못 쓴다, 한 검색에서 채택이 대개 하나라 층 안에 잔존 변이가 거의 없다. 잔존은 성향점수로 간다.

B.3 B.1 과 B.2 는 계획이다. 무엇이 실제로 돌았는지 여기 적는다#

⚠️ B.1 과 B.2 는 설계 검토 기록이고, 거기 적힌 대로 돌아간 것이 아니다. 특히 조건부 로지스틱 회귀는 한 번도 돌지 않았다. B.1 이 "주 설계" 로 삼겠다고 적은 그 방법이다. 5.1절의 "같은 검색 안에서만 비교한 값" 이 가장 가까운 시도인데, 정보를 주는 층이 153개뿐이라 B.1 표가 예상한 7,258층과 두 자릿수 차이다.

실제로 돌아간 것은 둘이다.

무엇 언제 보정한 것 코드
순위 층화 Mantel-Haenszel 과 permutation test 2026-09-20 노출 순위 scripts/analyze-placeholder-effect.py
성향점수 IPTW 와 1:1 매칭 2026-09-20 순위, 줄 넘침, 글자 넘침, 자리 수, 글 길이 scripts/analyze-placeholder-propensity.py

성향점수는 B.1 이 "확인용 보조 설계" 로 두겠다고 한 그것이고, 5.4절에 결과가 있다. 주 설계와 보조 설계가 같은 답을 줬다(0.688 대 0.663). B.1 이 기대한 대로다.

성향점수를 돌리면서 표본 만드는 길이 B.1 보다 짧아졌다. Databricks 가 필요 없다. demand_exposure_events 가 노출과 순위와 채택을, demand_units.sub_summary 가 대치에 넣을 요약 글을 준다. 절이 하나인 검색으로 제한하면 run_id = 4(2026-08-01 ~ 09-01) 기준 노출 417,664행, 컴포넌트 13,804개, 채택 17,634건까지 쓸 수 있다. 5.4절은 그 가운데 검색의 50/1000 을 뽑아 썼다.

처치 변수는 저장돼 있지 않다. Placeholder Text Slot 수를 담은 칼럼은 운영 DB 어디에도 없다 (garbage_collector 전량을 blank|placeholder|unauthor|fill|residu|surplus|overflow|defect|slot 으로 훑었다). 대치를 재현해야만 나오고, 재현 엔진 src/lib/demand/logic-replace 는 저장소 main 브랜치에 있다. 작업 브랜치에 없으면 git worktree 로 origin/main 을 따로 꺼내 돌린다.

B.2 검토하고 기각한 안#

  • judge 로 대신 재기. 이미 하고 있고, 그 수가 사용자 행동과 다를 수 있다는 것이 이 문서의 출발점이다.
  • 단순 교차표(빈칸 유무 × 채택률). 순위 하나만으로도 세 배 차이가 나므로 교란이 효과를 덮는다.
  • 전수 대치 재현. 14,202검색 × 최대 50후보. 검정력 계산상 3,000검색이면 채택 분석에 충분하다.
  • 회귀 보정만(성향점수 없이). 해도 되지만 균형이 맞았는지 눈으로 확인할 수 없다. 성향점수를 쓰는 실익은 추정값이 아니라 SMD 표로 균형을 보여 줄 수 있다는 데 있다.
  • 디자인 다운로드를 주 결과로 쓰기. 디자인 하나에 컴포넌트가 여럿이라 한 컴포넌트의 빈칸에 거의 반응하지 않는다. 잔존을 쓴다.

부록 C. 이 분석이 두 번 틀렸던 기록#

2026-09-20 에 낸 1차 결론을 같은 날 철회했다. 무엇을 잘못했는지 남긴다. WRITING.md 4절이 정한 대로 기록은 고쳐 쓰지 않고 덧붙인다.

C.1 첫 번째 철회: judge 판정문에서 뽑은 지표#

철회한 결론. "Placeholder Text Slot은 judge BAD 를 부르지 않는다"를 judge 판정문에서 뽑은 지표로 냈다. 같은 입력 안에서 잔존 후보의 BAD 가 26.56%, 비잔존이 28.77% 라는 수치였다.

왜 틀렸나. 그 지표가 디자인이 아니라 판정문의 성질을 쟀다. 2.3절의 세 진단이 그것을 보여 준다. 특히 잔존으로 잡힌 PERFECT 행의 77.5% 가 '감점 대상 아님' 문장을 동반했다. 감점하지 않겠다고 적은 행이 잔존으로 잡히니 BAD 가 낮은 것이 당연했다. 처치와 결과가 같은 문장에서 나온 동어반복이었다.

어떻게 고쳤나. 2.2절로 갈아탔다. 대치를 재현해 임시 문구를 직접 세면 판정문과 무관하게 개수가 나온다. 갈아탄 뒤의 4절 결론은 방향이 같지만 근거가 다르고, 이번에는 equivalence bounds 안에 들어간다.

같은 함정이 남아 있는 자리. /judge-stats 화면의 증상 태그 비율이 같은 방식이다. 결론의 근거로 인용하지 않는다(11절 4번).

C.2 두 번째 철회: 회차 27 에 건 산술 상한#

철회한 결론. "회차 27 규칙은 930건 중 47건(5.1%)만 바꾸므로 11%p 를 만들 수 없다."

왜 틀렸나. 모수와 대조군을 확인하지 않고 내 고정 세트의 수치를 그 회차에 옮겼다. 회차 27 은 원본 PERFECT 100건에 V5 를 대조군으로 둔 실험이고, 내 계산은 일반 1,000건에 제품 순위를 대조군으로 둔 것이다. 그 회차의 원시 결과를 직접 세면 규칙이 바꾸는 행은 30% 다.

어떻게 고쳤나. production DB 의 garbage_collector.judge_upload_* 에서 행 단위 판정을 받아 7.3절의 짝 비교로 다시 냈다. 결론이 기각에서 조건부 채택으로 바뀌었다.

교훈. 남이 보고한 수치에 내 자료로 계산한 상한을 걸기 전에, 그 보고의 모수와 대조군이 무엇인지 먼저 확인한다. 이번에는 둘 다 달랐다.

↑ 목차로 · 4부 머리로

16장Placeholder 효과 · 공유용 요약 파생위 보고서의 요약판

원문 evaluation/placeholder-effect-brief.md GitHub 에서 보기 7 KB 원문 제목 「Placeholder Text Slot 효과: 스쿼드 공유용 요약」

이 문서의 역할 스쿼드와 Confluence 독자가 숫자와 결정만 먼저 읽게 한다. 측정 방법, 표본, 철회 기록은 placeholder-effect.md 가 SSoT다. 오너 정원석. 상태 DRAFT. 최종 갱신 2026-09-20 근거 PR mordor-wiki #249 Confluence 같은 본문을 분석 리포트 페이지에 올린다. 수치를 고치면 이 파일과 SSoT를 먼저 고친다.

한 줄#

운영 중인 내용시각화에서, 대치 후 남은 글 자리(Placeholder Text Slot)는 채점기(judge) 점수를 바꾸지 않는다. 같은 자리가 사용자가 검색 카드를 고를 확률은 떨어뜨린다. 자리를 줄이는 일은 채점 점수를 맞추는 일이 아니라, 검색 결과 카드 채택률을 올리는 일이다. 대시보드의 약 40%는 하위검색 채택률이라 이 페이지의 3~4%와 분모가 다르다.

결론#

지금 사용자에게 나가는 경로에서 할 일은 이것이다.

  1. judge BAD를 내리려고 남은 자리를 줄이지 않는다. 임시 문구로 덮이면 채점기는 자리에 반응하지 않는다 (odds ratio 1.012). BAD의 76.0%는 텍스트 가독성(겹침, 넘침)이 단독으로 결정한다.
  2. 남은 자리를 줄이는 이유는 카드 채택률이다. 자리가 있으면 고를 odds가 약 31% 낮다. 상위 10위 안에서 5.70%가 2.65%로 내려간다. 고칠 시점은 검색 결과 카드다. 편집기가 아니다.
  3. 그 손실은 글이 넘쳐서 안 맞는 컴포넌트 탓이 아니다. 넘침을 보정해도 odds ratio는 0.688에서 0.663으로 거의 안 움직인다.
  4. judge 판정문에서 뽑은 증상 태그와 residue를 결론 근거로 쓰지 않는다. 검출률이 판정문 길이에 최대 10배 끌려간다.
  5. 새 경로의 judge 개선 약 7%p는 지금 빈 칸 버그 상태에서 잰 값이다. MOR-2504 로 임시 문구를 씌운 뒤에 다시 잰다. 채택률 근거는 그 전에도 성립한다.
  6. 자리를 전부 없애도 카드 채택률 상한은 상대 +6.2%(상위 10위 안 +12.1%)다. 상한선이고, 다운로드로 바로 이어진다고 읽지 않는다.

용어 (이 페이지만)#

내용시각화는 사용자가 쓴 글을 템플릿 컴포넌트의 글 자리에 끼워 넣어 한 장의 그림을 만든다. 이 끼워 넣기를 대치라고 부른다. 자리가 글보다 많으면 채우지 못한 자리가 남는다. 그 남은 자리가 Placeholder Text Slot이다.

지금 사용자에게 나가는 경로를 운영 경로(comp-sign)라고 한다. 못 채운 자리는 "제목을 입력해주세요." 같은 임시 문구로 덮인다. 교체하려고 만드는 경로를 새 경로(comp-frame)라고 한다. 새 경로는 지금 자리가 통째로 빈다. 그 빈 칸은 설계가 아니라 버그이고 MOR-2504 로 고친다.

judge는 결과 그림을 축마다 채점하는 LLM 채점기다. BAD는 그 결과의 종합 등급이 불합격인 비율이다.

채택률은 두 가지다. 이 페이지의 3~4%는 카드 한 장이다. 검색 결과 목록에 카드가 여러 장 보이므로, 그 검색에서 무언가를 골랐는가(하위검색 채택률)는 같은 기능에서 약 40%다. 둘 다 맞다. 분모만 다르다. 정의는 placeholder-effect.md 1.2절과 5.1절이다. 하위검색 쪽 숫자의 출처는 da-report-01-candidate-pool.md 다.

이름 분모 이 페이지 운영 대시보드 / DA Report 01
카드 채택률 노출된 카드 한 장 (view_component 한 쌍) 3.73% (281 / 7,535) 이 정의로 올리지 않음
하위검색 채택률 그 하위검색에서 카드를 하나라도 골랐는가 이 페이지에서 안 씀 40.59% (8,271 / 20,377)

검색 하나에 카드가 평균 17.8장 보인다 (노출 361,917 / 하위검색 20,377, 2026-09-01부터 2026-09-15). 40.59% / 17.8 ≈ 2.3% 이고, 한 검색에서 카드를 여러 장 고르면 관측된 카드 채택률 3.73%와 맞는다.

결과 1. 사용자에게 나가는 카드 (운영 경로)#

기간은 2026-09-01부터 2026-09-14까지다. Google Analytics 노출과 채택을 검색 8%로 뽑고, 운영과 같은 대치 엔진으로 임시 문구를 직접 셌다. 노출 7,535쌍 가운데 채택은 281건이다. 아래 비율은 카드 채택률이다.

무엇 값
남은 자리가 1개 이상인 노출 18.9% (있을 때 평균 1.39개)
카드 채택률, 자리 없음 3.96% (242 / 6,113)
카드 채택률, 자리 있음 2.74% (39 / 1,422)
차이 −1.22%p
상위 10위 안, 자리 없음 5.70%
상위 10위 안, 자리 있음 2.65%
차이 −3.05%p

순위가 채택률을 세 배 넘게 가른다. 0~2위는 9.39%, 20위 밖은 2.53%다. 그래서 순위 한 칸을 한 층으로 두고 비교했다.

자리가 있으면 채택 가능성이 약 31% 낮다. 층화 odds ratio 0.688, 95% 구간 0.487~0.972, permutation test p = 0.022.

같은 검색 안에서만 비교해도 방향은 같다 (5.08% 대 6.88%). 정보를 주는 층이 153개뿐이라 유의 경계에 있다.

글이 넘쳐서 안 맞는 컴포넌트여서가 아니다. 시그니처 용량과 대치 후 글로 넘침을 따로 재서 성향점수(IPTW)로 보정해도 odds ratio는 0.688에서 0.663으로 거의 안 움직인다 (95% 구간 0.402~0.981). 표본은 2026-08-01부터 2026-09-01까지 노출 8,398행이다. 5.1절 기간과 날짜가 겹치지 않아, 같은 기간으로 다시 뽑는 일을 TODO.md E2-7에 남겼다.

사용자가 한 번 고른 뒤에는 디자인 다운로드와 컴포넌트 잔존에서 추가 손실이 안 보인다. 잔존 odds ratio 0.908, 구간 0.572~1.440이라 없다고 단정하지는 않는다. 손실이 생기는 자리는 검색 카드가 보이는 시점이다.

가짜 연관인지도 봤다. 대치 재현에 실패한 쌍의 채택률이 성공 쌍과 0.01%p 차이다. 다른 컴포넌트가 채택됐는지를 결과로 바꾼 negative control은 odds ratio 0.965 (구간 0.859~1.084)로 1을 포함한다.

자리를 전부 없앴다고 두는 상한선은 이렇다. 카드 채택률이 노출 전체 3.73%에서 3.96% (상대 +6.2%). 상위 10위 안 5.08%에서 5.70% (상대 +12.1%). 관측된 자리를 없는 쪽 채택률로 바꾼 계산이고, 같은 카드에서 자리만 지운 실험이 아니다. 다운로드 증가로 바로 이어진다고 읽지 않는다. 하위검색 채택률 약 40%를 이 상한선과 더하지 않는다.

결과 2. 채점기 (운영 경로)#

판정 회차 0831 (2026-08, 디벨롭 sigv2 검증)에서 같은 요약에 붙은 후보끼리만 견줬다. 대치 재현에 성공한 7,238행이다.

남은 자리 n BAD
0개 5,077 27.02%
1개 1,414 26.45%
2개 275 25.82%
3개 197 22.34%
4개 이상 275 26.18%

임시 문구 1개 이상의 odds ratio는 1.012, 95% 구간 0.879~1.166이다. permutation test p = 0.878이다. 이 구간은 동등성 한계 [0.80, 1.25] 안에 들어간다. 못 찾아서 효과가 없는 것이 아니라, BAD를 17% 이상 올리거나 12% 이상 내리는 효과는 이 자료에서 배제된다.

담당 영역에 placeholder를 명시한 축(내용 대치 정확성)도 차이가 −0.22%p다.

이 회차에서 BAD를 실제로 결정하는 축은 텍스트 가독성이다. 종합 BAD의 76.0%를 이 축이 단독으로 결정한다. 이 축만 고치면 BAD가 27.3%에서 10.2%로 내려간다. 겹침, 넘침, 잘림, 강제 줄바꿈이 그 축의 주 증상이다. 임시 문구는 채점 프롬프트가 감점에서 뺀다.

그래서 운영 경로에서 "judge BAD를 내리려고 Placeholder Text Slot을 없애자"는 안은 근거가 없다.

결과 3. 새 경로와 재순위 실험#

새 경로는 아직 사용자에게 나가지 않는다. 아래 채택 숫자는 전부 운영 경로다. 새 경로 숫자는 자리가 통째로 비는 지금 상태에서 잰 것이다.

무엇 값
운영, 자리 1개 이상 18.9%, 있을 때 평균 1.39개
새 경로 제품 1위, 자리 1개 이상 16.6%, 있을 때 평균 6.34개
운영 BAD (공통 873 입력, 입력당 1행) 28.4%
새 경로 + V5 23.9%
차이 −4.5%p

V5는 채움 하한 0.10과 원래 폭 줄 넘침 최소다. Placeholder Text Slot 규칙이 아니다.

철회. 예전에 운영 35.3%에서 23.9%로 −11.4%p라고 적은 값은 집계 단위가 한쪽에만 유리했다. 운영은 입력 하나에 판정행이 평균 1.496개이고 새 경로 + V5는 1.000개다. "입력 안에 BAD가 하나라도 있으면 BAD"로 묶으면 운영만 BAD로 찍힐 기회를 1.5배 받는다. 양쪽을 입력당 1행으로 맞추면 −4.5%p다.

회차 27은 원본 컴포넌트가 PERFECT인 100건에서, 대조군 V5 위에 "자리가 가장 적은 후보를 고르는" 규칙을 얹은 실험이다. Slack에 보고된 BAD 27%에서 16% (11%p) 가운데 규칙의 몫은 약 7%p다. 컴포넌트가 그대로인 70건에서도 BAD가 16에서 12로 줄었고, 그 4%p는 같은 그림을 다시 채점할 때 생기는 잡음이다. 컴포넌트가 바뀐 30건만 보면 BAD 11에서 4다.

회차 26(자리 남는 후보를 이진으로 거름)의 악화는 규칙 탓이 아니다. 컴포넌트가 바뀐 43건은 9 대 10으로 거의 같고, 악화분은 그대로인 57건에서 같은 그림이 다섯 번 뒤집힌 것이다.

회차 28은 회차 27 규칙 위에 한 가지를 더 얹은 실험이다. 바뀐 27건에서 5 대 6, 그대로인 73건에서 14 대 14라 더하지 않는다.

새 경로에서 자리 있음의 BAD odds ratio는 1.222 (구간 0.849~1.760)다. 방향은 운영과 반대이나 처치군 151건이라 확증하지 못했다.

MOR-2504 가 닫히면 새 경로도 임시 문구로 덮인다. 운영 경로에서 이미 잰 대로 문구로 덮이면 judge는 자리에 반응하지 않는다. 그때 회차 27의 약 7%p가 남는지 다시 재야 한다. 채택률 쪽 이득은 문구로 덮여도 남는다. 다만 새 경로는 자리가 있을 때 개수가 평균 6.34개라, 임시 문구가 네 배 붙은 카드를 사용자가 어떻게 보는지는 아직 재지 않았다.

이 요약이 답하지 않는 것#

  • 새 경로에서의 실제 사용자 채택 (아직 서빙하지 않음)
  • MOR-2504 이후의 새 경로
  • 다운로드와 잔존의 정밀한 효과 (구간이 넓음)
  • 절이 여럿인 요약 (검색의 약 10%, 제외)
  • 현행 10축 운영 judge와의 차이 (판정 표본은 5축 gemini3.5)

후속 할 일은 TODO.md E2다.

출처#

↑ 목차로 · 4부 머리로

17장judge 재측정 · 상세 보고서 정본운영 요약 방식과의 비교 (2026-09-27)

원문 evaluation/judge-rerun-0927.md GitHub 에서 보기 9 KB 원문 제목 「운영 요약 방식과 Visual Summary의 judge 재측정: 상세 보고서 (2026-09-27)」

문서 안내 같은 운영 입력 100건으로 운영 markdown summary 방식과 Visual Summary(dev 배포판)의 결과를 각각 만들고, 같은 조건의 judge로 BAD 비율을 비교했다. 이번 측정의 수치는 이 문서를 기준으로 한다. 스쿼드 공유용 요약은 judge-rerun-0927-brief.md에 있다.

작성 정원석 · 상태 초안 · 최종 수정 2026-09-27 · 이슈 MOR-2477

결론#

judge 판정에서 BAD를 받은 비율은 지금 운영 중인 markdown summary 방식이 32.0%, Visual Summary가 30.9%다. Visual Summary의 BAD 비율이 1.1%p 낮다. Visual Summary는 여러 컴포넌트로 이루어진 결과도 한 번에 판정했고 판정기가 사용자 원문 전체와 대조하게 했으므로, 운영 방식보다 까다로운 조건으로 판정했다. 그런데도 BAD 비율이 운영 방식보다 높지 않았다.

다만 두 방식의 차이는 통계적으로 의미가 없다. 입력마다 1순위 결과로 센 BAD 비율도 운영 방식이 39.6%, Visual Summary가 36.4%다. 두 방식 모두 결과가 있는 입력 90건끼리 비교하면 Visual Summary의 BAD 비율이 2.2%p 낮고, 이 차이의 95% 신뢰구간은 −16.7~+12.2%p다. 따라서 이번 결과로는 Visual Summary가 운영 방식보다 나아졌다고 말할 수 없고, 나빠지지 않았다고 말할 수 있다.

  • 운영 방식의 BAD 비율 32.0%는 8/31에 잰 27.3%(그때 표본으로 다시 센 값은 30.9%)와 비슷하다.
  • 여러 컴포넌트를 묶은 결과(L2)의 BAD 비율은 44.3%로 높다. 하지만 이는 두 컴포넌트 중 하나만 BAD를 받아도 결과 전체를 BAD로 세기 때문이다. 컴포넌트 하나 단위로 환산하면 25.4%로, 컴포넌트 하나로 된 결과의 27.5%와 비슷하다(3절).
  • 평가 항목별로 보면 Visual Summary는 내용 대치 정확성, 시각적 계층 구조, 텍스트 가독성의 BAD 비율이 운영 방식보다 낮다. 디자인 완결성의 BAD 비율은 Visual Summary 전체가 21.0%로 운영 방식의 16.5%보다 높지만, 컴포넌트 하나로 된 결과만 보면 17.5%로 거의 같다. 이 차이도 묶음 결과를 세는 방식에서 나온다(3절).

1. 측정 설계#

처음에는 8/31 회차에서 잰 운영 judge의 BAD 비율 27.3%를 기준선으로 쓰려고 했다. 그러나 그 회차에 쓴 입력의 원문을 복구할 수 없었다. 8/31 요약 100개 중 운영 로그의 원문과 연결되는 것이 2개뿐이었다. 그래서 운영 로그에서 원문을 새로 뽑고, 두 방식의 결과를 모두 새로 만들어 판정했다. 이 문서에서는 운영 방식을 A, Visual Summary를 B로 부른다.

항목 A: 운영 markdown summary B: Visual Summary
입력 운영 로그에 있는 사용자 원문 100건. 8/1 이후, 50자 이상, 표가 없는 요약 중에서 md5(input_text) 순서로 앞의 100건을 골랐다 A와 같다
요약 운영에서 실제로 만든 markdown 요약을 # 제목 기준으로 단락을 나눴다(158단락) dev 배포판 comp-pass가 원문으로 문서를 생성했다. 모델은 gpt-5.6-luna, reasoning은 medium, 시도는 최대 3회이고, L1과 L2를 입력당 한 번씩 생성했다
검색 운영 comp-sign 검색을 재현한 prisonbreak search-sim을 썼다. 회차 #13에서 이 경로의 BAD 비율(26.3%)이 운영 judge(27.3%)와 맞는 것을 확인했다. 검색 대상은 SERVING_POOL(데이터 버전 2.0과 2.1, valid v3.4)이고, 표와 차트는 뺐다 dev 배포판 comp-pass를 썼다. 2.5 스냅샷(frame 113,614행, capacity 608,933행)에서 L1과 L2의 결과를 합쳐 순위를 매긴다
순위 단락마다 상위 10개 상위 10개 결과
내용 대치 replaceByLogic iui comp-frame-client의 buildReplacement. 제품의 결과 카드와 같은 인자(rlsc, 문서 제목)를 넘겼다
이미지 제품의 미리보기 렌더러(unicorn-73380)로 그렸다. 대치 후 재배치는 껐다 A와 같다
판정 한 건 단락 하나의 순위 하나. summary 칸에는 그 단락의 markdown을 넣었다 검색 결과 하나. 이미지는 컴포넌트별 판정 이미지를 세로로 이어 붙인 한 장이고, summary 칸에는 원문 전체와 그 레벨의 생성 문서 YAML 전체를 넣었다
판정 조건 gemini-3.5-flash, 프롬프트 묶음 0831(id 7), 평가 항목 5개(1, 2, 3, 5, 7번), 보정(excluder, 검증, 재평가) 켬, as_is, 반복 1회 A와 같다

1.1 두 방식의 판정 단위를 다르게 잡은 이유#

  • A는 운영 judge와 같은 방식으로 판정했다. 단락 하나와 그 단락의 이미지가 판정 한 건이다. 8/31 측정값과 직접 비교할 수 있는 수치는 A를 판정 한 건씩 센 BAD 비율뿐이다.
  • B는 검색 결과 하나를 통째로 판정했다. 여러 컴포넌트를 묶은 결과(L2)는 컴포넌트 여럿이 모여 결과 하나를 이룬다. 컴포넌트를 따로 판정하면 다른 컴포넌트에 들어간 내용이 빠졌는지 알 수 없다. 원문을 보지 않으면 생성 단계에서 빠진 내용도 잡을 수 없다. 그래서 컴포넌트 이미지를 모두 한 장에 담고, summary 칸에 원문과 생성 문서 전체를 넣기로 했다(정원석, 2026-09-27 결정).
  • 판정기는 판정 한 건에 이미지를 한 장만 받는다. 그래서 컴포넌트마다 따로 만든 판정 이미지(renderForJudge)를 세로로 이어 붙였다. 컴포넌트를 한 캔버스에 다시 배치하지는 않았으므로, 컴포넌트마다 배치는 그대로다.
  • 이 때문에 A와 B는 판정 한 건의 단위가 같지 않다. 두 방식을 공정하게 비교하는 기준은 입력마다 1순위 결과로 센 BAD 비율이다. 이 비율은 입력마다 1순위 결과 중 가장 나쁜 판정을 그 입력의 판정으로 보고 계산한다. 판정 한 건씩 센 비율은 참고로 함께 적는다.

2. 결과#

판정은 Eregion 회차 #40(입력 150)과 #41(입력 51100)에서 했다. 2,406건을 모두 판정했고 실패한 판정은 없다. 비용은 69.2달러다. 95% 신뢰구간은 입력을 단위로 다시 뽑는 부트스트랩을 5,000번 돌려 구했다.

지표 A: 운영 markdown summary B: Visual Summary 차이 (B − A, 두 방식 모두 결과가 있는 입력)
입력마다 1순위 결과로 센 BAD 비율 39.6% (29.7~49.5), 91건 36.4% (27.3~45.5), 99건 −2.2%p (−16.7~+12.2), 90건
판정 한 건씩 센 BAD 비율 32.0% (28.3~36.3), 1,420건 30.9% (26.4~35.6), 986건 −1.6%p (−7.2~+3.7), 91건. 판정 단위가 달라 참고용
결과가 없는 입력 9건(검색 후보가 없는 입력 8건, 1순위 이미지를 만들지 못한 입력 1건) 1건(입력 #50, 생성 실패)
등급 분포 PERFECT 435건, ACCEPTABLE 530건, BAD 455건 PERFECT 390건, ACCEPTABLE 291건, BAD 305건

평가 항목별 BAD 비율(판정 한 건씩 센 값. 판정 한 건이 여러 항목에서 BAD를 받을 수 있다)

평가 항목 A B
구조 적합성 2.7% 2.8%
내용 대치 정확성 12.4% 8.9%
시각적 계층 구조 10.8% 6.4%
텍스트 가독성 13.7% 10.2%
디자인 완결성 16.5% 21.0%

입력별 판정 비교(두 방식 모두 결과가 있는 입력 90건, 1순위 결과)

B 통과 B BAD
A 통과 35건 20건
A BAD 22건 13건

A만 BAD인 입력은 22건, B만 BAD인 입력은 20건으로 거의 같다. 두 방식이 모두 BAD인 입력은 13건뿐이므로, 두 방식이 실패하는 입력은 대부분 서로 다르다.

세부 구분

구분 BAD 비율
B 중 컴포넌트 하나로 된 결과 (판정 한 건씩 / 1순위만) 27.5% (215/783) / 34.1% (29/85)
B 중 여러 컴포넌트를 묶은 결과 (판정 한 건씩 / 1순위만) 44.3% (90/203) / 50.0% (7/14)
B 중 묶음 결과를 컴포넌트 하나 단위로 환산한 값 (종합 / 디자인 완결성) 25.4% / 19.1% (묶음 BAD 비율 = 1 − (1 − 컴포넌트 하나의 BAD 비율)²에서 거꾸로 계산)
B 전체를 컴포넌트 하나 단위로 환산한 추정값 26.8% (컴포넌트 1,189개 기준)
B의 생성 레벨별 L1 / L2 (판정 한 건씩) 26.1% (166/635) / 39.6% (139/351)
A의 1순위 결과 (단락 단위) 38.5% (57/148)
A를 입력마다 1순위 결과로 센 값. 단락이 1개인 입력 / 2개 이상인 입력 32.4% (22/68) / 60.9% (14/23)

3. 해석#

  1. 차이는 오차 범위 안에 있다. 입력마다 1순위 결과로 센 차이의 95% 신뢰구간은 폭이 ±15%p에 가깝다. 입력 100건으로는 5%p 안팎의 차이를 가려낼 수 없다. 이번 결과로 말할 수 있는 것은 "Visual Summary가 운영 방식보다 뚜렷하게 나쁘지는 않다"까지다.
  2. 묶음 결과의 BAD 비율이 높은 것은 품질이 아니라 세는 방식 때문이다. 묶음 결과 203건은 모두 컴포넌트가 2개이고, 두 컴포넌트 중 하나만 BAD를 받아도 결과 전체가 BAD로 집계된다. 컴포넌트 하나로 된 결과의 BAD 비율 27.5%로 계산하면, 두 컴포넌트 중 하나 이상이 BAD를 받을 확률은 47.4%다. 묶음 결과의 실제 BAD 비율은 44.3%로 이보다 조금 낮다. 평가 항목별로도 계산이 맞는다. 디자인 완결성은 예상값 31.9%에 실제값 34.5%, 텍스트 가독성은 예상값 16.1%에 실제값 17.2%, 내용 대치 정확성은 예상값 14.5%에 실제값 14.3%다. 운영 방식처럼 판정 한 건에 컴포넌트가 하나씩 들어가도록 환산하면 B의 BAD 비율은 약 26.8%로, A의 32.0%보다 약 5%p 낮다. 거꾸로 계산한 추정치라 신뢰구간은 구하지 않았다.
  3. 디자인 완결성 항목의 차이도 세는 방식 때문이다. 컴포넌트 하나로 된 결과의 디자인 완결성 BAD 비율은 17.5%로, A의 16.5%와 거의 같다. 차이는 모두 묶음 결과에서 나온다. 컴포넌트 이미지를 이어 붙인 것 자체의 영향은 작다. 판정기는 두 컴포넌트를 "첫 번째 슬라이드", "두 번째 슬라이드"로 따로 알아보고, 각 컴포넌트의 실제 결함(표의 폭이 줄어든 것, 텍스트가 넘친 것, 내용에 비해 빈 여백이 큰 것)을 지적했다. 두 컴포넌트가 나뉘어 보인다거나 통일성이 없다고 언급한 판정은 묶음 결과에서 29%로, 컴포넌트 하나로 된 결과(15%)나 A(19%)보다 조금 많다. 이어 붙인 이미지의 영향은 예상값과 실제값의 차이인 2~3%p 정도로 본다.
  4. 운영 방식은 단락이 많은 입력에서 BAD 비율이 크게 높아진다. 단락이 2개 이상인 입력은 1순위 결과로 센 BAD 비율이 60.9%다. 이 기준은 1순위 결과 중 가장 나쁜 판정을 보기 때문에, 단락이 많을수록 BAD를 받을 기회가 많다. 운영 제품이 한 입력에 대해 단락마다 결과를 보여 주는 구조가 그대로 반영된 결과다.
  5. 운영 방식의 기준선은 달라지지 않았다. A의 BAD 비율 32.0%는 8/31의 27.3%(그때 표본으로 다시 센 값은 30.9%)와 판정 재현 오차(1~4%p) 범위 안에서 비슷하다.

4. 한계#

  • A의 검색과 내용 대치는 운영 서버를 직접 쓰지 않고 prisonbreak의 재현 코드로 했다. 회차 #13에서 운영 judge와 1%p 안쪽으로 맞춰 본 경로다.
  • B의 생성은 결과가 매번 달라질 수 있다. 입력당 한 번만 생성했으므로 입력별 결과의 변동이 크다.
  • B는 dev 배포판을 직접 호출했다. 다만 내용 대치(buildReplacement)와 이미지 렌더링은 로컬에서 제품과 같은 코드로 실행했다.
  • B는 summary 칸에 원문과 생성 문서 전체를 넣었지만, 판정 프롬프트(묶음 7)는 요약 한 조각을 넣는 것을 전제로 만들어졌다. 그래서 원문에는 있지만 생성 단계에서 빠진 내용이 내용 대치 정확성 BAD로 잡힐 수 있다. 생성 단계에서 빠진 내용도 결과의 결함으로 보려는 의도였지만, A와 판정 기준이 같지 않다는 점은 감안해야 한다.
  • 묶음 결과는 컴포넌트 두 개를 한 번에 판정하므로, 컴포넌트 하나로 된 결과보다 BAD를 받을 기회가 두 배다. 3절처럼 거꾸로 계산해 환산할 수 있지만, 다음 측정부터는 컴포넌트별로 나눠 판정하거나 컴포넌트 수를 반영해 환산한 값을 함께 보고해야 한다. 이어 붙인 이미지가 디자인 완결성 판정에 준 영향은 2~3%p 정도로 추정한다.
  • 같은 이미지를 다시 판정하면 결과가 1~4%p 달라진다(mordor-wiki의 judge-loop 문서).
  • 판정에서 뺀 건: A는 39건이다(검색 후보가 없는 9단락, 렌더링 시간 초과 1건, Eregion 이미지 합성 실패 29건). B는 입력 #74의 결과 2건(이미지 합성 실패)과 입력 #50 전체(생성 실패)다.

5. 인계 이후 바뀐 점#

인계 당시 계획 실제로 한 것 이유
로컬에 comp-pass 서버를 띄우고 2.5 스냅샷을 추출한다 dev 배포판(api-develop)을 로그인한 브라우저에서 호출했다 스냅샷 추출이 30분 넘게 걸리고 AWS SSO 설정도 필요했다. 배포판을 직접 쓰면 "로컬 서버가 dev 배포판과 다를 수 있다"는 한계도 없어진다
B는 상위 10개 결과의 컴포넌트마다 한 건씩 판정한다 검색 결과마다 한 건씩 판정했다. 컴포넌트 이미지 전부와 원문, 생성 문서 전체를 넣었다 1.1절
한 번에 업로드한다 입력 150과 51100으로 나눠 두 번 업로드하고, 회차도 2개로 나눴다 Eregion은 한 번에 1,000건까지만 업로드할 수 있다
입력당 한 번 생성한다 입력 #50이 실수로 두 번 생성됐다. 첫 번째 결과(422, 생성 실패)를 유효한 결과로 보고, B의 결과가 없는 입력으로 처리했다 입력당 한 번만 생성한다는 규칙을 따랐다. 두 번째 응답은 집계에서 뺐다

6. 재현#

명령과 파일은 prisonbreak의 scripts/judge-loop/mor2477/README.md(PR #2477)에 정리했다. 스쿼드 공유용 요약은 Confluence TM 페이지에 올렸다.

항목 위치
코드 prisonbreak 브랜치 feat/MOR-2477-judge-rerun의 scripts/judge-loop/mor2477/
중간 자료 (운영 원문 포함, 저장소 밖에 보관) EC2 ~/judge-work/mor2477-0927.tar.gz
B의 판정 이미지 GC 버킷 garbagecollector/mor2477/B-entry/<입력 id>-e<순위>.jpg
Eregion 업로드 A1 #50, A2 #51, B1 #52, B2 #53. #49는 1,000건을 넘어 파싱이 거부된 빈 업로드다
Eregion 회차 #40(A1과 B1, 입력 150), #41(A2와 B2, 입력 51100). 한 번에 16건씩 판정했고, 두 회차를 동시에 돌려 19분 걸렸다

부록. 중간 자료 목록#

ec2-user@10.122.9.74:~/judge-work/mor2477-0927.tar.gz (110MB, sha256 edbb0a7b24c3ae4a648878d42f977cb6dbbd33ffb2bba1cea0d58bf8784c3981)

압축 파일 안의 경로 (judgeA/ 기준) 내용
inputs.json, input-sha.json 운영 원문 100건과 검증용 해시
before/records.json, before/payloads/, before/docs/ A의 검색과 대치 기록, 렌더링 요청, 렌더링 결과 문서
after/responses/, after/responses_excluded/, after/_runlog.json B의 dev 응답(상위 10개 결과), 입력 #50의 두 번째 응답, 생성 로그
after/records.json, after/dos/, after/payloads/, after/docs/ B의 컴포넌트별 기록, sheet와 rlsc, 렌더링 요청, 렌더링 결과 문서
after/entries.json, after/entry-img/ B의 검색 결과별로 이어 붙인 이미지와 GC URL
judge_{A,B}{1,2}.csv, _map.json Eregion 업로드 파일과 행 대응표(sha256 앞자리: A1 307357d5, A2 eafa09f4, B1 20a95b84, B2 42a057da)
report.json aggregate.py의 집계 결과
samples-inline.html 입력 #1~3의 샘플 비교 페이지

↑ 목차로 · 4부 머리로

18장judge 재측정 · 운영 요약 파생위 보고서의 요약판

원문 evaluation/judge-rerun-0927-brief.md GitHub 에서 보기 6 KB 원문 제목 「운영 요약 방식과 Visual Summary의 judge 재측정 결과 (2026-09-27)」

문서 안내 스쿼드 공유용 요약이다. 측정 설계, 한계, 재현 방법은 judge-rerun-0927.md에 있고, 수치는 그 문서를 기준으로 한다.

작성 정원석 · 상태 초안 · 최종 수정 2026-09-27 · 이슈 MOR-2477

Confluence 같은 내용을 TM 페이지에 올렸다. 수치를 고칠 때는 이 문서와 상세 보고서를 먼저 고치고, 그다음 페이지에 반영한다.

요약#

judge 판정에서 BAD를 받은 비율은 지금 운영 중인 markdown summary 방식이 32.0%, Visual Summary가 30.9%다. Visual Summary의 BAD 비율이 1.1%p 낮다.

Visual Summary는 운영 방식보다 까다로운 조건으로 판정했다. 여러 컴포넌트로 이루어진 결과도 한 번에 판정했고, 판정기가 사용자 원문 전체와 대조하게 했다. 그런데도 BAD 비율이 운영 방식보다 높지 않았다.

다만 1.1%p는 판정 오차 범위 안에 있는 차이다. 따라서 Visual Summary가 운영 방식보다 나아졌다고 말할 수는 없고, 나빠지지 않았다고 말할 수 있다.

운영 방식과 BAD 비율 비교#

집계 기준 운영 markdown summary Visual Summary 차이
판정 한 건씩 센 비율 (8/31 측정과 같은 방식) 32.0% 30.9% Visual Summary가 1.1%p 낮음
입력마다 1순위 결과로 센 비율 39.6% 36.4% Visual Summary가 3.2%p 낮음
  • 입력마다 1순위 결과로 센 비율은, 입력마다 1순위 결과 중 가장 나쁜 판정을 그 입력의 판정으로 보고 계산한 값이다.
  • 두 방식 모두 결과가 있는 입력만 골라 같은 입력끼리 비교해도 Visual Summary의 BAD 비율이 낮다. 판정 한 건씩 센 비율은 1.6%p, 1순위 결과로 센 비율은 2.2%p 낮다.
  • 이 두 차이의 95% 신뢰구간은 각각 −7.2~+3.7%p와 −16.7~+12.2%p다. 두 구간 모두 0을 포함하므로 통계적으로 의미 있는 차이가 아니다.
  • 운영 방식의 BAD 비율 32.0%는 8/31에 잰 27.3%(그때 표본으로 다시 센 값은 30.9%)와 비슷하다. 운영 방식의 기준선은 달라지지 않았다.

Visual Summary는 더 까다로운 조건으로 판정했다#

두 방식 모두 같은 판정기(gemini-3.5-flash, 0831 프롬프트, 평가 항목 5개)로 판정했다. 하지만 판정기에 넣은 자료는 다음과 같이 달랐다.

운영 markdown summary Visual Summary
판정 한 건에 들어가는 것 요약 단락 하나와 컴포넌트 하나 검색 결과 하나. 여러 컴포넌트를 묶은 결과는 컴포넌트 전부를 이미지 한 장에 담았다
판정기가 이미지와 대조하는 글 그 단락의 markdown 요약 사용자 원문 전체와 생성된 문서 전체
판정기가 잡을 수 있는 결함 이미지가 요약과 어긋나는 부분 이미지가 원문과 어긋나는 부분, 생성 단계에서 빠진 내용, 컴포넌트끼리 어긋나는 부분

Visual Summary가 불리한 조건에서 판정을 받았는데도 BAD 비율이 운영 방식보다 높지 않았다는 뜻이다.

두 방식은 서로 다른 입력에서 실패한다#

두 방식 모두 결과가 있는 입력 90건에서 1순위 결과의 판정을 비교했다.

Visual Summary 통과 Visual Summary BAD
운영 방식 통과 35건 20건
운영 방식 BAD 22건 13건
  • 운영 방식만 BAD인 입력은 22건이고, Visual Summary만 BAD인 입력은 20건이다. 두 수가 거의 같으므로, 두 방식의 BAD 비율에 차이가 없다는 결론과 맞는다.
  • 두 방식이 모두 BAD인 입력은 13건뿐이다. 운영 방식이 BAD를 받은 35건 가운데 22건은 Visual Summary가 통과했다. 두 방식이 실패하는 입력은 대부분 서로 다르다.

묶음 결과의 BAD 비율이 높은 것은 세는 방식 때문이다#

Visual Summary의 결과는 컴포넌트 하나로 된 결과와 여러 컴포넌트를 묶은 결과(L2)로 나뉜다. 겉으로 보면 묶음 결과의 BAD 비율이 훨씬 높다.

결과 유형 BAD 비율 디자인 완결성 항목의 BAD 비율
운영 markdown summary (참고) 32.0% 16.5%
Visual Summary, 컴포넌트 하나로 된 결과 (상위 10개 결과의 79%) 27.5% 17.5%
Visual Summary, 여러 컴포넌트를 묶은 결과 (상위 10개 결과의 21%) 44.3% 34.5%

그런데 묶음 결과 203건은 모두 컴포넌트가 2개이고, 두 컴포넌트를 이미지 한 장에 담아 한 번에 판정했다. 그래서 두 컴포넌트 중 하나만 BAD를 받아도 묶음 결과 전체가 BAD로 집계된다.

컴포넌트 하나가 BAD를 받을 확률이 27.5%라면, 두 컴포넌트 중 하나 이상이 BAD를 받을 확률은 47.4%다. 묶음 결과의 실제 BAD 비율 44.3%는 이 값보다 조금 낮다. 거꾸로 묶음 결과의 BAD 비율에서 컴포넌트 하나당 BAD 비율을 계산하면 25.4%이고, 이는 컴포넌트 하나로 된 결과의 27.5%와 비슷하다.

평가 항목별로 계산해도 결과가 같다.

평가 항목 컴포넌트 하나로 된 결과의 BAD 비율 묶음 결과의 BAD 비율 예상값 (두 컴포넌트 중 하나 이상 BAD) 묶음 결과의 실제 BAD 비율
디자인 완결성 17.5% 31.9% 34.5%
텍스트 가독성 8.4% 16.1% 17.2%
내용 대치 정확성 7.5% 14.5% 14.3%

따라서 묶음 결과의 BAD 비율이 높은 이유는 품질이 나빠서가 아니다. 판정 한 건에 컴포넌트가 두 개 들어 있어서 BAD를 받을 기회가 두 배이기 때문이다.

운영 방식은 판정 한 건에 컴포넌트가 하나씩 들어간다. Visual Summary도 컴포넌트 하나 단위로 환산하면 BAD 비율이 약 26.8%로, 운영 방식의 32.0%보다 약 5%p 낮다. 묶음 결과의 BAD 비율에서 거꾸로 계산한 추정치이며, 신뢰구간은 따로 구하지 않았다.

디자인 완결성 항목의 BAD 비율이 높아진 이유#

디자인 완결성 항목의 BAD 비율은 Visual Summary 전체가 21.0%로, 운영 방식의 16.5%보다 높다. 하지만 컴포넌트 하나로 된 결과만 보면 17.5%로, 운영 방식과 거의 같다. 차이는 모두 묶음 결과에서 나오고, 그 원인은 앞에서 설명한 세는 방식이다. 묶음 결과를 컴포넌트 하나 단위로 환산하면 디자인 완결성 항목의 BAD 비율은 19.1%다.

컴포넌트 이미지를 이어 붙인 것 자체의 영향은 크지 않다. 판정 근거를 보면, 판정기는 "첫 번째 슬라이드는 안정적이나 두 번째 슬라이드에서 표의 폭이 수축됐다"처럼 두 컴포넌트를 따로 알아보고 각 컴포넌트의 실제 결함을 지적한다. 지적한 결함은 표의 폭이 줄어든 것, 텍스트가 넘친 것, 내용에 비해 빈 여백이 큰 것 등이다. 두 컴포넌트가 나뉘어 보인다거나 통일성이 없다고 언급한 판정은 묶음 결과에서 29%로, 컴포넌트 하나로 된 결과(15%)나 운영 방식(19%)보다 조금 많다. 이어 붙인 이미지의 영향은 예상값과 실제값의 차이인 2~3%p 정도로 본다.

결론과 다음 단계#

  1. judge 기준으로 Visual Summary는 운영 markdown summary 방식보다 나쁘지 않다. 더 까다로운 조건으로 판정했는데도 Visual Summary의 BAD 비율이 1~3%p 낮다. 다만 통계적으로 의미 있는 차이는 아니다.
  2. 묶음 결과의 BAD 비율 44.3%를 품질 문제로 해석하지 않는다. 판정 한 건에 컴포넌트가 두 개 들어 있어서 BAD를 받을 기회가 두 배인 결과다. 컴포넌트 하나 단위로 환산하면 25.4%로, 컴포넌트 하나로 된 결과와 비슷하다.
  3. 디자인 완결성도 운영 방식과 차이가 없다. 컴포넌트 하나로 된 결과의 디자인 완결성 BAD 비율은 17.5%로, 운영 방식의 16.5%와 거의 같다. 다음 측정부터는 묶음 결과를 컴포넌트별로 나눠 판정하거나, 컴포넌트 수를 반영해 환산한 값을 함께 보고한다.
  4. 차이를 통계적으로 확인하려면 표본을 늘린다. 같은 방식으로 300건 이상을 측정하면 신뢰구간의 폭이 ±8%p 안팎으로 좁아질 것으로 예상한다.

측정 방법#

항목 내용
입력 운영 로그에 있는 사용자 원문 100건. 8/1 이후, 50자 이상, 표가 없는 요약을 골랐다
운영 markdown summary 운영에서 만든 markdown 요약을 단락으로 나누고, 단락마다 상위 10개 컴포넌트에 내용을 대치했다. 검색과 대치에는 운영 로직을 재현한 코드를 썼다
Visual Summary dev 배포판이 원문으로 문서를 생성했다(L1과 L2를 입력당 한 번씩). 그 결과 중 상위 10개에 제품의 결과 카드와 같은 방식으로 내용을 대치했다
이미지 두 방식 모두 제품의 미리보기 렌더러로 그렸다. 대치 후 재배치는 끄고 그렸다
판정 Eregion 회차 #40과 #41(사내망)에서 2,406건을 판정했다. 실패한 판정은 없고, 비용은 69달러다
제외 운영 방식은 9건을 뺐다(검색 후보가 없는 입력 8건, 이미지를 만들지 못한 입력 1건). Visual Summary는 입력 #50 한 건을 뺐다. 생성에 실패했고, 입력당 한 번만 생성한다는 규칙에 따라 다시 생성하지 않았다

결과 직접 보기#

  • Eregion 결과 화면(사내망, 로그인 필요): 회차 #40, 입력 1~50, 회차 #41, 입력 51~100. '짝으로 보기'를 누르면 같은 입력에 대한 운영 방식(A)과 Visual Summary(B)의 이미지, 판정 등급, 평가 항목별 판정 근거를 나란히 볼 수 있다.
  • 입력 하나에 대한 두 방식의 결과를 한 화면에 모아 보는 뷰어도 만들었다. 사용자 원문이 들어 있어서 공개 주소에는 올리지 않았다. 재현 코드의 build-viewer.py로 각자 만들 수 있다.

참고 자료#

↑ 목차로 · 4부 머리로

19장재순위 규칙 회차 26·27 비교 정본정반대 결과가 나온 이유

원문 evaluation/rerank-rule-rounds-26-27.md GitHub 에서 보기 9 KB 원문 제목 「visual summary 재순위 규칙 회차 26·27 비교 — 중간 정리」

  • 작성 계기: 류민님이 Slack #mordor스쿼드(스레드, 2026-09-20)에 공유한 판정 루프 회차 10~30 결과 중, 정원석님이 회차 26과 27이 언뜻 비슷한 규칙인데 결과가 정반대(26은 악화, 27은 개선)로 나온 이유를 물어 조사한 내용을 정리한다.
  • 조사 방법: aippt-prisonbreak 저장소(scripts/judge-loop/rerank.ts)와, 그 결함 예측 로직의 원본인 miridih/miricanvas-iui 저장소(src/comp-frame/align.ts, cost.ts)를 직접 읽고 blame/커밋 이력을 대조했다. 이후 정원석님이 실제 판정 화면 URL을 제시해, production DB(garbage_collector 스키마)의 회차별 원시 판정 결과를 직접 조회해 가설을 검증했다(§4~§7).
  • 정원석님이 같은 스레드에 본인 결론을 독립적으로 정리해 올렸고, 아래 §7에서 확인한 근본 원인과 일치한다(교차 확인됨). §9는 그 내용을 반영해 최신화했다.

1. 배경 — 판정 루프가 뭘 하는가#

visual summary(요약 이미지 생성) 결과를 운영 judge와 같은 채점 조건으로 미리 재현해, 코드를 한 군데 고칠 때마다 BAD 비율이 어떻게 변하는지 실측하는 반복 실험 루프다. 코드는 aippt-prisonbreak main 브랜치 scripts/judge-loop/에 있다.

회차마다 다음 중 하나를 바꿔가며 실험했다: 요약 생성 방식, 검색 대상 후보 풀, 검색·대치 로직, 판정 방법. 그중 회차 26·27은 "검색이 이미 정한 상위 후보들 중에서 다시 고르는 규칙(rerank)"을 바꾼 실험이다.

2. 회차 26 vs 27 — 코드 상 차이#

두 규칙 모두 aippt-prisonbreak main의 scripts/judge-loop/rerank.ts, pickRank 함수에서 구현된 RerankSpec의 옵션이다.

회차 26 회차 27
옵션 avoid_defects: ['slots.lineSurplus'] min_unauthored: true
동작 상위 k개 후보를 순서대로 보다가, slots.lineSurplus(칸이 남는다는 예측 결함)가 찍힌 후보는 통째로 건너뛰고, 그 결함이 없는 첫 후보를 그대로 쓴다 (이진 배제) 조건을 통과한 후보들끼리 unauthored_count(빈 슬롯 + 경계 안 잔여 + 경계 밖 원문을 모두 합친 실측치)를 비교해, 가장 적은 후보를 상대적으로 골라낸다 (배제 없음, 순위 매기기)
결과 운영 방식 한 장 기준 BAD 20%→26% (악화) 운영 방식 한 장 기준 BAD 27%→16% (개선)

두 규칙 다 "칸이 남는 문제"를 다룬다는 점에서 비슷해 보이지만, 하나는 한 가지 결함만 보고 이진적으로 후보를 지우는 방식이고 다른 하나는 여러 결함을 종합한 값으로 상대 순위를 매기는 방식이라는 점이 다르다.

중요한 정정(정원석님, §9 참고): 회차 26과 27은 서로를 비교하는 A/B가 아니다. DB(garbage_collector.judge_upload_runs)의 note를 보면 둘 다 A(대조군)는 똑같이 V5이고, B(실험)만 각각 다르다 — run 26은 A=V5, B=w3(avoid_defects), run 27은 A=V5, B=w2(min_unauthored). 즉 "V5 대비 26의 처치 효과"와 "V5 대비 27의 처치 효과"를 각각 재는 독립적인 두 실험이지, 26에서 27로 이어지거나 27이 26 위에 뭔가를 더한 게 아니다. 두 표의 baseline 숫자(20% vs 27%)가 다른 것도 이 때문이다 — 같은 100건 세트에 대해 판정을 두 번 따로 돌리면서 생기는 재판정 흔들림(위키 문서에서 언급된 1~4%p)까지 겹친 것으로 보인다.

3. slots.lineSurplus(예측 결함)란 무엇인가#

  • predicted_defects는 검색 결과가 실제로 화면에 렌더링되기 전에, 정렬(alignment) 규칙만으로 미리 추정한 결함 라벨이다. 사람이나 별도 AI/ML 모델이 매기는 게 아니라, miricanvas-iui의 결정론적 규칙 기반 로직(if-else 성격)이 계산한다.
  • 위치: miridih/miricanvas-iui 저장소
    • src/comp-frame/align.ts — 쿼리(요약할 내용)와 candidate(후보 템플릿)의 슬롯을 규칙 (a)~(g)로 매칭하는 정렬 단계. 다 매칭한 뒤에도 채워지지 않은 candidate 슬롯 수를 itemBlank로 센다.
    • src/comp-frame/cost.ts — itemBlank > 0이면 slots.lineSurplus 결함 라벨을 붙인다.
  • slots.lineSurplus는 "내용이 넘쳐서 잘림"이 아니라 그 반대다. candidate 템플릿의 항목(item) 슬롯 개수가 실제로 채울 내용 개수보다 많아서, 다 채우고도 칸이 남는 경우를 가리킨다.
    • 참고로 내용이 넘치는 경우(콘텐츠 > 슬롯)는 align.ts에 이미 별도 로직이 있다 — 넘친 항목을 마지막 items 슬롯에 줄바꿈으로 이어 붙이는(fold/merge) 처리가 되어 있다. 이건 slots.lineSurplus와는 무관한 로직이다.
    • 즉 slots.lineSurplus는 같은 후보 안에서 합쳐서 해결할 수 있는 문제가 아니라, 애초에 슬롯 수가 더 적은 다른 템플릿을 골라야 해결되는 구조적 불일치 문제다.

이 코드를 누가 작성했나 (miricanvas-iui 기준, blame)#

파일·부분 작성자 커밋
cost.ts — slots.lineSurplus 결함을 붙이는 코드(결함 예측기 자체를 처음 만든 커밋) 이석님 e82e0284, 2026-09-07, feat(comp-frame): add cost computation (cost.ts)
align.ts — itemBlank를 세는 정렬 규칙 홍지혜님 4bc87c13, 2026-09-13, fix(comp-frame): Visual Summary 정본대로 검색·대치를 맞춘다

aippt-prisonbreak 쪽 코드(scripts/judge-loop/rerank.ts, pickRank)는 이 예측 결함을 읽기만 하고 그 위에 재순위 규칙(회차 26/27)을 얹은 것이며, 예측 로직 자체를 건드리지 않는다. comp-frame 전체는 사내 npm으로 배포되지 않아(@miri-unicorn/miricanvas-iui-comp-frame-shared 404, 2026-09-19) miricanvas-iui 원본을 그대로 복사(vendoring)해 들여온 것이다(src/lib/demand/vendor/comp-frame/PROVENANCE.md, 원본 커밋 c0aa4ad3, 옮긴 사람 류민님).

4. 원시 데이터 위치 정정 — production DB garbage_collector 스키마에 실제로 있었다 (2026-09-20)#

처음에 repo(aippt-prisonbreak)·production DB의 public 스키마·mordor-wiki 세 곳을 봤을 때는 회차 26·27 원시 결과가 안 보였다. 이는 내가 public 스키마만 보고 garbage_collector 스키마를 확인하지 않은 실수였다. 정원석님이 공유한 실제 판정 화면 URL(http://10.122.9.74:3000/visual-summary/judge?tab=result&upload=7&uploadB=8&run=26&variant=baseline)의 run=26은 production DB garbage_collector.judge_upload_runs.id = 26을 직접 가리키고, 이 행이 Slack에서 말한 "회차 26"과 정확히 일치한다(note: "MOR-2496 원본 PERFECT 100건 A=V5 B=w3", upload_id=26, upload_b_id=27). 회차 27도 마찬가지로 id=27(note: "... A=V5 B=w2")이다.

즉 repo·production DB(public 스키마)·wiki에 없다는 이전 결론은 절반만 맞았다 — DB 자체에는 있었고, 내가 스키마를 놓쳤을 뿐이다. garbage_collector.judge_upload_runs(회차 메타), judge_upload_rows(입력 원문·후보), judge_upload_results(행별 판정 등급·symptom_tags)를 조회하면 회차별 원시 결과를 그대로 볼 수 있다.

5. 실측 — 회차 26·27의 BAD 증상 태그 비교#

judge_upload_results.grade·symptom_tags를 직접 집계했다(2026-09-20, readonly 접속).

등급 분포(둘 다 100건, Slack 보고 수치와 정확히 일치):

run variant BAD ACCEPTABLE PERFECT
26 a (V5, baseline) 20 48 32
26 b (avoid_defects: lineSurplus) 26 41 33
27 a (V5, baseline) 27 33 40
27 b (min_unauthored) 16 43 41

BAD로 채점된 행의 symptom_tags(판정기가 실제로 관측한 증상, 여러 개 가능) 상위 항목:

run variant 1위 증상(건수) 비고
26 a (baseline) 넘침·잘림(4) / 색상대비(4) / 위계역전(4) / 텍스트겹침(4) 빈 슬롯 방치 3
26 b (avoid lineSurplus) 빈 슬롯 방치(6) 더미 문구 잔존 4
27 a (baseline) 빈 슬롯 방치(7) 요소에 가림 4, 텍스트겹침 4
27 b (min_unauthored) 넘침·잘림(3) / 색상대비(3) 빈 슬롯 방치 1

6. 결론 — §4(이전 버전)의 가설은 틀렸다#

이전 버전의 이 문서는 "avoid_defects가 slots.lineSurplus 하나만 보고 다른 결함(contentLoss/residue)은 못 걸러서 그쪽으로 밀려났을 것"이라는 가설을 세웠다. 실측 결과는 이 가설을 지지하지 않는다.

정작 벌어진 일은 이렇다 — 회차 26에서 "빈 칸이 남는 후보를 피하자"는 규칙(avoid_defects: ['slots.lineSurplus'])을 넣었는데, 판정기가 실제로 관측한 "빈 슬롯 방치" 증상이 baseline(3건)보다 오히려 늘었다(6건). 그 규칙이 막으려던 바로 그 증상을 못 막았을 뿐 아니라 더 나빠졌다. 반대로 회차 27의 min_unauthored는 baseline 7건에서 1건으로 확실히 줄였다.

이건 §3에서 이미 짚었던 우려 — predicted_defects(slots.lineSurplus)는 실제 렌더링 전 정렬 단계의 사전 추정값이라는 것 — 가 실측으로 뒷받침되는 그림이다. 가장 유력한 해석: slots.lineSurplus 예측이 판정기가 실제로 보는 "빈 슬롯"과 잘 맞지 않는다. 예측을 믿고 후보를 배제했더니, 정작 그 후보가 실제로는 안 비었을 수도 있고(예측 오탐으로 좋은 후보를 억울하게 버림), 배제 후 골라진 후보가 예측에는 안 걸렸지만 실제로는 다른 자리가 비어 있었을 수도 있다(예측 미탐) — 어느 쪽이든 예측(정렬 단계 추정)과 실측(렌더 후 판정) 사이의 괴리가 원인으로 보인다.

7. 근본 원인 확정 — 이미지 + 코드 대조 (2026-09-20)#

design_object_id별 실제 판정 이미지를 직접 열어봤다(garbagecollector 버킷, 예: judge-uploads/27/image/39.webp, .../2.webp).

  • row 39(do_id=9122740): 3단 레이아웃인데 왼쪽·가운데 컬럼의 제목 자리가 "제목을 입력해주세요." placeholder 그대로 남아 있다. 오른쪽 컬럼만 실제 내용(Steven. SONG 프로필)이 채워졌다.
  • row 2(do_id=9787354): 같은 방식으로 두 자리가 "내용을 입력해주세요."로 남아 있다.

이걸 코드와 대조하면 avoid_defects: ['slots.lineSurplus']가 왜 이 사례를 못 걸렀는지 정확히 설명된다.

  • align.ts는 빈 슬롯을 두 종류로 나눠 센다 — itemBlank(반복되는 "many" 항목 슬롯, 예: 카드 여러 장 중 못 채운 카드)와 fieldBlank(제목·부제처럼 하나뿐인 "one" 필드가 빈 것). 둘 다 state.blanks에는 같이 들어가지만 별도 카운터로 나뉜다(align.ts 626~636행).
  • cost.ts는 itemBlank > 0일 때만 slots.lineSurplus 결함 라벨을 붙인다(508~513행). fieldBlank에 대응하는 예측 결함 라벨은 아예 없다.
  • 반면 회차 27의 min_unauthored가 쓰는 unauthored_count(miricanvas-iui rank.ts 386~389행)는 alignment.blanks(itemBlank + fieldBlank 전부) + 경계 안 잔여 + 경계 밖 원문을 모두 합산한다.

결론: avoid_defects: ['slots.lineSurplus']는 예측이 부정확했던 게 아니라, 애초에 "빈 제목 자리"(fieldBlank) 같은 결함 종류를 감지하는 라벨 자체가 예측 결함 목록에 없었다. 그래서 제목이 통째로 비는 후보도 slots.lineSurplus엔 안 걸려 그대로 통과했고, 회차 26에서 "빈 슬롯 방치" 증상이 줄기는커녕 늘어난 것으로 보인다. 이건 §6에서 "예측과 실측의 괴리"로 잠정 서술했던 것보다 더 명확한 원인 — 결함 분류 체계(taxonomy)의 공백이다.

8. 회차 27의 성능이 어느 정도인지 — 원본 컴포넌트 기준선과 비교 (정원석님, 2026-09-20)#

min_unauthored(회차 27, placeholder 제거 스코어링/재정렬) 자체는 성능이 검증됐다고 볼 수 있다.

  • **원본 컴포넌트(내용 대치 없이 원본 디자인 그대로 평가) 자체의 BAD가 13.6%**다 — 즉 대치를 아무리 잘해도 이 이상 좋아지기 어려운 사실상의 하한선.
  • placeholder 스코어링(회차 27)만 적용해도 그 하한선 대비 3.6%p 차이로 좁혀졌다.
  • 단, 이 3.6%p가 "상방이 없다"는 뜻은 아니다 — 원본 디자인 자체에도 애초에 placeholder가 다수였을 것이므로(그래서 대치가 필요했던 것이므로), 원본 컴포넌트가 "내용 관련 감점"을 이미 일부 받았을 수 있다. 하한선 자체가 다소 낮게(느슨하게) 잡혔을 가능성이 있어, 3.6%p라는 차이를 "이게 최선"이라는 증거로 해석하면 안 된다.

9. 정원석님 결론(Slack) — 근본 원인 교차 확인, 최종 결정#

정원석님이 스레드에 직접 정리한 결론은 이 문서 §7의 코드 분석과 독립적으로 도출됐고, 내용이 정확히 일치한다.

  • 회차 26 (근본 원인, 정원석님 표현): "제목이 통째로 비는 후보는 고려되지 않는 로직이었음. 그리고, placeholder가 많은 것을 제거해주는 Scoring/Reordering은 여기 적용되지 않았음." → §7에서 코드로 확인한 "slots.lineSurplus는 itemBlank만 보고 fieldBlank(제목류)는 아예 안 본다"는 것과 동일한 지적이다.
  • 회차 26 최종 결정: 이 규칙은 버린다(채택 안 함).
  • 회차 27 최종 결정: 이 방향은 쓸만하다(채택). 원본 컴포넌트 기준선(13.6%) 대비 3.6%p 차이까지 좁혔다(§8, 단 상한 증거는 아님).
  • 26·27 관계: 서로 비교 대상이 아니라 독립적인 실험(§2 정정 참고).

10. 후속 조치 — visual summary first release 마일스톤에 block 이슈 연결#

정원석님이 스레드에서 Cursor에게 "visual summary first release 마일스톤에 block으로 신규 issue 생성 후 연결"을 요청했고, Cursor가 이를 확인·처리했다(Cursor 세션 링크). 즉 회차 26에서 드러난 "제목 placeholder 미고려" 문제는 단순 실험 기록으로 끝나지 않고, visual summary 첫 출시를 막는 블로커 이슈로 트래킹되기 시작했다. 이 이슈의 정확한 트래커(Jira MOR 등)·키는 이 문서 작성 시점에 확인되지 않았으니, 확인되는 대로 이 절에 링크를 보강한다.

11. 다음 액션 (미확정 — 대화로 정할 것)#

  1. cost.ts에 fieldBlank > 0일 때의 결함 라벨(예: slots.fieldSurplus)을 추가하는 것을 miricanvas-iui에 제안할지 — 이러면 avoid_defects도 title 빈 슬롯을 잡을 수 있게 된다. (다만 26 자체는 이미 폐기 결정이 났으므로, 이건 향후 비슷한 이진 배제 규칙을 또 만들 때를 위한 예방적 제안이다.)
  2. §10에서 생성된 block 이슈의 정확한 트래커 키를 확인하고 이 문서에 연결한다.
  3. 홍지혜님이 스레드에서 "결과 나온 거 보고 개선 가능한 부분 도출해보겠다"고 답했으므로, 이 근본 원인(§7·§9)을 공유하고 fieldBlank 결함 라벨 추가 여부를 함께 논의하는 것을 제안한다.

↑ 목차로 · 4부 머리로

20장재현 스크립트 참고수치를 다시 만드는 순서

원문 evaluation/scripts/README.md GitHub 에서 보기 3 KB 원문 제목 「`evaluation/scripts/` — [`../placeholder-effect.md`](../placeholder-effect.md) 의 수치를 다시 만드는 재료」

이 폴더의 역할 보고서의 수치를 처음부터 다시 만들 수 있게 조회와 분석 코드를 문서 옆에 둔다. 오너 정원석 · 상태 DRAFT · 최종 갱신 2026-09-20

파일#

파일 하는 일
placeholder-adoption-funnel.sql Databricks 조회 네 가지. 노출과 채택, 다운로드와 컴포넌트 잔존, 운영 검색 기록, 검색 절 층의 크기
analyze-placeholder-effect.py 보고서 4절, 5.1절, 6.3절, 9.4절의 수치를 다시 만든다. 표준 라이브러리만 쓴다
judge-rounds-rerank.sql 보고서 7절과 9.3절. 회차별 A/B 배정, 7.3절 표, 집계 단위 비대칭, ref 짝짓기 검증
analyze-placeholder-propensity.py 보고서 5.4절. 성향점수 IPTW 와 1:1 매칭, SMD 균형표. 표준 라이브러리만 쓴다

여기 없는 것: 처치와 공변량을 만드는 도구#

처치 변수를 만드는 placeholder-count.ts 와, 5.4절이 쓴 placeholder-overflow-count.ts 는 앱 저장소의 scripts/ 에 있다. 문서 옆에 둘 수 없다. 두 스크립트가 운영과 같은 엔진으로 대치를 재현하려고 src/lib/demand/logic-replace, src/lib/demand/search-sim, src/lib/demand/identity 를 직접 부르기 때문이다. 코드에서 떼면 재현이 운영과 달라진다.

⚠️ logic-replace 는 main 브랜치에 있다. 작업 브랜치에는 없을 수 있다(2026-09-20 에 feat/MOR-2396 에서 없는 것을 확인했다). 그럴 때는 작업 트리를 건드리지 말고 git worktree add <경로> origin/main --detach 로 따로 꺼내 그 안에서 pnpm install --offline 후 돌린다. 브랜치를 섞으면 search-sim 의 export 가 달라 깨진다.

placeholder-overflow-count.ts 가 만드는 것은 처치(Placeholder Text Slot 수)와 공변량(줄 넘침, 글자 넘침, 자리 수, 글 길이)이다. 넘침은 시그니처 용량과 대치 후 글을 predictLines 로 맞대 계산한다. judge 판정문도 slots.lineSurplus 도 쓰지 않는 이유는 보고서 5.4절에 있다.

다시 만드는 순서#

네 단계다. 1 과 2 는 내부망 또는 VPN 이 필요하고, 3 과 4 는 로컬에서 돈다.

1. 결과 변수를 받는다 (Databricks). placeholder-adoption-funnel.sql 의 조회를 돌려 노출과 채택, 다운로드와 컴포넌트 잔존을 받는다. 노출 표본은 crc32(search_id) % 100 < 8, 채택 표본은 < 15 로 뽑았다. 같은 값을 쓰면 같은 표본이 나온다. 결과를 expo-meta.json 으로 저장한다. 모양은 {"<search_id>#<do_uuid>": {"search_id", "rank", "adopted"}} 다.

2. 요약을 받는다 (Databricks). bronze.miridih_miricanvas_designcontent_template.component_search_request_log 에서 같은 기간, 같은 해시 구간의 search_key 와 summary 를 받는다. 절이 하나인 요약만 남긴다.

3. Placeholder Text Slot을 센다 (aippt-prisonbreak, 운영 readonly 필요).

bash
DOTENV_CONFIG_PATH=.env.local npx tsx scripts/placeholder-count.ts \
  --pairs <pairs.json> --out <out.csv> [--pool serving|gc]

pairs.json 은 [{"id", "summary", "component_key"}] 다. 노출 표본과 채택 표본은 실서빙 풀이라 --pool serving(기본값), 판정 표본은 --pool gc 를 쓴다.

4. 집계한다.

bash
python3 analyze-placeholder-effect.py \
  --judge-ph <판정표본.csv> --judge-case <cases0831.json> \
  --expo-ph <노출표본.csv> --expo-meta <expo-meta.json>

cases0831.json 은 judge 회차 0831 의 행 전량이고 http://10.122.9.74:3000/api/judge-runs/0831/cases?limit=200&offset=N 을 200개씩 넘겨 받는다. 인증이 필요 없다.

중간 파일을 여기 두지 않는 이유#

위 1 에서 4 사이의 중간 파일에는 사용자가 입력한 원문과 그 파생 요약이 들어 있다. 저장소에 올리지 않는다. 판정 루프의 고정 세트도 같은 이유로 GC 버킷 안에만 두고 사외로 내보내지 않는다(mordor-wiki monitoring/judge-loop/README.md 3절).

7절과 9.3절의 조회#

회차별 원시 판정 결과는 production DB 의 garbage_collector.judge_upload_runs, judge_upload_rows, judge_upload_results 에 있다. 운영 readonly 접속으로 읽는다. Databricks 가 아니라 production DB 다.

조회문은 judge-rounds-rerank.sql 에 있다. 2026-09-20 에 복원했다. 그전까지는 어디에도 남아 있지 않아 7절 수치를 재현할 수 없었다. 복원하면서 7.3절 표 여섯 줄이 그대로 나오는 것을 확인했고, 9.3절의 집계 단위 오류를 찾아 고쳤다.

↑ 목차로 · 4부 머리로

5부

계획 — plan/

무엇을 어떤 순서로 만들 것인가. Blueprint 와 PoC 기록이 섞여 있다.

21장PoC 안내서 정본처음 보는 사람의 진입점

원문 plan/poc-guide.md GitHub 에서 보기 22 KB 원문 제목 「Visual Summary PoC 안내서: 무엇을 만들었고, 어떻게 돌고, 무엇이 남았나」

이 문서의 역할 이 PoC 를 처음 보는 사람이 이 문서 하나만 읽고 전체를 이해하도록 쓴다. 왜 만들었는지, 입력에서 판정까지 무엇이 어떤 순서로 도는지, 어디까지 실서비스 코드를 그대로 쓰고 어디부터 PoC 가 따로 만들었는지, 어떤 정책을 어떤 근거로 정했는지, 지금 수치가 얼마인지, 무엇이 남았는지를 담는다. 오너 정원석 (MORDOR) · 상태 진행 중 · 최종 갱신 2026-09-05 (8절 — 복잡도 레벨 토글, 로컬 실험 로그 logs/visual-summary-runs.jsonl 추가) 세부 문서 이 문서는 진입점이고, 모듈 지도는 poc-module-map.md, 상세는 comp-sign-bridge.md, eregion-poc.md 다.

이 문서 훑기

절 무엇에 답하나
1 이 PoC 가 없으면 무엇이 막히나
2 사용자 입력에서 판정까지 무엇이 어떤 순서로 도나
3 실서비스 코드를 그대로 쓴 곳과 PoC 가 대신 만든 곳
4 나란히 둔 두 경로가 각각 무엇인가
5 결함을 어떻게 정의하고 세나
6 생성·검색·대치 중 어디를 고쳐야 하나
7 무엇을 왜 그렇게 정했나, 무엇을 기각했나
8 명령어와 화면
9 기준선이 얼마인가
10 내가 할 것 · 남에게 달린 것 · 스쿼드가 정할 것

1. 왜 만들었나#

제품에서 무슨 일이 벌어지고 있나#

사용자가 글을 넣으면 AI 가 그 내용을 담을 디자인 컴포넌트를 골라 텍스트를 갈아 끼워 슬라이드를 만든다. 그 결과가 나쁠 때, 나쁜 이유가 무엇인지를 2026-07-26 에 조사했다. 결과가 나쁘다고 판정된 것 200건을 직접 읽어 분류하고 자동 채점 16,819행을 함께 집계했더니, 나쁜 결과의 85% 가 그림이 잘못 그려져서가 아니라 "고른 컴포넌트와 넣으려는 내용이 안 맞아서" 였다 (Slack).

세부 원인은 넷이다. 넣을 글자가 그 자리에 안 들어가서 넘치거나, 제목에 들어갈 내용이 설명 자리에 들어가는 식으로 위계가 어긋나거나, 항목이 다섯인데 칸이 셋이거나, 아예 대치가 안 돼 템플릿 원문이 남는 것이다. 이 넷이 이 PoC 가 다루는 구간 전체다.

그런데 이 구간에 규약이 없다#

구현은 이미 있다. 이석님의 packages/comp-sign 패키지가 시그니처 추출과 검색과 랭킹과 내용 대치 네 단계를 전부 구현하고 있다. 비어 있는 것은 구현이 아니라 규약이다 (../design/content-binding-contract.md). 어떤 컴포넌트를 고를 것인가까지는 정해져 있는데, 고른 다음에 무엇을 어디에 어떻게 넣을 것인가가 정의돼 있지 않다.

그래서 이 PoC 의 목적은 데모가 아니다#

이 PoC 는 스쿼드가 못 닫고 있는 결정을 실측으로 닫기 위한 장비다. 화면이 있는 이유는 보여 주기 위해서가 아니라 판정 이력을 사람이 열어 보기 위해서다.

닫아야 할 결정이 구체적으로 무엇인지는 ../TODO.md 에 있고, 이 PoC 가 직접 겨냥하는 것은 넷이다.

티켓 무엇을 정해야 하나 이 PoC 가 주는 것
MOR-1926 4단계(노드 단위 태그)를 건너뛰고 5단계로 갈 것인가 노드 태그 없이 type 만으로 대치가 어디까지 되는지의 실측
MOR-1927 품질이 나빠졌을 때 생성·검색·대치 중 누구 탓인지 어떻게 가릴 것인가 결함을 셋으로 나누는 귀속 계산
MOR-1929 판정 절차와 지표를 어떻게 정의할 것인가 두 검색의 교집합 상위 10건 품질을 실제로 계산한 결과
MOR-1936 랭킹 가중치를 바꾸면 품질이 좋아지는가 가중치를 바꿔 가며 같은 자로 재는 실험대

여기서 MOR-1926 이 특히 급하다. 노드 단위 태그(I.I.S.)는 RAT pod 본편이 2026년 6월 중순부터 정지 상태지만, 2026-08-22 에 PoC 대행 생산으로 진행하기로 닫았다 (../TODO.md D-5, poc-module-map.md). "본편 없이도 4단계 가치가 있는가"를 판정하는 것이 MOR-1926 이다.


2. 전체 흐름#

사용자가 쓴 글 한 덩어리가 들어가서, 슬라이드에 그려질 결과물과 그 결과물의 결함 채점표가 나온다. 사이에 여덟 단계가 있다.

mermaid
flowchart TD
    UserInput["사용자 입력<br/>(실사용 로그 원문)"]
    Generator["1. Visual Summary 생성<br/>LLM 이 YAML 을 쓴다"]
    Validate["2. 검증<br/>파싱 → id 부여 → 스키마 검사"]
    Establish["3. 카테고리 확정<br/>성립 조건 판정 + fallback"]
    Query["4. 검색 질의 변환<br/>structure → 태그와 제약"]
    SearchSemantic["5-A. 시멘틱 검색<br/>semantic tag 로 후보를 찾는다"]
    SearchStructural["5-B. 구조 검색<br/>구조 해시로 후보를 찾는다"]
    BindRlsc["6-A. 자체 대치<br/>RLSC 슬롯에 넣는다"]
    BindCompSign["6-B. comp-sign 대치<br/>comp-sign 이 넣는다"]
    Render["7. 실서비스 렌더러로 렌더<br/>브라우저에서 미리캔버스 렌더러로 그린다"]
    Audit["8. 결함 채점<br/>같은 자로 두 결과를 잰다"]
    Report["리포트<br/>수치 + 사람 검수용 대조표"]

    UserInput --> Generator --> Validate --> Establish --> Query
    Query --> SearchSemantic --> BindRlsc --> Render
    Query --> SearchStructural --> BindCompSign --> Render
    BindRlsc --> Audit
    BindCompSign --> Audit
    Audit --> Report

1. Visual Summary 생성. 사용자가 쓴 글을 LLM 에 넣어 규약이 정한 YAML 형식으로 구조화한다. 항목과 그 항목의 세부, 값과 단위, 기간, 관계 같은 것이 필드로 분리된다. 지금 쓰는 모델은 gpt-5.4-mini 이고, 스키마를 어기면 오류 메시지를 되먹여 다시 생성하는 루프가 있다 (최대 3회).

2. 검증. YAML 을 파싱하고, 각 항목에 배열 순서대로 식별자를 붙이고, 스키마에 맞는지 검사한다. 파싱은 됐는데 값의 타입이 조용히 바뀌는 경우(예: 1.0 이 숫자 1 로 읽히는 것)를 따로 세는 계측이 붙어 있다.

3. 카테고리 확정. 생성기가 "이건 단계형(Steps)" 이라고 지목해도 그 카테고리가 요구하는 성립 조건(항목이 둘 이상 등)을 못 채우면 후보에서 빠지고, 미리 정해 둔 대체 순서를 따라 내려간다. 마지막 대체 후보 셋(목록형, 장문 설명형, 강조 메시지형)은 개수 조건만 보므로 언제나 하나는 성립한다.

4. 검색 질의 변환. 확정된 카테고리와 구조를 검색이 쓸 수 있는 형태로 바꾼다. 어떤 태그를 어느 우선순위로 볼 것인지, 항목이 몇 개이고 글자가 얼마나 되는지 같은 제약이 여기서 나온다.

5. 검색. 여기가 두 갈래로 갈린다. 자세한 것은 4절에 있다.

6. 내용 대치. 고른 컴포넌트의 텍스트 자리에 사용자 내용을 넣는다. 이것도 두 갈래다.

7. 실서비스 렌더러로 렌더. 대치된 결과를 브라우저에서 실제 미리캔버스 렌더러로 그린다. 이미지를 만드는 합성 서버를 왕복하지 않고, 합성 서버가 쓰는 것과 같은 페이지 렌더러를 화면에 직접 띄운다.

8. 결함 채점. 두 갈래의 결과물을 같은 자로 재서 결함을 센다. 5절에 있다.


3. 실서비스 코드와 PoC 코드의 경계#

PoC 라는 말이 "전부 임시로 만든 것"으로 읽히면 곤란하므로 경계를 분명히 적는다.

구간 상태 설명
Visual Summary 생성 실서비스 코드 실제 OpenAI 호출. 프롬프트와 재생성 루프가 그대로 돈다
검증·카테고리 확정·질의 변환 실서비스 코드 규약을 코드로 옮긴 것. 문서와의 정합을 테스트가 기계로 대조한다
시그니처 색인 계산은 실서비스 코드 · 저장은 PoC 코드 실제 createCompSignatureBuilder() 로 만든다. 다만 실서비스는 미리 테이블에 적재해 두는데, 여기서는 서버가 뜰 때 메모리에 즉석으로 만든다
후보 조회 PoC 코드 실서비스는 백엔드가 데이터베이스에서 조회한다. 여기서는 메모리 안에서 해시를 맞춰 본다
랭킹·내용 대치 (구조 검색 쪽) 실서비스 코드 이석님 패키지 rankCompCandidates·buildContentReplacement 를 그대로 호출한다. 내용시각화 BE가 쓰는 4단계 경로
랭킹·내용 대치 (시멘틱 검색 쪽) PoC 자체 구현 시멘틱 검색은 search.ts. 대치는 binding.ts(규약 RLSC). comp-sign·BE 경로 아님
렌더 실서비스 코드 합성 서버가 쓰는 것과 같은 페이지 렌더러
결함 채점 자체 구현 실서비스에 대응물이 없다. 이 PoC 가 새로 만든 계측기다

색인 규모는 로컬에 복사해 둔 태깅 컴포넌트 2,468건이다. 여기서 시그니처 3,424벌이 나오고 고유 구조 해시가 668개다. 빌드에 33초쯤 걸리고 30분 캐시한다. 실서비스 규모는 178,095건이라 이 방식을 그대로 쓸 수 없다. 10절의 열린 결정이다.

범위 밖으로 둔 것은 차트와 이미지 데이터 대치, 색상 대치, 실제 테이블 적재, 백엔드 검색 실행, 노드 단위 태그 소비다. 마지막 것은 데이터가 아직 없어서다.


4. 두 검색 경로를 왜 나란히 두었나#

이 PoC 는 같은 입력을 두 가지 방식으로 검색하고 대치해서 나란히 보여 준다. 어느 쪽을 실서비스에 넣을지는 말로 정할 문제가 아니라 실측으로 정할 문제라고 봤기 때문이다.

시멘틱 검색(자체 구현 경로) 은 design_object_semantic_tags 에 붙은 의미 태그로 후보를 찾는다. 태그가 맞는 후보를 먼저 채우고, 그 안에서 항목 수와 글자 용량이 얼마나 맞는지로 점수를 매긴다. 여기서 태그의 "군(tier)"은 그 후보를 끌어온 근거가 무엇이냐를 나눈 것이다. 생성기가 지목한 카테고리의 태그를 가졌으면 앞 군, 대체 후보 카테고리의 태그면 뒤 군이다.

구조 검색(comp-sign 실제 코드 경로) 은 의미 태그를 안 본다. 사용자 내용의 구조를 markdown 으로 바꿔 comp-sign 에 넣으면, 그것이 구조 해시와 변형들을 만들어 준다. 색인에서 해시가 정확히 일치하는 컴포넌트를 후보로 삼고, comp-sign 의 채점 20여 항목(면적 비율, 줄 수 초과, 스타일 불일치 등)으로 순위를 매긴다.

두 축이 깨끗하게 갈려 있다는 것이 뜻밖의 자산이 됐다. 스쿼드가 합의한 판정 지표가 "시멘틱 검색과 구조 검색의 교집합 상위 10건 품질"인데 (../README.md 6절), 그 두 검색이 이 PoC 에 이미 각각 실서비스 코드로 있는 셈이다. 그래서 교집합을 실제로 계산할 수 있다.

다리를 어떻게 놓았나. comp-sign 의 입력은 markdown 인데 Visual Summary 는 항목 트리를 내놓는다. 그 사이를 잇는 것이 structure → markdown 변환이다. 트리를 직접 조립하지 않고 markdown 을 거치는 이유는, comp-sign 이 트리와 해시를 같은 파싱에서 만들기 때문이다. 트리를 직접 만들면 해시 경로와 어긋날 수 있다고 패키지 소스가 명시하고 있다.

4.1 대치 경로 비교 화면#

플레이그라운드 탭은 파이프라인 전 구간을 세로로 보여 준다. 대치 경로 비교 탭은 내용 대치만 컴포넌트마다 나란히 보도록 분리했다. MOR-1926(I절 contract)과 4단계 vs comp-sign 실물 차이를 실례 기반으로 개발할 때 쓴다.

열 경로 입력 PoC 코드 비고
왼쪽 규약 RLSC 대치 Visual Summary structure binding.ts PoC 전용. 실서비스·내용시각화 BE 아님
오른쪽 comp-sign textTree 대치 markdown → textTree + signature compSignService.ts 4단계 실서비스 경로
(예정) property 대치 (T-8) I.I.S. 속성 + projection 미구현 I-8 pool bake + I-7 규칙 선행 (TODO I-9)

관련 문서. fallback 이후 대치 계층과 평가 pool bake는 ../design/content-binding-contract.md 3.4~3.6절. 용어는 2.5절.

화면에서 할 수 있는 것

  • 실사용 샘플 10건 또는 YAML 직접 입력으로 실행
  • Summary 안 컴포넌트마다 두 경로의 캔버스와 슬롯 매핑과 결함 채점을 좌우 대조
  • 후보 선택: 검색 1위 / 같은 design_object_id / 경로별 독립 선택
  • comp-sign 쪽 다리 markdown 접기/펼치기

5. 결과물 품질을 어떻게 재나#

그동안은 사람 눈이 유일한 계측기였다#

이전 라운드에서는 대치 결과 23장을 합성 서버로 렌더해 손으로 보고 판정했다 (comp-sign-bridge.md 6.1절). 그 방식은 결함을 실제로 찾아냈지만 반복이 안 된다. 코드를 고칠 때마다 23장을 다시 볼 수 없으니, "이 변경이 좋아졌나"를 말할 수 없고 열린 결정도 못 닫는다.

그래서 같은 판정 기준을 기계로 옮겼다#

결함 16종을 정의했고, 그 16종은 나쁜 결과의 원인 넷에 하나씩 대응한다. 종류 이름은 코드에 쓰는 식별자를 그대로 적는다.

원인 결함 종류 무슨 일이 벌어진 것인가
텍스트 용량 초과 capacity.lineOverflow 넣은 글이 그 자리의 줄 수를 넘는다
capacity.charOverflow 줄 수를 모르는 자리에서 글자 수가 용량을 넘는다
매핑과 위계 오류 mapping.categoryDrift 요청한 카테고리에서 규약이 허용하지 않는 카테고리의 컴포넌트를 골랐다
mapping.roleInversion 세부 내용이 제목 자리로 올라가거나 페이지 제목이 설명 자리로 내려간다
mapping.markerAbuse 순서 번호가 들어갈 자리에 문장이 들어간다
mapping.orderInversion 화면에 놓인 순서가 입력 순서와 뒤집힌다
슬롯 수 불일치 slots.unitDeficit 항목이 칸보다 많다
slots.unitSurplus 칸이 항목보다 많아 빈 칸이 남는다
slots.lineDeficit 여러 항목이 한 자리에 접히거나 아예 버려진다
slots.lineSurplus 채우지 못한 텍스트 자리가 남는다
대치 실패 replace.residue 결과에 남은 글이 사용자 입력에서 온 것이 아니다
replace.placeholder comp-sign 이 임시 문구로 채웠다
replace.emptied 채울 내용이 없어 템플릿 원문을 비웠다
replace.markupLeak markdown 표기가 결과 글에 그대로 나갔다
replace.unmapped 대치할 텍스트를 실제 요소에 못 붙였다
replace.contentLoss 사용자가 넣은 조각이 결과 어디에도 없다

심각도는 셋이고 코드에서 쓰는 이름을 그대로 적으면 blocker, major, minor 다. 사용자 내용이 사라졌거나 남의 내용이 남은 것이 가장 무거운 blocker(가중치 5), 위계가 어긋난 것이 major(2), 자리가 비거나 임시 문구가 들어간 것이 minor(1) 다. 결함 수를 그냥 더하면 가벼운 것이 많은 결과가 더 나빠 보이므로, 이 가중치를 곱해 더한 값을 한 건의 점수로 쓴다.

설계 원칙이 하나 있다#

런타임 랭킹이 최적화하는 바로 그 양을 잰다. 채점기가 랭킹과 다른 자를 쓰면, 랭킹이 그 결함을 보고도 그 후보를 골랐는지 아니면 애초에 볼 수 없었는지를 가릴 수 없다. 그러면 6절의 귀속이 성립하지 않는다.

그래서 넘침 판정을 comp-sign 의 채점과 같은 계산으로 맞췄다. comp-sign 번들을 직접 뜯어 확인한 결과, 그 판정은 글자 수가 아니라 줄 수를 본다. 텍스트를 줄바꿈으로 끊고 조각마다 올림(조각 길이 ÷ 줄당 글자 수) 을 더해 그 자리의 줄 수와 비교한다. 채점기도 같은 식을 쓴다.

두 경로를 같은 용량 근거로 잰다. 자리마다의 용량(줄 수, 줄당 글자 수, 글자 수, 역할 이름)은 comp-sign 시그니처가 담고 있는 값을 쓴다. 자체 구현 경로도 그 값을 쓴다. 서로 다른 자로 재면 나란히 놓은 비교가 성립하지 않기 때문이다.

이게 성립하려면 전제가 하나 필요하다. RLSC 노드 식별자와 sheet 요소 식별자가 같아야 두 경로의 자리를 같은 용량표에 붙일 수 있다. 색인 2,468건의 텍스트 자리 17,899개 전량에서 확인했고 결합률이 100% 다.

기계 수치를 그대로 믿지 않는다#

기계가 세기 시작했다고 그 숫자가 맞는 것은 아니다. 결함으로 잡힌 것들의 실제 텍스트를 찍어 눈으로 보고 잘못 잡은 것 셋을 걷어냈다.

첫째, 순서 번호(01, 02 …)를 "입력에서 오지 않은 글"로 세고 있었다. 그건 사용자 입력이 아니라 항목 순서에서 파생된 것이라 입력 목록에 없는 게 정상이다.

둘째, 정상 배치를 위계 어긋남으로 세고 있었다. 페이지 제목이 제목 자리에 가는 것과 항목 이름이 설명 자리에 가는 것은 둘 다 설계된 동작인데 컴포넌트당 3건씩 잡혔다. 진짜 어긋남 둘(세부가 제목 자리로 올라감, 페이지 제목이 설명 자리로 내려감)로만 좁혔다.

셋째, 잔존한 템플릿 원문에 markdown 기호가 들어 있으면 "잔존"과 "markdown 노출"로 한 결함을 두 번 세고 있었다. 미리캔버스 템플릿의 일본어 안내문구가 - 로 시작해서 걸린 것이다. markdown 노출은 우리가 쓴 글에만 판정하도록 고쳤다.

셋 다 회귀 테스트로 고정했다. 판정 임계값을 근거 없이 옮기면 그 테스트가 깨진다.


6. 결함을 누구 탓으로 돌리나#

품질이 나쁠 때 생성이 문제인지, 검색이 문제인지, 대치가 문제인지를 가려야 어디를 고칠지 정할 수 있다. 이것이 MOR-1927 이 요구하는 것이다.

귀속은 단일 결과가 아니라 대안과의 비교에서만 나온다. 어떤 후보로 대치했더니 결함이 다섯 개라는 사실만으로는 누구 탓인지 알 수 없다. 다른 후보로 갔으면 어땠는지를 함께 봐야 한다.

다행히 그 자료가 이미 계산되고 있었다. 두 경로 다 상위 후보 전부에 대해 대치를 돌려 보고 그중 하나만 쓰고 나머지는 버리고 있었다. 채점기는 그 버려지던 것을 주워 담는다.

풀 안 어느 후보로도 못 피한 결함          → 생성 탓 (내용 자체가 그런 것이다)
같은 경로의 다른 후보로는 피할 수 있던 것   → 검색 탓 (더 나은 후보를 못 골랐다)
같은 후보를 다른 경로가 더 잘 채운 것       → 대치 탓 (넣는 방식이 문제다)

여기에 정직성 장치가 하나 붙는다. 랭킹이 볼 수 없었던 결함은 검색 탓에서 덜어낸다. comp-sign 의 채점은 시그니처에 실린 자리만 본다. 시그니처 밖 자리의 넘침을 검색 탓으로 돌리면, 랭킹에게 없는 정보로 벌점을 매기는 셈이고 그 수치로 가중치를 튜닝하면 잘못된 방향으로 간다.

한계도 적어 둔다. "생성 탓"은 어디까지나 "이 색인 풀 안에서"라는 뜻이다. 풀 밖에 완벽한 컴포넌트가 있어도 생성 탓으로 잡힌다. 그래서 리포트에 풀의 지문을 싣고, 풀이 다른 두 실행의 귀속 수치는 도구가 비교를 막는다.


7. 정책과 설계 결정#

여기가 "왜 그렇게 했나"다. 각 결정에 근거와 기각한 안을 함께 적는다.

7.1 다리는 항목 트리를 markdown 으로 바꿔서 놓는다#

comp-sign 이 요구하는 트리 구조를 직접 조립할 수도 있었지만 그러지 않았다. comp-sign 은 markdown 을 받아 트리와 해시를 같은 파싱에서 만든다. 트리를 직접 넣으면 해시 경로와 어긋날 수 있다고 소스 주석이 명시하고 있다. markdown 은 사람이 읽고 디버그할 수 있다는 이점도 있다.

기각한 안: 트리 직접 조립(파싱 우회).

7.2 표를 파이프 표가 아니라 중첩 목록으로 직렬화한다#

comp-sign 의 파이프 표 인식은 4.3.8 판에만 있는데, 사내 저장소에 올라와 있는 최신판은 4.3.5 다. 팀과 자동화가 설치할 수 있어야 하므로 4.3.5 를 쓰고, 표는 중첩 목록으로 적는다(행이 항목, 칸이 열이름: 값 하위 항목).

기각한 안: 4.3.8 을 소스에서 빌드해 넣기. 빌드는 성공했지만 다른 사람이 같은 결과를 재현할 수 없어 기각했다. 대신 4.3.8 을 사내 저장소에 올려 달라고 요청하는 것이 남았다.

7.3 기존 comp-sign 2.5.0 을 건드리지 않는다#

이 저장소의 파이프라인 백필 워커가 comp-sign 2.5.0 에 고정돼 있다. 버전을 올리면 그 워커가 만드는 구조 해시의 세대가 바뀌어 이미 적재된 데이터와 어긋난다. 그래서 패키지 별칭으로 4.3.5 를 따로 설치해 두 버전을 공존시킨다.

기각한 안: 단일 버전으로 강제 상향.

7.4 두 검색 경로가 같은 후보 풀을 쓰게 한다#

계측을 시작하고 보니 두 경로가 서로 다른 조건으로 색인을 읽고 있었다. 한쪽은 컴포넌트 구조가 없으면 페이지 전체 구조로 대신하고 있었고, 다른 쪽은 sheet 와 구조가 둘 다 있는 것만 읽고 있었다. 서로 다른 집합에서 후보를 고르면 나란히 놓은 비교가 성립하지 않는다.

엄격한 교집합(sheet 와 컴포넌트 구조가 둘 다 있음)으로 통일했다. 근거 셋이다. 첫째, 채점의 용량 근거가 시그니처인데 시그니처는 sheet 없이 못 만든다. 둘째, 렌더 경로가 sheet 없으면 실패한다. 셋째, 페이지 전체 구조는 컴포넌트 밖 자리까지 딸려 와 자리 수와 용량이 부풀려진다.

로컬에서는 두 조건이 같은 2,468건을 내므로 후보 손실이 없다. 옛 조건으로 되돌리는 환경 변수를 한 릴리스 동안 남겼다.

7.5 상위 순위가 실행마다 같아야 한다#

두 색인 조회에 정렬이 없었고 랭킹 비교에 동점 처리가 없었다. 동점 후보의 순서가 데이터베이스가 돌려준 순서(정의되지 않은 순서)로 떨어져서 상위 10건이 실행 간 재현되지 않았다. 평가 지표가 그 순위 위에 서므로 먼저 고정했다. 같은 평가셋으로 두 번 돌려 결함 수 차이가 전부 0 이고 고른 후보가 하나도 안 바뀌는 것을 확인했다.

7.6 채점은 무료이고 결정적이어야 한다#

생성은 LLM 호출이라 돈이 들고 실행마다 결과가 다르다. 그래서 생성과 채점을 두 단으로 분리했다. 생성 결과를 저장소에 스냅샷으로 커밋해 두고, 채점은 그 스냅샷 위에서 돈다. 채점은 LLM 을 한 번도 부르지 않는다.

스냅샷 캐시 키에 프롬프트 본문의 해시를 넣었다. 프롬프트를 고치면(MOR-1910) 스냅샷이 자동으로 낡은 것이 되고 도구가 멈춘다. 모델 이름만 키로 쓰면 프롬프트 변경이 조용히 무시된다.

7.7 비교 조건이 다르면 도구가 비교를 거부한다#

두 실행의 수치를 비교할 때, 프롬프트, 모델, 색인 풀과 채점 임계값과 comp-sign 버전과 상위 몇 건까지 봤는지와 평가셋이 하나라도 다르면 비교를 거부한다. 계측기가 스스로 비교 불가를 판정하는 것이 이 도구의 유일한 신뢰 근거다.

이 판단이 실제로 값을 했다. 처음에는 평가셋을 지문에 안 넣었는데, 10건짜리 실행과 50건짜리 실행을 비교했더니 통과했고 모든 결함이 100건씩 늘어난 것처럼 보였다. 표본 크기 차이를 회귀로 읽은 것이다.

7.8 교집합은 진단으로 보고하고, 운영 후보는 순위 융합으로 만든다#

두 검색의 점수는 비교할 수 없는 척도다. 한쪽은 0 과 1 사이의 적합도이고, 다른 쪽은 페이지 크기와 요소 수에 따라 절대 크기가 달라지는 감점 합계다. 정규화해서 가중합하면 손잡이가 늘고 후보 수에 따라 요동친다.

그래서 순위만 쓰는 융합 방식을 택했다. 각 검색에서의 순위를 1 ÷ (60 + 순위) 로 바꿔 더한다. 손잡이가 상수 하나뿐이고 척도 문제가 없다.

기각한 안 둘. 순수 교집합만 지표로 쓰는 것은, 교집합이 비면 지표 자체가 정의되지 않아 기각했다. 두 축을 한 랭킹으로 합치는 것은 방향으로는 맞지만 지금 하면 "어느 신호가 품질을 만드는가"를 측정할 수 없게 되므로 미뤘다. 측정기를 먼저 세우고 융합 랭킹은 그 측정 결과를 근거로 설계한다.

7.9 최적화보다 계측을 먼저 한다#

결함을 재는 것과 결함을 고치는 것을 동시에 하지 않는다. 지표를 만들면서 최적화하면 지표를 튜닝해서 지표를 좋게 만드는 길이 열린다. 그래서 채점 결과를 검색 순위에 되먹이는 일은 범위 밖으로 뒀다.

같은 이유로, 계측이 드러낸 결함 중에서도 일부러 안 고친 것이 있다. comp-sign 경로가 markdown 기호를 결과에 그대로 내보내는 문제는 수치만 세고 고치지 않았다. 그 수치가 4.3.8 을 올려 달라고 요청하는 근거가 되기 때문이다.


8. 어떻게 돌리나#

화면#

미리디 내부 도구(Eregion)의 사이드바에서 "Visual Summary PoC" 를 연다. 로컬 개발 환경에서는 계정 없이 열린다.

탭이 셋이다. 플레이그라운드는 아무 글이나 넣어 파이프라인 전체를 돌려 볼 수 있고, 실사용 샘플 탭은 실제 로그 원문 10건을 한 번에 돌린다. 색인 현황 탭은 지금 색인에 무엇이 얼마나 들어 있는지 보여 준다.

결과 화면은 컴포넌트마다 카드로 서고, 카드 안에서 카테고리 확정 이력, 검색 질의, 후보 순위와 점수, 대치 전후 텍스트, 그리고 왼쪽에 원본 컴포넌트 오른쪽에 대치 결과를 실제로 그린 캔버스가 나란히 선다. 화면 머리의 칩이 색인 빌드 진행 상황을 보여 준다.

플레이그라운드에 복잡도 레벨(K-4/K-5, 2026-09-05 추가)이 있다. 모드가 "텍스트 → LLM 생성"일 때 Complexity-1-Single/Complexity-2-Juxtaposition/Complexity-3-Containment 중 하나를 골라 그 레벨로만 생성하거나(schema-v0.5.md 8.8절), "⧉ L1/L2/L3 동시 생성" 버튼으로 같은 입력을 세 레벨 다 돌려 위쪽 요약 표(컴포넌트 타입 비교)에서 훑고 표의 행을 클릭해 그 레벨 전체 결과를 아래에 펼쳐 볼 수 있다. 예시 입력도 여덟 개로 늘렸다 — 뒤 넷("분기 리포트(복합)" 등)은 실사용 로그와 비슷하게 여러 사실이 한 문단에 섞여 있어 레벨마다 판단이 실제로 갈린다.

로컬 실험 로그 — 브라우저에서 돌린 결과를 Claude 가 다시 읽는 법 (K절, 2026-09-05)#

플레이그라운드에서 실행한 모든 결과(성공·실패 둘 다)가 두 곳에 남는다.

  • pnpm dev 터미널 — 한 줄 요약이 실시간으로 찍힌다: [visual-summary:run] ok level=L1 components=1[...] search=[DO123(0.85)] attempts=1 — "입력 앞부분…"
  • logs/visual-summary-runs.jsonl(gitignore 대상, 커밋 안 됨) — 같은 내용이 구조화된 JSON 한 줄로 append 된다. 필드: 레벨·모델·성공 여부·입력 미리보기·최종 확정 카테고리(resolvedCategories)·컴포넌트별 검색 후보 요약(search[].top = 1순위 design object id·군·점수, slotsFilled = 대치 슬롯 채움 비율)·재생성 시도 횟수와 스키마/capability 위반 카운트·인덱스 크기. 코드는 src/app/api/visual-summary/runLog.ts, 호출부는 service.ts::runVisualSummaryPoc.

왜 필요했나. 정원석이 브라우저에서 실험하고 결과(YAML·화면)를 그대로 옮겨 붙이지 않아도, Claude 가 tail -20 logs/visual-summary-runs.jsonl 로 최근 실행을 바로 읽고 진단·수정할 수 있게 하려는 것이다. 실제로 이 로그로 "L1을 선택해도 여러 컴포넌트로 쪼개지는" 버그(L1 위상 지시가 비어 있던 것, prompt.ts::topologyBlocksFor)를 잡았고, generate.ts 에 "레벨 선언과 산출물이 어긋나면 재생성" 검증(schema-v0.5.md 8.9절이 "아직 구현되지 않았다"로 남겨 뒀던 것)도 이때 채워 넣었다.

읽는 법.

bash
tail -20 logs/visual-summary-runs.jsonl | python3 -m json.tool  # 최근 실행 하나씩 보기 (파일 통째로는 json이 아니라 JSONL이라 tool 하나로는 안 열림)
grep '"ok":false' logs/visual-summary-runs.jsonl                 # 실패만 추리기

프로덕션·서버리스 배포에서는 안 쌓인다(NODE_ENV !== 'development'면 콘솔에만 찍는다) — 파일시스템이 읽기 전용이거나 인스턴스마다 갈려 append 가 의미 없어서다.

명령어#

bash
# 1) 생성 — 돈이 들고 드물게 돈다. 결과 스냅샷은 저장소에 커밋한다
DOTENV_CONFIG_PATH=.env.local pnpm tsx scripts/visual-summary-eval.ts --generate --k 1

# 2) 채점 — LLM 호출이 없고 결정적이다. 상위 10건을 보려면 힙을 키워야 한다
DOTENV_CONFIG_PATH=.env.local NODE_OPTIONS="--max-old-space-size=12288" \
  pnpm tsx scripts/visual-summary-eval.ts --score --top 10 --out eval/baseline --html

# 3) 회귀 비교 — 조건이 다르면 거부한다
pnpm tsx scripts/visual-summary-eval.ts --diff eval/before/report.json eval/after/report.json

기본 힙으로 상위 10건을 50건에 돌리면 조용히 죽는다. 후보마다 sheet 를 복제하기 때문이다. 상위 3건까지만 볼 때는 기본 힙으로도 돈다.

--html 을 주면 사람 검수용 대조표가 함께 나온다. 입력마다 좌우 2열로 두 경로의 1위 후보와 결함 배지가 서고, 각 열에 판정 라디오(정확, 넘침, 잔존, 누락, 애매)가 있다. 판정을 다 넣고 "라벨 JSON 복사" 를 누르면 그 라벨이 채점기 임계값을 조정하는 근거가 된다.

색인 넓히기#

로컬 색인이 모자라면 운영 읽기 전용 데이터베이스에서 태그별로 고르게 표본을 떠 온다. 같은 명령을 여러 번 돌려도 이미 있는 행은 건드리지 않는다.

bash
DOTENV_CONFIG_PATH=.env.local pnpm tsx scripts/visual-summary-expand-index.ts --per-tag 60

코드가 어디 있나#

무엇 어디
규약을 코드로 옮긴 것 src/lib/visual-summary/
항목 트리를 markdown 으로 바꾸는 다리 src/lib/visual-summary/markdown.ts
결함 채점과 귀속 src/lib/visual-summary/audit.ts
교집합과 순위 융합 src/lib/visual-summary/intersect.ts
두 경로가 공유하는 후보 풀 src/app/api/visual-summary/indexPool.ts
comp-sign 실제 코드 호출 src/app/api/visual-summary/compSignService.ts
화면 src/components/pages/VisualSummaryPocPage/
평가 도구 scripts/visual-summary-eval.ts
평가셋 50건과 생성 스냅샷 src/lib/visual-summary/fixtures/

9. 지금 수치#

평가셋#

실사용 로그 원문 50건이다. 시멘틱 쿼리 v2 가 라벨을 붙여 둔 1,051건에서 층화 표본으로 뽑았다. 중분류 32종을 덮고, 표와 차트로 가는 것이 19건, 길이는 짧음 19건과 보통 17건과 김 14건으로 갈라 뽑았다. 분할 과정에서 앞머리가 잘린 조각은 걸러냈다.

길이와 종류를 갈라 뽑은 이유는, 긴 입력은 자리 부족과 접힘과 넘침을 만들고 짧은 입력은 빈 자리를 만들기 때문이다. 한쪽에 쏠리면 결함 종류 중 일부가 아예 안 나타난다.

기준선 (입력 50건, 컴포넌트 65개, 상위 10건)#

시멘틱 검색 구조 검색 순위 융합
심각도 가중 평균 (낮을수록 좋다) 20.0 22.0 20.7
1위 후보가 결함 0 인 비율 0.0% 1.6% 1.5%
상위 10건 중 결함 0 비율 0.5% 2.5% 1.2%

입력 단위로 보면 가장 무거운 등급(blocker)의 결함이 하나도 없는 입력이 시멘틱 검색 8%, 구조 검색 7% 다. 이 수치는 경로별로 낸다. 예전에는 시멘틱 1위만 보고 하나의 수를 내서 두 경로 비교가 한쪽으로 치우쳤다. 결함이 아예 없는 입력은 0% 라 추적에 쓸 수 없어서, 무거운 결함이 없는 비율을 나쁜 결과 체감의 대용치로 본다. 무거운 등급은 사용자가 넣은 내용이 결과에 없거나 남의 내용이 남은 경우이므로 체감에 가장 가깝다.

결함 수는 이렇다. 1위 후보 기준 합계다.

결함 시멘틱 검색 구조 검색
줄 수 넘침 176 177
미채움 자리 비움 173 0
markdown 표기 노출 0 137
템플릿 원문 잔존 79 40
내용 누락 48 33
채우지 못한 자리 46 18
항목이 칸보다 많음 7 39

여기서 읽어야 할 것 셋#

의미 적합성을 재기 시작하자 두 경로의 격차가 사라졌다. 2026-08-15 이전에는 구조 검색이 17.5 로 시멘틱 검색(19.9)을 크게 이겼는데, 검색이 허용한 카테고리 밖에서 고른 것을 결함으로 세자 시멘틱 20.0, 구조 22.0 으로 순위가 바뀌었다. 새 결함은 검색이 허용한 카테고리 집합 밖에서 고른 것이다. 허용 집합은 채점기가 따로 계산하지 않고 검색(search.acceptedCategoriesFor)이 만든 것을 그대로 받는다. 두 벌로 만들었더니 PR 리뷰가 같은 종류의 결함을 다섯 냈다. 구조 검색이 크게 앞서 보이던 것의 대부분은 의미를 안 잰 덕이었다. BAD 대용치(무거운 결함이 없는 입력 비율)도 같은 말을 한다. 시멘틱 8%, 구조 7%. 한때 구조가 14% 로 두 배 나아 보였는데, 그중 절반이 계측 실패였다 (아래). 자세한 것은 node-tag-and-property-poc-blueprint.md 6절 T-1.

넘침이 양쪽 모두 1위 결함이고 수가 거의 같다 (176 대 177). 나쁜 결과의 1순위 원인이 실제로 1순위인 것이 이 프로젝트에서 처음 확인됐다. 더 중요한 건 경로를 바꿔도 안 줄어든다는 점이다. 대치 방식이 아니라 검색 풀과 용량 자체의 문제라는 뜻이다.

결함의 대부분이 검색 탓이다. 귀속을 세어 보면 생성 36, 검색 466, 대치 17, 랭킹이 못 본 것 74 다. 즉 "상위 10건 안에 더 나은 후보가 있었는데 못 골랐다"가 압도적이다. 대치 알고리즘을 다듬는 것보다 검색을 고치는 편이 값이 크다는 뜻이고, 랭킹 가중치 실험(MOR-1936)이 왜 가장 싼 다음 수인지의 근거다.

두 검색은 맨 위에서 거의 합의하지 않는다. 1위가 겹치는 컴포넌트가 65개 중 2개(3%)뿐이고, 3위까지 14개, 5위까지 25개, 10위까지 84개다. 교집합 후보 중 결함 0 인 비율도 4% 에 그친다. 그래서 "교집합 상위 10건"을 그대로 지표로 쓰면 표본이 거의 안 남는다. 이것이 MOR-1929 판정 절차를 확정할 때의 첫 입력이다.

그 밖에 확인한 것#

생성은 50건 전량 성공했다(실패 0). 자동 테스트는 273건이 통과한다. comp-sign 이 계산해 주는 박스 보정은 실제 컴포넌트 250건과 보정 자리 1,907개 기준 99.1% 가 적용되고, 회전이 걸린 자리 0.9% 만 건너뛴다.


10. 남은 것#

세 갈래로 나눠 적는다. 내가 할 수 있는 것, 남의 답을 기다리는 것, 스쿼드가 정해야 하는 것이다.

10.1 내가 할 수 있는 것#

평가 pool 고정과 사전 적재 (I-8). comp-sign signature, I.I.S. 경계, I.I.S. 속성을 같은 design_object_id subset에 bake 한다. 전량 17만 건이 아니라 content-binding-contract.md 3.6절 규칙을 따른다. 이게 끝나야 5단계 property 대치(T-8, I-9) 비교가 의미 있다.

사람 판정과 기계 판정의 일치율 재기. 이것이 가장 먼저다. 기계 채점이 사람 검수를 대체한다고 주장하면 아무도 안 믿는다. 기계 채점은 사람 검수를 선별하고(23장을 보는 대신 교집합 10~30장만 보게) 재현한다(다음 라운드는 무료). 그 주장의 근거가 일치율 수치다. 대조표에 판정 입력칸이 이미 있으므로, 교집합 후보를 눈으로 판정해 라벨을 뽑고 기계 판정과 대조하면 된다.

랭킹 가중치 실험 (MOR-1936). comp-sign 패키지가 가중치를 옵션으로 받고 기본값도 내보내고 있어서, PoC 안에서 바로 돌릴 수 있다. 귀속 수치가 검색 탓을 지목하고 있으므로 값이 큰 실험이다.

대체 순서 관련 열린 결정의 실측 (MOR-1934). 도착 카테고리의 개수 상한을 넘을 때 초과분을 접을 것인지 후보에서 뺄 것인지가 규약과 표에서 서로 다르게 정의돼 있다. 두 정책을 다 돌려 결과물 품질을 비교하면 근거가 나온다.

10.2 남의 답을 기다리는 것#

comp-sign 4.3.8 을 사내 저장소에 올려 달라고 요청하기 (이석님). 파이프 표 인식과 제목 표시가 그 판에만 있다. 정량 근거가 확보됐다. markdown 기호가 결과에 그대로 나가는 것이 컴포넌트당 137건이다.

임시 문구에 큰따옴표가 들어가는 것 알리기 (이석님). comp-sign 이 미매핑 자리를 채우는 문구가 "설명을 입력해주세요." 처럼 큰따옴표를 포함한 채 캔버스에 그려진다. 패키지 소스만 봐서는 판별이 안 돼 실제로 돌려서 확인했다. 이전 검수에서 설명을 입력해주세요.. 로 보였던 것이 이 닫는 따옴표다.

평가셋을 38 중분류로 넓히기 (MOR-1928, 허재영님). 나오면 지금 50건 셋을 그것으로 교체한다. 그때까지 남의 일정에 안 막히게 자체 셋으로 간다.

I.I.S. 본편(RAT pod) 는 기다리지 않는다 (D-5, 2026-08-22). PoC iis/ 가 경계, 속성, T-5/T-6 을 대행 생산한다. 남은 것은 T-4 사람 검수, T-5/T-6 메인 파이프라인 연동, T-7 정확도 검수다 (poc-module-map.md 3절).

10.3 스쿼드가 정해야 하는 것#

운영 규모의 색인을 어떻게 만들 것인가. PoC 는 서버 메모리에 즉석으로 만들지만(2,468건에 33초) 실서비스는 178,095건이라 미리 적재해야 한다. 선택지가 둘이다. 이 저장소의 시그니처 워커를 4.3.x 로 올려 다시 적재하거나, 이석님 쪽 테이블을 직접 조회하는 것이다. 실서비스 편입이라면 뒤쪽이 정도인데, 백엔드 연동이 필요하다.

규약의 대체 순서와 comp-sign 의 태그 군 체계를 맞추는 것. 규약은 "성립 조건을 못 채우면 후보에서 빠진다"는 이분법인데, 구현은 빼지 않고 뒤 군으로 미룬다. 두 축이 실제로 다른 결과를 내는 것이 실측으로 확인됐다.

판정 절차와 지표의 확정 (MOR-1929). 9절이 보여 주듯 두 검색이 맨 위에서 거의 합의하지 않으므로, "교집합 상위 10건"을 그대로 쓸지 아니면 세 축(시멘틱, 구조, 융합)을 나란히 볼지를 정해야 한다.


11. 참고#

↑ 목차로 · 5부 머리로

22장PoC 모듈 지도 정본모듈별 구현 상태와 I.I.S. 진행 방침

원문 plan/poc-module-map.md GitHub 에서 보기 9 KB 원문 제목 「Visual Summary PoC 모듈 지도」

이 문서의 역할 Visual Summary PoC 에서 어떤 모듈이 어디에 있고, 무엇까지 구현됐는지, 지금 무엇에 집중해야 하는지를 한 장으로 본다. 로드맵과 할 일의 정본은 ../README.md, ../TODO.md 다. 오너 정원석 (MORDOR) · 상태 진행 중 · 최종 갱신 2026-08-22 (평가 pool·대치 3경로 우선순위)

먼저 볼 것 아래 전체 파이프라인 다이어그램과 모듈별 구현 상태 표, 그다음 지금 집중할 것 순이다.


1. 전체 파이프라인#

질의 갈래(사용자 입력 → 컴포넌트 선택 → 대치 → 채점)와 색인 갈래(I.I.S. 로 node 태그를 미리 뽑아 둠)가 검색 직전에 합류한다.

mermaid
flowchart TB
  subgraph query["질의 갈래 (매 요청)"]
    IN["사용자 입력"]
    GEN["① Summary 생성기<br/>generator/"]
    VAL["② 검증·id 부여<br/>validate · assignIds · parse"]
    FB["③ Fallback 사다리<br/>establish + fallback"]
    PROJ["④ Structure projector<br/>projection (P1~P8)"]
    QRY["⑤ 검색 질의 변환<br/>query · markdown"]
    SRCH["⑥ 검색·랭킹<br/>search · compSignService"]
    BIND["⑦ 내용 대치<br/>binding · sheet · comp-sign"]
    AUD["⑧ 결함 채점·귀속<br/>audit"]
    IN --> GEN --> VAL --> FB
    FB -->|"카테고리가 바뀌면"| PROJ
    PROJ --> QRY --> SRCH --> BIND --> AUD
  end

  subgraph index["색인 갈래 (배치·적재)"]
    IISB["I.I.S. 경계 추출<br/>iis/extractBoundary"]
    IISB --> DB[("iis_semantic_component")]
    DB --> IISS["I.I.S. 노드 태그 검색 T-5<br/>iis/nodeSearch"]
    DB --> IISP["I.I.S. 속성 추출 T-7<br/>iis/extractProperty"]
    IISS --> SRCH
    IISP --> BIND
  end

  subgraph ui["Eregion PoC"]
    POC["/visual-summary"]
    BIND["/visual-summary<br/>대치 경로 비교 탭"]
    IISUI["/visual-summary/iis-boundary"]
  end

  query --> POC
  query --> BIND
  index --> IISUI

읽는 법

  • ①~⑧ 은 한 번의 사용자 입력을 처리하는 질의 갈래다. pipeline.ts 가 이 순서로 조립한다.
  • I.I.S. 는 컴포넌트 RLSC 를 미리 읽어 node 단위 semantic category 를 DB 에 적재하는 색인 갈래다. RAT pod 본편과 출력 모양을 맞추되, 재개를 기다리지 않고 PoC 에서 그대로 진행한다 (../TODO.md D-5, 2026-08-22).
  • ⑥ 검색 과 ⑦ 대치 는 PoC 에서 여러 경로가 나란히 있다. 시멘틱 검색(search.ts) + PoC 규약 RLSC 대치(binding.ts, 실서비스 아님), comp-sign 구조 검색과 대치(compSignService.ts, 4단계 실서비스), I.I.S. 노드 태그(T-5/T-6, 미연동), property 대치(T-8, 미착수). 비교와 채점용이다.

2.5 세 가지 대치 경로#

경로 코드 실서비스 입력 PoC UI
PoC 규약 RLSC binding.ts 아님 (MOR-1926 베이스라인) Visual Summary structure → RLSC 슬롯 플레이그라운드 ④, 대치 비교 왼쪽
4단계 comp-sign buildContentReplacement 예 (내용시각화 BE) markdown → textTree + signature 플레이그라운드 ⑤, 대치 비교 오른쪽
5단계 property T-8 (미구현) 미정 I.I.S. 속성 + property projection 미연동 (I-8·I-7·I-9)

세 경로를 같은 평가 pool에서 비교하려면 comp-sign signature와 I.I.S. 경계와 속성을 pool subset에 미리 bake 한다 (../design/content-binding-contract.md 3.6절, TODO I-8).


2. 모듈별 구현 상태#

2.1 이름 정리 (헷갈리기 쉬운 것)#

문서·구두 표현 코드·정확한 이름 역할 검색과의 관계
Fallback 사다리 (fallback ladder) fallback.ts, establish.ts, capability.ts 생성기가 고른 카테고리가 성립 조건을 못 채우면 capability-table 3절 순서대로 다른 카테고리로 내려간다. 빈 검색을 막는 마지막 안전망까지 포함 검색 직전 단계. buildSearchQuery() 가 확정 카테고리와 제약을 넘긴다
Structure projector projection.ts Fallback 사다리 한 칸을 따라갈 때 structure 를 다른 카테고리에 맞게 변형한다 (P1~P8). 내용은 최대한 유지하고 표현만 바꾼다 검색·대치 양쪽 모두 투영된 structure 를 쓴다. 검색 모듈 자체는 아니다
Property projector (이름 주의) PoC 에 별도 모듈 없음 "property" 를 structure 투영으로 오해하기 쉽다. 구조 투영은 위 Structure projector 이다 대치 준비용
I.I.S. 속성 추출 iis/extractProperty.ts 경계가 잡힌 node 에 카테고리 고유 속성(방향, 순서 등)을 LLM 으로 뽑는다. 5단계 node 태그 검색·대치의 입력 데이터

2.2 구현 상태 표#

상태 기호: 완료 PoC 에서 end-to-end 동작, 부분 코드 있으나 검수, 통합, 규모 미완, 미착수 설계만 또는 범위 밖

# 모듈 코드 PoC 상태 남은 것 (짧게)
① Summary 생성기 generator/generate.ts, generator/prompt.ts 부분 실사용 입력에서 라벨 정렬 약함. Mastra 경로는 feature/MOR-1910-mastra-skeleton (main 미포함)
② 검증·파싱 validate.ts, parse.ts, assignIds.ts, schema.ts 완료 재생성 루프로 스키마 위반 복구 실측됨
③ Fallback 사다리 fallback.ts, establish.ts, capability.ts 완료 열린 결정 B-27/B-28 (요청 vs 도착 시 개수 초과 처리). E-2 fallback map 스쿼드 확인
④ Structure projector projection.ts 완료 B-27/B-28 과 연동. SWOT→Quadrant 등 죽은 fallback 칸 논의 (MOR-1934)
⑤ 검색 질의 변환 query.ts, markdown.ts 완료 BE structureHash 교체는 D-4·실서비스 연동 시
⑥ 검색·랭킹 search.ts, compSignService.ts 부분 PoC 메모리 색인 ~2.5k건. 운영 17만+건 적재 미정. T-5 는 별도 탭만
⑦ 내용 대치 binding.ts, sheet.ts, comp-sign 경로 부분 I절 contract 없음. 대치 경로 비교 탭으로 RLSC vs comp-sign 나란히 검수. T-6 은 메인 파이프라인 미연동. box 보정은 comp-sign 경로만
⑧ 결함 채점·귀속 audit.ts, intersect.ts 완료 사람 검수와 일치율 측정이 다음 (poc-guide 10.1)
— I.I.S. 경계 (T-4) iis/extractBoundary.ts, iisSemanticComponent.ts 부분 추출·UI·배치·gpt-5.6-luna production. 층화 30건 사람 검수 미완
— I.I.S. 노드 검색 (T-5) iis/nodeSearch.ts 부분 /visual-summary 탭 + API. service.ts 메인 파이프라인 미연동
— I.I.S. 노드 대치 (T-6) iis/nodeBinding.ts 부분 T-5 와 동일. 평가셋 전량 실행·검수 미완
— I.I.S. 속성 (T-7) iis/extractProperty.ts 부분 v1 제네릭 추출. 정확도 검수 미완

UI 와 조립

항목 경로 상태
PoC 메인 /visual-summary, VisualSummaryPocPage 완료
I.I.S. 검수 /visual-summary/iis-boundary 완료
API 조립 src/app/api/visual-summary/service.ts 부분 (질의 갈래 중심, T-5/T-6 미포함)
End-to-end pipeline.ts 완료 (질의 갈래)

3. 지금 집중할 것#

아래는 지금 손대면 진도가 나는 것만 적었다. 규약 A/B/H 와 J(파인튜닝) 전체는 ../TODO.md 를 본다. I절 실행 순서는 TODO I절 우선순위 표와 같다.

우선 무엇 왜 지금 어디
1 평가 pool + 사전 적재 (I-8, blueprint T-2 subset) comp-sign / I.I.S. / 5단계 property 3-way 비교 전제. 전량 17만 건이 아니라 고정 subset indexPool.ts, comp_signatures, iis/runExtraction.ts, contract 3.6절
2 T-8 property projection 초안 (I-7) 5단계 대치 규칙 없으면 속성 bake 만으로 화면 불가 contract 3.5절, blueprint T-8
3 5단계 property 대치 PoC (I-9) I-8·I-7 후 service.ts / 대치 비교 UI 3열 iis/, BindingCompareTab
4 대치 경로 2-way 검수 (현재 UI) RLSC vs comp-sign 실측으로 I-2·MOR-1926 입력 /visual-summary 대치 경로 비교
5 내용 대치 contract (I-2, 이석·류민) BAD 85% 구간. I-8과 병행 가능 ../design/content-binding-contract.md
6 I.I.S. 경계 사람 검수 (T-4) pool bake 전에 품질 기준 필요 /visual-summary/iis-boundary
7 생성기 라벨 정렬 (MOR-1910) 검색·채점 입력 흔들림 generator/
8 Fallback 열린 결정 (B-27, B-28) PoC 수치 해석 projection.ts, capability-table
9 T-5/T-6 을 service.ts 에 연동 MOR-1926 판정 service.ts
10 운영 전량 색인 (T-2 full, 178k) dev/stg 판정 직전. 평가 pool과 별개 poc-guide 10.3

의도적으로 뒤로 미룬 것

  • RAT pod I.I.S. 본편 재개 시점 (MOR-1425 등): 기다리지 않음. 출력 계약은 9절, 교체는 나중에.
  • J절 파인튜닝: J-1(평가셋)만 병행 가능, 나머지는 규약과 contract 선행.
  • BE 배포와 2단계 semantic tag 제품 출시: 2026-08-07 스킵, 4단계 직행.

3.1 공통·4단계·5단계 요구사항 구분 (2026-09-05 신설)#

TODO.md의 남은 항목([ ]·[~])을 어느 단계가 소비하는가로 나눈다. 상세 내용·근거는 각 항목이 있는 TODO.md 절을 본다 — 여기는 분류표만 유지한다. 4단계는 comp-sign 구조 검색·대치(compSignService.ts, 실서비스), 5단계는 I.I.S. property 대치(T-8, 미착수)다(2.5절).

공통 (규약·생성기 — 어느 매칭 백엔드가 소비하든 먼저 정해져야 하는 것)#

절 항목 왜 공통인가
A A-2, A-4, A-6 규약 자체의 정합·링크 정리. 매칭 백엔드와 무관
B B-10, B-21, B-22, B-23, B-27, B-28, B-29, B-31, B-32, B-36 capability-table 의 성립 조건·fallback 사다리·꾸밈 축·Native 필터는 질의 스키마 층이다. comp-sign 이 뽑든 I.I.S. 가 뽑든 이 층을 거쳐야 한다. B-36 은 생성기 어휘만 제한하고 I.I.S. 어휘(45종 전량)는 그대로 둔다고 명시(I-16)
D D-4, D-6 structure→질의 변환기와 용어는 매칭 백엔드 선택 이전의 것
E E-1, E-2, E-3 문서화·fallback 테이블·유효 컴포넌트 게이트. 스키마 층
F F-1, F-2 생성기 포맷 안정성·silent corruption 지표. 생성기 자체의 문제
I I-1, I-2, I-3, I-8, I-11, I-13, I-14 I-1(속성 목록)·I-2(노드 분류, comp-sign 변형은 B-19 로 이미 닫힘)·I-3(fallback 규약)은 스키마 층. I-8(평가 pool bake)은 comp-sign·I.I.S.·5단계 property 3-way 비교의 공통 전제라고 문서가 명시한다. I-11(중첩·병치 오케스트레이션)은 매칭 백엔드 이전에 질의를 쪼개는 로직. I-13(복잡도 레벨)·I-14(type 필수·버전 규칙)는 생성기·zod 스키마 자체
J J-1~J-8 Summary 생성 모델 파인튜닝. 매칭 백엔드가 아니라 생성기 학습 문제

4단계 전용 (comp-sign 구조 검색·대치)#

절 항목 왜 4단계인가
C C-1, C-2, C-4, C-6, C-7 태깅·적재·BE 전달 인프라. 2단계 배포는 스킵됐지만 이 인프라는 4단계에서 그대로 쓰인다(C절 머리말)
D D-8 이종중첩 템플릿을 검색 풀(comp-sign 색인)에 되돌릴지
I I-4, I-5, I-6 I-4(comp_signs 컬럼)·I-5(comp-sign 실물 반영)·I-6(rankCompCandidates)는 전부 comp-sign 패키지 자체

5단계 전용 (I.I.S. property 대치, T-8)#

절 항목 왜 5단계인가
I I-7, I-9, I-10, I-15, I-16 I-7(T-8 cross-type property projection, I-15 StackedBarChart 쓰기 포함)·I-9(T-8 대치 PoC, service.ts 3번째 열)·I-10(속성 골든셋 UI)은 5단계 property 경로 자체다. I-16(대치 불가 카테고리 생성기 어휘 제외)은 생성기 어휘 제한이라 공통에 가깝지만, 근거 문서(10절)가 "I.I.S. 어휘는 이 필터와 무관"이라고 갈라 둔 대칭점이라 여기 남겨 5단계 관련성을 표시한다

분류가 아직 확정 판단은 아니다. I-11·I-13·I-14·I-16 처럼 경계가 애매한 항목은 위 표의 배치 이유를 참고하되, 실제 구현 시점에 재확인한다.

4. I.I.S. 진행 방침 (2026-08-22)#

한 줄로 말하면 RAT pod(류민님) I.I.S. 본편 재개를 기다리지 않고, Visual Summary PoC 색인 갈래에서 같은 출력을 계속 만든다.

항목 내용
결정 D-5 닫힘. PoC 대행 생산이 4단계 실행 경로다
유지 출력 계약은 node-tag-and-property-poc-blueprint.md 9절. RAT pod 에 제안·공유는 하되 블로커로 두지 않는다
교체 RAT pod I.I.S. 가 같은 모양으로 재개되면 iis/ 구현을 통째로 교체 가능 (D-2)
당장 할 일 T-4 사람 검수, T-5/T-6 품질·통합, T-7 정확도 검수

5. 참고#

↑ 목차로 · 5부 머리로

23장Eregion PoC 구현 기록 정본질의 갈래 end-to-end

원문 plan/eregion-poc.md GitHub 에서 보기 5 KB 원문 제목 「Eregion Visual Summary PoC (질의 갈래 end-to-end 구현 기록)」

이 문서의 역할 2026-08-1112 에 Eregion(aippt-jailbreak repo)에 세운 Visual Summary PoC 의 구현 기록이다. 무엇이 코드로 존재하고, 어떤 실측이 나왔고, 구현이 임시로 정해 스쿼드 확정이 필요한 판단이 무엇인지 남긴다. 코드와 화면이 SSoT 인 내용은 여기 복사하지 않고 repo 를 가리킨다 (src/lib/visual-summary/README.md). 처음 보는 사람은 여기 말고 poc-guide.md 를 먼저 읽는다. 이 문서는 2026-08-1112 시점의 구현 기록이고, 그 뒤에 결함 계측과 두 검색 경로 비교가 더해졌다. 오너 정원석 (MORDOR) · 상태 PoC 동작 · 최종 갱신 2026-08-12 관련 ../README.md 허브 · ../spec/schema-v0.5.md 규약 · MOR-1923 Artifact

1. 무엇이 세워졌나#

규약 v0.5 의 질의 갈래 전체가 실행 가능한 코드로 존재한다. 사용자 입력 → LLM YAML 생성(재생성 루프) → 검증(파싱 → id 부여 → zod) → 카테고리 확정(성립 조건 + fallback 그래프) → 실색인 검색(태그 군 + 채점) → 내용 대치(RLSC 슬롯 주입) → 실물 렌더까지 한 파이프라인으로 돈다.

구간 코드 (aippt-jailbreak repo) 대응 티켓
검증 파이프라인 (규약 3.1절) + silent corruption 계측 src/lib/visual-summary/validate.ts, parse.ts F-2
capability 상수 41종 + BE 소비용 JSON src/lib/visual-summary/capability.ts MOR-1925
성립 조건 판정 + fallback 그래프 순회 + P1~P8 projection establish.ts, fallback.ts, projection.ts MOR-1926 선행 절반
structure → 검색 질의 변환기 query.ts MOR-1924
YAML 생성 프롬프트 초안 + 재생성 루프 generator/ MOR-1910
실색인 검색 (태그 군 + 슬롯 프로파일 채점) search.ts, rlsc.ts content-binding-contract 2.1절 재현
내용 대치 (entities → RLSC Title/Description/Marker 슬롯) binding.ts 규약 7절 대응

Eregion 화면: /visual-summary (사이드바 "Visual Summary PoC"). 플레이그라운드(임의 입력 라이브 실행, YAML 직접 검증 모드), 실사용 샘플 10건 개별과 전체 실행, 색인 현황 세 탭이다. 모든 단계의 판정 이력(성립 탈락 사유, fallback 경로, projection 규칙, 후보 군과 점수, 슬롯별 대치 전후)이 화면에서 열람된다.

문서와의 정합은 테스트가 기계로 검사한다. capability.test.ts 가 ../spec/schema/capability.json 과 41종 전량(개수, dataset, reads, fallback)을 대조하고, 규약 예시 18종 전량 통과와 안티패턴 5종 거부, 3.5절 엣지 규칙 25종 대조가 vitest 166건에 들어 있다.

2. 인라인 실시간 렌더 캔버스 (이 PoC 에서 가장 의미 있는 부분)#

내용 대치 결과를 합성기(composite 서버) 없이 브라우저에서 실제 미캔 렌더러로 그린다. @miri-unicorn/miricanvas-editor-2/page-root 가 노출하는 PageRootView(합성 서버가 쓰는 페이지루트)를 Eregion 페이지에 직접 마운트하고, 텍스트 대치가 적용된 sheet JSON 을 그대로 먹인다. 원본 컴포넌트와 대치 완료본이 나란히 실물로 보인다.

조립에 필요했던 것 셋: 사설 레지스트리 scope 매핑(@miricanvas/@libs/@shared), engine-util 0.0.846 통일(구 0.0.818-mordor.5 와 엔진 그래프 충돌. Eregion 자체 사용은 adjustTextBoundingBox 하나뿐이라 안전 확인), 폰트 API CORS 의 same-origin 프록시(/mc-api/* → api-staging) + Node 전용 dd-trace 브라우저 스텁.

기각한 대안 둘. ① composite 서버 합성(이미지 왕복 수 초, 인라인이 대체하여 제거). ② miricanvas-web-2 feature/UNICORN-73380 브랜치의 component-thumbnail preview iframe. IUI comp-match Thumbnail.tsx 가 쓰는 방식인데, preview 환경(unicorn-73380)이 삭제돼 있고 부모 origin allowlist(local.miricanvas.com:3000 등) 제약이 있다. 다시 필요하면 그 브랜치의 ComponentThumbnailIframeHost 계약(PING/READY/COMPONENT_THUMBNAIL)을 쓰면 된다.

3. 실측 요약 (2026-08-11~12, gpt-5.4-mini)#

  • 규약 예시 19건(18종 + 병합 표): 검증→확정→질의 전량 통과. 색인 임계값 시나리오에서 fallback 이 문서의 순서 그대로 재현.
  • 실사용 입력 10건(markdown-summary-samples 원문, 로컬 색인 50건, List 44건으로 실서비스와 같은 편중): 생성 10/10 성공(재생성 루프 7회 발동과 전부 회수), 검색 후보 전 컴포넌트 확보, 대치 entity 56/56 수용(43 배치 + 13 접기).
  • 재현성: 같은 입력 3회 → 구조 다이제스트 3/3 일치. id 를 배열 순서로 매기는 규약 3.2절 설계가 지표를 실제로 안정화시켰다.
  • 생성기 라벨 정렬은 다음 과제다. 시멘틱 쿼리 v2 라벨과 exact 3 / major 1 / different 6. 생성기가 한 블록을 여러 컴포넌트로 분해하거나 이웃 카테고리를 고르는 경우가 대부분이고, 애매한 장문은 실행마다 구성이 흔들린다. MOR-1910(프롬프트)·MOR-1928/MOR-1929(평가셋과 판정)의 실측 근거.

실데이터가 찾아낸 시스템 구멍 둘은 코드에 반영했다: 검색 군 안에서 fallback 행 순서가 점수에 밀리던 문제(태그 순위 → 점수로 고정), 마지막 fallback 카테고리가 색인에 없으면 후보 0 이 되는 문제(4군 안전망. C-5 의 "빈 결과 없음"은 카테고리 차원 보장이라 색인 차원은 검색이 메워야 한다).

4. 구현이 임시로 정한 것 (스쿼드 확정 필요)#

규약이 비워 둔 자리에서 PoC 가 답을 골라야 했던 곳이다. 확정 전까지는 구현 편의이지 규약이 아니다.

# 판단 근거 확정할 곳
1 fallback 도착 판정은 투영된 구조로 한다. P6 이 matrix 를 만들고 P8 이 초과를 접은 뒤 도착의 성립 조건을 본다. 요청 카테고리 자체는 원 구조로 판정해 못 채우면 탈락(규칙 1) capability-table 8절 "표 안에서는 P8 로 통일"의 구현. 즉 "출발은 규칙 1, 도착은 P8" B-27 → MOR-1934
2 P6 은 도착이 비교형(열 기준 계수)이면 entities 를 열로 전치한다 문서 P6 문구("행으로 편다")대로면 Versus→ComparisonTable 이 열 2 이상 조건을 영원히 못 채운다. 규약 예시(1.4, 3.2)가 비교 대상을 열에 둔다 3.4절 P6 표기 → MOR-1934
3 성립 판정에서 탈락한 카테고리는 검색 질의 태그 군에서 뺀다 구조가 못 그리는 카테고리를 질의로 내보내면 랭킹과 확정이 어긋난다 (실색인에서 실측) MOR-1924 스펙
4 js-yaml 4(YAML 1.2)에서 no→false 계열 오독은 실재하지 않는다. F-2 의 실감시 대상은 숫자 표기 손실(1.0→1)뿐 실경로 파서 확인. 계측은 둘 다 세도록 방어적으로 유지 F-2 기록

5. 다음#

Summary 생성 단계 튜닝(라벨 정렬, 4절), 검색 고도화(comp-sign buildCompSearchData/rankCompCandidates 와의 접속, 지금 structureDigest 는 자리 표시), 대치 고도화(차트 데이터와 이미지, 폰트 축소 등 web-2 component-thumbnail 의 shrink 계열)는 이제 시작하는 단계다. PoC 화면이 그 실험대다.

↑ 목차로 · 5부 머리로

24장comp-sign bridge 정본structure 를 이석님 파이프라인에 잇는다

원문 plan/comp-sign-bridge.md GitHub 에서 보기 15 KB 원문 제목 「comp-sign bridge: Visual Summary structure 를 이석님 파이프라인에 잇는다」

이 문서의 역할 content-binding-contract.md 가 지적한 공백 G1("이 경로의 입력은 아직 markdown")을 실제로 메우는 PoC 의 blueprint 이자 changelog 다. 진행하며 이 문서를 갱신한다. 오너 정원석 (MORDOR) · 상태 IN PROGRESS · 시작 2026-08-12 처음 보는 사람은 여기 말고 poc-guide.md 를 먼저 읽는다. 이 문서는 다리를 놓은 과정과 실측을 시간순으로 남기는 기록이라, 전체 그림을 잡기에는 안내서 쪽이 낫다. 지금 어디까지 왔나 (2026-08-14) 다리는 놓였고(aippt-prisonbreak#2134 merged), 이제 대치 결과물의 결함을 기계가 센다(6.2절). 사람이 렌더를 눈으로 보던 판정이 매 실행 재현되는 수치가 됐고, MOR-1927(계측 지점)·MOR-1929(판정 지표)가 그 위에 선다. 관련 eregion-poc.md (선행 PoC) · ../design/content-binding-contract.md · ../README.md 1절 로드맵 · MOR-1926

1. 왜 이걸 하는가#

선행 PoC(eregion-poc.md, aippt-prisonbreak#2125 과 #2133)는 질의 갈래 전체(생성→검증→확정→검색→대치→인라인 렌더)를 자체 구현으로 세웠다. 검색 채점과 대치가 순수 RLSC 기반으로 동작하지만, 실서비스의 그 자리는 이석님의 packages/comp-sign v4.3.x 다. 두 구현이 평행선을 달리면 PoC 결과가 실서비스 편입으로 이어지지 않는다.

contract 문서가 밝혔듯 comp-sign 은 시그니처 추출, 검색, 랭킹, 내용 대치 네 단계를 이미 구현하고 있고, 비어 있는 것은 구현이 아니라 입력이다: comp-sign 의 입력은 markdown 인데 Visual Summary 는 structure(entity 트리)를 내놓는다. 이 다리 하나를 놓으면 이석님의 채점 20여 종과 box 보정과 placeholder 처리 자산을 그대로 얻는다.

2. 계약 조사 결과 (comp-sign v4.3.8 소스 실측, 2026-08-12)#

miridih/miricanvas-iui packages/comp-sign 을 직접 읽었다. 요지:

단계 함수 입력 출력
색인 createCompSignatureBuilder() → (doc, rlsc, opts) sheetJson(Doc) + RLSC (둘 다 필수) CompSignature[] 최대 3벌 (원본/Subtitle 제거/Hl 제거) — structureHash(int64 문자열)·textMap·textNodeRoles·capChars/capLines·채점 feature
질의 buildCompSearchData(markdown, opts) markdown { textTree: SummaryNode[], variants: {hash, variant, demand}[] } (기본 상한 100변형)
조회 (BE 몫) where structure_hash in (hashes) CompCandidate[] (행에 variant 를 실어 돌려줌)
랭킹 rankCompCandidates(textTree, candidates, n) textTree + 후보 군(tier) 절대 우선 → 점수 정렬 Match[]
대치 buildContentReplacement({textTree, variant, textMap, textNodeRoles, sheetJson}, opts) 랭킹 상위 1건 ThumbnailLoad { sheetJson, nodeTextMap, nodeBboxMap, siblingGroups } — RLSC 불필요

핵심 발견:

  • textTree = SummaryNode[] { text, children, head?, grid? }. head 는 markdown 헤딩에서, grid 는 파이프 표에서 온다. 리스트는 들여쓰기 2칸 = 한 단계.
  • parseSummaryTree(markdown) 가 유일한 진입 방언이다. 헤딩(#~), 리스트(*/-/+), 파이프 표, 일반 텍스트. > [!HIGHLIGHT] 꼬리는 무시된다.
  • key:value 변형이 있다. label: value 꼴 리스트 항목을 쪼개는 expandKeyValue 변형이 검색에 들어 있어, entity 의 value 를 label: value 로 실으면 변형이 알아서 대응한다.
  • 파이프 표는 grid leaf 가 되고 [table]/[chart] 토큰 변형으로 검색된다. dataset(matrix) 의 자연 대응.
  • 대치는 RLSC 없이 돈다. text_node_roles(v36+) 가 role 와 글자 용량을 담는다. box 보정 4종(relieveTopBias, ensureBgMargin, expandText, tidyLayout)이 옵션.
  • 군(tier) 축이 규약과 다르다. comp-sign 군은 후보를 끌어온 근거 출처(0=사용자 지정, 1=LLM 추정, 2=구조 해시만)고, 규약과 선행 PoC 군은 성립 조건과 fallback 행이다. 이 PoC 에서는 생성기 태그를 1군으로 넣는 매핑을 쓰고, 정합은 contract 문서 I-5 로 넘긴다.
  • 패키지는 nexus 에 배포돼 있다 (@miri-unicorn/miricanvas-iui-comp-sign@4.3.5 최신, 의존은 color-mapper 1.7.3 + xxhash-wasm 뿐).

3. 설계 결정#

# 결정 근거 기각한 안
D1 다리는 structure → markdown 으로 놓는다 (textTree 직접 조립이 아니라) buildCompSearchData 가 markdown 을 받아 트리와 해시를 같은 파싱에서 만든다 — 트리를 직접 만들면 해시 경로와 어긋날 수 있다고 소스 주석이 명시. markdown 은 사람이 읽고 디버그할 수 있다 SummaryNode[] 직접 조립 (파싱 우회)
D2 로컬 색인을 실제 buildCompSignatures 로 만든다 — 로컬 DB 의 (sheetJson, RLSC) 50건을 그대로 태워 메모리 캐시 comp_signs 테이블이 로컬에 없지만, 색인 함수가 순수 계산이라 BE 없이 재현 가능. "comp-sign 실물"이라는 말이 성립하려면 색인도 실물이어야 한다 가짜 시그니처 손조립
D3 BE 조회(where structure_hash in)는 메모리 매칭으로 대행한다 PoC 경계. DB 접근·후보 fetch 는 원래 호출자(BE) 몫이라고 패키지가 정의 로컬 comp_signs 테이블 신설
D4 대치 결과 nodeTextMap 을 기존 인라인 캔버스의 sheet fills 로 변환해 렌더한다 선행 PoC 의 same-origin iframe + PageRootView 렌더러를 재사용. nodeBboxMap(box 보정)도 같은 경로로 적용 가능 comp-match preview iframe 이식
D5 entity 대응: label(: value) → 리스트 항목, items[] → 하위 리스트, 페이지 제목 → # 헤딩, dataset(matrix) → 파이프 표, period → items 앞줄 parseSummaryTree 방언과 key:value·grid 변형에 1:1 로 얹힌다 별도 role 힌트 주입 (방언에 없음)
D6 두 경로를 나란히 둔다 — 순수 RLSC 경로(4단계 node-tag 시뮬)와 comp-sign 경로를 같은 화면에서 비교 어느 쪽을 실서비스에 편입할지는 실측으로 정할 문제 선행 PoC 경로 교체

4. Scope (4단계 경계)#

  • 한다: structure→markdown 다리, 실물 comp-sign 색인, 검색, 랭킹, 대치, 인라인 렌더, 두 경로 비교 트레이스, semantic tag 군 매핑(생성기 태그 = 1군).
  • 안 한다: node 태그(I.I.S.) 소비. 색인 갈래가 멈춰 있어 데이터가 없다. category 고유 props 주입(5단계). comp_signs 실테이블 적재. 색상과 꾸밈 대치.

5. 구현 중 내린 추가 결정#

# 결정 근거 기각한 안
D7 패키지는 nexus 최신 4.3.5(SIGNATURE_VERSION 36)를 쓴다. 파이프 표(grid leaf)·헤딩 head 마킹·sig3 는 4.3.8 에만 있어 matrix 는 중첩 리스트로 직렬화한다 (행 = 항목, 셀 = 열이름: 값 하위 항목) 팀·CI 가 설치할 수 있어야 한다. 4.3.8 은 nexus 에 미배포 (소스 빌드는 재현 불가) → 이석님께 4.3.8 publish 요청이 후속 4.3.8 소스 빌드 vendoring
D8 aippt repo 의 기존 comp-sign 2.5.0 은 건드리지 않는다 — pipeline 백필 워커(src/workers/comp-signature-worker.ts)가 2.5.0 시그니처 세대에 고정돼 있어, bridge 는 pnpm alias(miricanvas-iui-comp-sign-v4 → @…comp-sign@4.3.5)로 두 버전을 공존시킨다 버전을 올리면 백필이 만드는 structure_hash 세대가 바뀐다 (기존 적재분과 어긋남) 단일 버전 강제 상향
D9 4.3.5 에는 semanticTags/tier 모듈이 없다(4.3.8 신설) → 군 분할은 bridge 가 감싼다: 1군 = 생성기 추정 태그 보유, 2군 = 구조 해시로만 걸림, 군별로 rankCompCandidates 후 연결 comp-sign v4.3.8 군 정의(근거 출처)와 동형 태그 가점 방식 (comp-sign 이 명시적으로 배제)

6. 실측 (2026-08-13, 로컬 색인 50건)#

  • 색인: 실제 buildCompSignatures 로 50건 → 시그니처 60벌, 고유 해시 24 (실패 0). List 편중 seed 라 구조 중복이 크다.
  • 검색 hit rate: 규약 예시 18종(질의 20건) 기준 19/20. 정확 구조 해시 매칭이지만 변형(자식 수 확장, 병합, unwrap 등, 질의당 1~22개)이 recall 을 만든다.
  • 대치 실물 확인 (온보딩 4단계 Steps): 변형 18개 중 2개 hit → 컴포넌트 5건 → 1위 카드 4장 List 에 entity 4개 정확 주입, 매핑 안 된 노드는 placeholder("설명을 입력해주세요.") 처리, box 보정 8노드 계산 (인라인 렌더에는 미적용).
  • 두 경로가 다른 선택을 한다: 순수 RLSC 경로는 규약(군과 태그 순위 절대 우선)대로 Timeline 을, comp-sign 경로는 구조 해시 + 기하 채점으로 List 카드를 골랐다. 규약의 fallback 서열과 comp-sign 의 "제외 대신 뒤 군으로 미룸"이 실제로 다른 결과를 내는 첫 실측이다. contract 문서 I-5(정합) 논의의 구체 사례다.

6.1 대치 결과물 precision 검수 (2026-08-13, 합성기 렌더 직접 판정)#

hit rate 같은 recall 지표는 이 프로젝트의 KPI 가 아니다. 대치 결과물의 precision 이 제품 품질을 가른다 (BAD 85% = 컴포넌트-내용 불일치). 실사용 입력 10건을 생성→검색→대치까지 돌리고, 두 경로의 대치 적용본 23장을 합성기(composite staging)로 렌더해 직접 검수했다. rubric: 매핑 정확 / 텍스트 넘침 / placeholder 과 잔존 노출 / 내용 누락.

순수 RLSC 경로 결함 (빈도순):

# 결함 실측 사례 조치
1 템플릿 원문 잔존 — 미채움 슬롯에 남의 내용(Lorem ipsum, 75%/$300B 수치, "Sub Topic N Description")이 그대로 노출 BigNumber 3카드, Timeline 상세, 4분면 리스트 등 검수분 과반 수정함: 단위 안 미채움 슬롯을 비운다 (slotsCleared). comp-sign 의 placeholder 처리에 대응
2 fold 가 잔존 텍스트에 이어붙음 — 접힌 entity 가 템플릿 원문 뒤에 , 로 연결 100119_1 Timeline ("실험 기반 UX 개선 적용, 레크레이션, …") 수정함: 우리가 채운 슬롯에만 이어붙인다
3 텍스트 넘침·겹침 — 긴 한국어 label 이 제목 영역을 넘어 본문과 겹침 94497_2·100046_2 Timeline box 보정 부재. comp-sign 의 relieveTopBias/ensureBgMargin 에 해당하는 것이 없다 — 후속
4 BigNumber 값이 큰 숫자 슬롯에 못 감 — 역할(Subtitle) 기반 대응의 한계, 시각적 핵심이 남의 숫자로 남음 94449_1·94669_1 5단계(category 고유 props) 없이는 근본 해결 불가 — node 태그 필요성의 실측 근거

comp-sign 경로 관찰:

  • 텍스트 매핑 자체는 일관되게 정확하고, 미매핑 노드는 placeholder("설명을 입력해주세요..") 처리이고 잔존이 없다. 검수분에서 내용 보존이 RLSC 경로보다 대체로 좋다.
  • 결함: ① 병합(merge) 변형에서 markdown bullet * 가 텍스트에 그대로 노출, ② 제목 넘침(우리 렌더 경로가 nodeBboxMap 을 아직 안 먹여서 comp-sign 의 box 보정이 무효화된 불공정 비교), ③ 2군(구조 해시만) 후보의 의미 불일치. 에너지 통계에 여행 사진 3장, 일정표에 파티 장식 등.
  • ③이 "내용 대치는 검색에 종속"의 실측이다: 대치가 정확해도 검색이 의미 무관 후보를 주면 결과물이 나쁘다. 태그(1군)가 비면 구조만으로 뽑는 comp-sign 방식은 색인이 작을수록 이 위험이 커진다.

6.2 결함을 기계가 센다 (2026-08-14)#

6.1절의 판정은 사람이 렌더 23장을 눈으로 본 것이었다. 반복이 안 되고 커밋마다 다시 못 재니, 열린 결정을 실측으로 닫는 데 쓸 수가 없었다. 그래서 같은 rubric 을 기계가 세게 만들었다. src/lib/visual-summary/audit.ts.

설계 원칙은 하나다: 런타임 랭커가 최적화하는 바로 그 양을 잰다. 감사기가 랭커와 다른 자를 쓰면 "검색 탓 결함"이 정의되지 않는다. 랭커가 보고도 그 후보를 골랐는지, 애초에 못 본 것인지를 가를 수 없기 때문이다.

결함 15종을 BAD 원인 넷에 1:1 로 붙였다. 용량 초과, 매핑과 위계 오류, 슬롯 수 불일치, 대치 실패. 두 경로를 같은 용량 근거(comp-sign 시그니처의 text_map 과 text_node_roles)로 잰다.

채점기가 서기 위해 실물에서 확인한 것 셋#

# 확인한 것 결과
1 채점의 전제인 "RLSC 노드 id == sheet 요소 id" 로컬 색인 2,468건 · Text 슬롯 17,899개 결합률 100%. 그중 lines/charsPerLine(랭커가 실제로 보는 값)까지 아는 것이 94.4%, 나머지는 chars 비율로 2차 판정
2 comp-sign 의 넘침 판정식 textDiff 는 chars 가 아니라 lines/charsPerLine 을 본다 — predicted = Σ max(1, ceil(조각길이/cpl)), 감점 √(predicted − lines). 감사기가 같은 계산을 쓴다
3 comp-sign placeholder 문구 큰따옴표가 문구에 실제로 들어 있다 ("설명을 입력해주세요."). 번들 소스만 보면 판별이 안 돼(S: '"제목"') 실제로 buildContentReplacement 를 돌려 확인했다. 6.1절이 관찰한 설명을 입력해주세요.. 가 이 닫는 따옴표다 — 이석님께 알릴 것

기준선 (실사용 입력 50건, 컴포넌트 65, 상위 10)#

평가셋은 10건에서 50건으로 넓혔다. 중분류 32종, 표와 차트 19건, 길이 3버킷(짧음 19 / 보통 17 / 김 14). 원천은 시멘틱 쿼리 v2 검증 시트(라벨 붙은 1,051 block)이고, 분할 찌꺼기는 걷어냈다. 생성 결과는 스냅샷으로 repo 에 커밋해 채점이 무료와 결정적으로 반복된다 (LLM 호출 0회). 캐시 키에 프롬프트 본문 해시를 넣어, 프롬프트가 바뀌면(MOR-1910) 스냅샷이 자동으로 stale 이 된다.

시멘틱 검색 (RLSC) 구조 검색 (comp-sign)
심각도 가중 평균 19.9 17.5
텍스트 넘침 176 177
미채움 슬롯 비움 173 0
마크다운 노출 0 137
잔존 79 40
내용 누락 48 33
슬롯 부족(병합) 7 39

pageNoBlocker 8%. 입력 50건 중 4건만 blocker 결함이 하나도 없다. 이것이 BAD 체감의 기계 대용치다. (pageClean 은 minor 하나에도 떨어져 0% 라 추적에 못 쓴다.)

읽어야 할 것 셋:

  1. 넘침이 양쪽 모두 1위 결함이고 수가 거의 같다 (176 대 177). BAD 원인 1순위가 실제로 1순위인 것이 이 프로젝트에서 처음 확인됐다. 경로를 바꿔도 안 줄어든다. 검색 풀과 용량 자체의 문제라는 뜻이다.
  2. 마크다운 bullet 노출 137건은 comp-sign 경로에만 있다. 6.1절이 눈으로 본 결함이 수치가 됐고, 4.3.8 publish 요청(파이프 표와 grid leaf)의 정량 근거다.
  3. 미채움 슬롯을 비우는 것(173건)과 placeholder 로 채우는 것(20건)의 맞바꿈이 드러난다. 순수 RLSC 경로는 6.1절 결함 1(잔존)을 고치면서 "비움"을 173건 만들었다. 잔존은 blocker 이고 비움은 minor 라 가중 합에서는 이득이지만, 어느 쪽이 사람 눈에 나은지는 아직 사람이 판정해야 한다. 그것이 판정 절차의 마지막 단계다.

결함을 어디 탓으로 돌릴 것인가 (MOR-1927)#

귀속은 단일 결과가 아니라 대안과의 비교에서만 나온다. 반사실 자료는 이미 계산되고 버려지고 있었다. 두 경로 다 상위 N 후보 전부에 대치를 돌리기 때문이다.

floor(kind) = min over 모든 후보 × 경로            → 생성 탓 (풀 안 어디로도 못 피함)
pickedLane(kind) − floor                          → 검색 탓 (더 나은 후보가 있었는데 못 고름)
picked(kind) − pickedLane(kind)                   → 대치 탓 (같은 후보를 다른 경로가 더 잘 채움)
sawByRanker=false 인 몫은 검색 탓에서 덜어낸다       → 랭커 미가시

기준선 50건 기준 생성 36, 검색 466, 대치 17, 랭커 미가시 74. 결함의 **압도적 다수가 "상위 10 안에 더 나은 후보가 있었는데 못 고른 것"**이다. 검색이 지금 가장 큰 손잡이라는 뜻이고, I-6(rankCompCandidates 위계 가중치 A/B, MOR-1936)이 왜 "가장 싸게 검증할 수 있는 다음 수"인지의 실측 근거다. 대치 탓이 17건뿐이라는 것도 같은 방향을 가리킨다. 대치 알고리즘을 다듬는 것보다 검색을 고치는 편이 값이 크다.

sawByRanker 를 따로 세는 것이 이 설계의 정직성 장치다. 시그니처 밖 노드의 넘침을 검색 탓으로 돌리면 랭커에게 없는 정보로 벌점을 매기는 셈이고, 그 수치로 가중치를 튜닝하면 잘못된 방향으로 간다.

표본을 눈으로 봐서 과탐 둘을 걷어냈다#

기계가 세기 시작했다고 그 수치를 그대로 믿으면 안 된다. 결함 표본의 실제 텍스트를 찍어 보고 둘을 고쳤다.

  • 순번 표식 (01 와 02…) 을 잔존으로 세고 있었다. 이건 입력 텍스트가 아니라 entity 순서에서 파생된 것이라 출처 사전에 없는 것이 정상이다.
  • 정상 위계 배치를 역전으로 세고 있었다. 페이지 제목이 Title 슬롯에 가는 것, entity 이름이 설명 슬롯에 가는 것은 둘 다 설계된 동작인데 컴포넌트당 3건씩 잡혔다. 판정을 진짜 역전 둘(상세가 제목 계열로 올라감, 페이지 제목이 설명으로 내려감)로 좁혔다.

둘 다 회귀 테스트로 고정했다. 임계값을 옮기면 그 테스트가 깨진다.

판정 지표(MOR-1929)가 이 PoC 로 계산된다#

스쿼드 합의 지표는 "시멘틱 검색과 구조 검색의 교집합 상위 10개 품질"이다. 이 PoC 에 그 두 검색이 이미 각각 실물로 있다. 순수 RLSC 경로는 태그 멤버십만 보고(structureDigest 는 미사용), comp-sign 경로는 구조 해시만 본다. 두 축이 깨끗하게 갈려 있어 교집합의 의미가 명확하다.

교집합은 보고하고, 운영 후보는 RRF 융합(1/(60+rank))으로 만든다. 두 점수가 비교 불가능한 척도라(한쪽은 0~1 적합도, 다른 쪽은 페이지 크기에 의존하는 가산 감점) 정규화 가중합은 손잡이만 늘린다.

첫 관측: 두 검색은 맨 위에서 거의 합의하지 않는다. 컴포넌트 65 기준 합의 깊이가 k=1 에서 2(3%), k=3 에서 14, k=5 에서 25, k=10 에서 84 다. 1위가 겹치는 경우가 사실상 없다는 뜻이라 "교집합 상위 10개"를 그대로 KPI 로 쓰면 표본이 거의 안 남는다. 교집합 후보의 결함 0 비율도 4% 에 그친다.

그래서 판정 절차는 교집합을 진단으로 보고하고, 품질 수치는 세 축(시멘틱, 구조, RRF 융합)을 나란히 낸다. 합의 깊이 곡선 자체가 "두 신호가 같은 것을 보고 있는가"라는 진단이고, 지금 답은 "거의 다른 것을 보고 있다" 이다. 이 사실이 MOR-1929 판정 절차 확정의 첫 입력이다.

6.3 계측기를 세우며 고친 것#

  • 두 경로가 다른 집합에서 후보를 골랐다. 순수 RLSC 경로는 coalesce(structure_json, linter_results.rlsc) 로 넓게, comp-sign 경로는 sheet_json AND structure_json 으로 좁게 읽고 있었다. D6("어느 쪽을 편입할지 실측으로")의 비교가 성립하려면 같은 풀이어야 한다. 엄격한 교집합으로 통일했고, 로컬에서는 둘 다 2,468건이라 후보 손실이 0이다.
  • 상위 N 이 실행 간 재현되지 않았다. 두 색인 SQL 에 ORDER BY 가 없고 랭킹 비교자에 tiebreak 이 없어 동점 후보의 순서가 미정의였다. 평가 지표가 그 순위 위에 서므로 먼저 고정했다.
  • box 보정이 한쪽만 죽어 있었다. comp-sign 의 nodeBboxMap 을 인라인 렌더가 안 먹여서 그 경로만 넘침이 과대 계상됐다. 적용하면서 실측으로 하나 더 나왔다. sheet 의 boundingBox 에는 comp-sign 이 안 주는 필드(angle, angleType)가 붙어 있어서 통째로 갈아 끼우면 사라진다. 네 수만 덮는다.

7. 남은 것#

  • 이석님께 comp-sign 4.3.8 nexus publish 요청 → matrix 파이프 표([table]/[chart])와 head 마킹과 sig3 전환. 정량 근거 확보: 마크다운 bullet 노출이 comp-sign 경로에서 컴포넌트당 1.80건 (6.2절)
  • 이석님께 placeholder 문구의 따옴표 알리기. "설명을 입력해주세요." 가 그대로 캔버스에 그려진다 (6.2절)
  • nodeBboxMap(box 보정)을 인라인 캔버스 렌더에 적용 → 완료 (2026-08-14), 6.3절
  • I-5: 규약 fallback 서열 ↔ comp-sign 군 체계 정합 (6절 실측 사례 지참)
  • I-6 A/B: rankCompCandidates 가중치를 바꿔 가며 계측기로 재기. 패키지가 weights 옵션과 DEFAULT_SCORING_WEIGHTS 를 export 하므로 PoC 안에서 바로 돌릴 수 있다 (MOR-1936)
  • 사람 검수 라벨과 기계 판정의 일치율(κ) 측정 → 넘으면 다음 라운드부터 기계 수치 단독 사용 (MOR-1929 판정 절차 6단계의 마지막)
  • 색인 확장 → 완료 (2026-08-13): prod readonly 의 태깅 컴포넌트를 태그별 균형 샘플링으로 로컬 복사하는 스크립트(scripts/visual-summary-expand-index.ts, 멱등)로 50건 → 2,468건 (49종 전부). Steps 질의가 fallback 없이 1군 직행하는 것 실측
  • production 색인 방식 결정: PoC 는 서버 메모리 즉석 빌드(2,468건 ~수 초, 30분 캐시)지만 실서비스 규모(178k)는 사전 적재가 필요하다. 선택지는 둘이다. ① repo 의 comp_signatures 워커(현재 2.5.0 세대)를 4.3.x 로 올려 재적재, ② iui comp_signs_v3_4 를 직접 조회(세대 일치 보장, BE 연동 필요). 편입이라면 ②가 정도
  • binding 에 box 보정 도입 (또는 comp-sign 보정 재사용). 6.1절 결함 3
  • 대치 결과(잔존, 접힘, 누락) 기반 re-ranking. 검색 종속성을 결과물 품질로 되먹임

8. Changelog#

날짜 변경 비고
2026-08-12 문서 개설. comp-sign v4.3.8 소스 계약 실측(2절), 설계 결정 D1~D6 iui sparse clone 으로 조사
2026-08-13 다리(markdown.ts) 구현 + 실제 parseSummaryTree round-trip 테스트 7건. nexus 는 4.3.5 가 최신이라 D7(중첩 리스트 직렬화) 결정 4.3.8 소스 빌드는 성공했으나 재현성 때문에 기각
2026-08-13 aippt repo 가 comp-sign 2.5.0 을 이미 쓰는 것 발견(pipeline 백필 워커) → D8 pnpm alias 공존 무심코 4.3.5 로 올렸다가 백필 시그니처 세대가 바뀔 뻔했다
2026-08-13 실물 연동 서비스(compSignService.ts): 색인·검색·군 분할(D9)·랭킹·대치 → 인라인 캔버스 fills. Eregion /visual-summary 에 두 경로 비교 섹션 6절 실측 결과
2026-08-13 precision 검수 (6.1절): 실사용 10건 × 두 경로 = 23장 합성기 렌더 직접 판정. RLSC 경로 결함 1·2(잔존, fold 오염) 즉시 수정 recall 이 아니라 결과물 precision 이 KPI (정원석님 정정)
2026-08-13 로컬 색인 50→2,468건 확장(49종, 태그별 60건 샘플링) + Eregion 에 서버 빌드 상태 칩(loading/signing 단계) + 결과 화면 서사 재구성(①~⑤ 읽는 순서) 정원석님 로컬 테스트 피드백 반영
2026-08-14 결함 채점기(audit.ts, 결함 15종 = BAD 원인 4항목) + 귀속(MOR-1927) + 교집합·RRF(MOR-1929). 6.2절 사람 눈이 유일한 계측기이던 것을 기계로 옮겼다
2026-08-14 평가셋 10 → 50건(중분류 32종, 표·차트 19건, 길이 3버킷). 생성 스냅샷을 repo 에 커밋해 채점을 무료·결정적으로 만들었다 프롬프트 해시를 캐시 키에 넣어 프롬프트 변경이 조용히 무시되지 않게 했다
2026-08-14 두 경로의 후보 풀 통일, 상위 N 재현성(정렬 tiebreak), box 보정 인라인 적용. 6.3절 계측 전에 비교 조건부터 맞췄다
2026-08-15 의미 거리 결함 mapping.categoryDrift 신설 → 두 경로 격차가 2.4 에서 0.2 로 줄었다. 구조 17.5 → 20.1, 시멘틱 19.9 → 20.3. 새 결함은 시멘틱 13건·구조 23건. 후보 선택은 하나도 안 바뀌었다. (첫 보고는 시멘틱 0건이었는데 auditBoundRlsc 가 인자를 안 넘긴 버그였다 — PR 리뷰에서 잡혔다) 색인 태그 어휘 49종 대 규약 41종(색인 12.6%가 규약 밖 태그뿐). 설계는 node-tag-and-property-poc-blueprint.md

↑ 목차로 · 5부 머리로

25장노드 태그와 노드 속성 PoC Blueprint 정본경계 지도·속성 지도 계약

원문 plan/node-tag-and-property-poc-blueprint.md GitHub 에서 보기 73 KB


title: 노드 태그와 노드 속성 PoC Blueprint (4단계판과 5단계판 결과물) description: Visual Summary 결과물의 4단계판(노드 단위 의미 태그)과 5단계판(노드 단위 카테고리 속성)을 각각 완성하기 위한 설계 정본. 무엇을 만들고, 어떤 테이블을 세우고, 무엇을 판단해야 하고, 무엇을 기각했는가.#

노드 태그와 노드 속성 PoC Blueprint#

이 문서는 Visual Summary 결과물의 4단계판과 5단계판을 각각 완성하기 위한 설계 정본이다. 지금까지의 PoC 는 두 검색 경로를 나란히 놓고 재는 데까지 왔는데(poc-guide.md), 정작 로드맵이 말하는 4단계와 5단계의 결과물 자체가 아직 없다. 이 문서가 그 둘을 만들 때까지의 설계, 결정, 할 일을 담는다.

  • 이 문서가 답하는 것: 4단계판과 5단계판이 각각 무엇인가, 데이터를 어디에 어떻게 저장하는가, 속성 추출이 무엇을 뽑는가, 무엇이 완료인가, 무엇을 아직 못 정했는가
  • 이 문서가 답하지 않는 것: 현행 대비 우위 증명 실험(별도 계획), 실서비스 편입 일정, 색상 대치, 이미지 대치. 차트 데이터 대치는 범위 밖이 아니라 순서상 뒤다 (D-7)
  • 상위 맥락: ../README.md 1절 로드맵 · ../TODO.md
  • 규약 정본: ../spec/schema-v0.5.md · ../spec/capability-table.md

1. 먼저 이름을 정리한다 (2026-08-15 정원석 확정)#

I.I.S. 는 Incremental Information Structuriser, 우리말로 증분 정보 구조화기다. Old RLSC 를 New RLSC 로 바꾸는 RAT pod(류민님) 소관 모듈이고(MOR-1425, ../README.md 5절), 하나의 모듈이 아니라 계열이다. 이 문서가 필요로 하는 I.I.S. 2종을 아래와 같이 정의한다.

표기 우리말 무엇을 내나 로드맵
I.I.S. (Semantic Component Boundary) 증분 정보 구조화기 (시맨틱 컴포넌트 경계) 컴포넌트 안에서 어느 노드 묶음이 어느 의미 카테고리인가. 곧 경계의 특정 4단계
I.I.S. (Semantic Component Property) 증분 정보 구조화기 (시맨틱 컴포넌트 속성) 경계가 잡힌 묶음의 카테고리 고유 속성(방향·순서·중심 등) 5단계

속성은 경계를 포함한다. 어느 묶음인지 모르면 그 묶음의 방향을 말할 수 없다. 그래서 Property 는 Boundary 를 딛고 서고, 로드맵의 4단계 ⊂ 5단계 포함 관계와 정확히 겹친다.

코드 식별자에는 대상을 반드시 넣는다. I.I.S. 는 계열이므로 다른 epic 이 다른 대상에 대해 I.I.S. 를 필요로 할 수 있다. iisBoundary 처럼 대상을 빼고 적으면 모든 I.I.S. 가 시맨틱 컴포넌트에 대한 것으로 읽힌다. 그래서 이름 규칙을 둘로 나눠 못 박는다.

iis + 대상 + 국면. 이 문서의 둘은 iisSemanticComponentBoundary · iisSemanticComponentProperty 다. 나중에 다른 대상이 생기면 같은 규칙으로 iis<대상><국면> 을 만든다.

길지만 줄이지 않는다. 줄이면 대상이 빠지고, 대상이 빠지는 것이 바로 여기서 막으려는 것이다.

줄여 쓰기는 이 문서 안에서만, 그것도 조건부다. 절 안에서 전체 이름이 한 번 나온 뒤에는 경계 추출 과 속성 추출로 적어도 된다. 다른 문서로 옮길 때는 전체 이름을 쓴다.

1.0.1 5단계 이름을 다시 본다 (2026-08-23 제기 → 같은 날 확정)#

5단계가 내야 하는 것은 속성만이 아니다. 대치가 실제로 필요로 하는 것은 두 가지이고, 지금 이름은 그중 하나만 가리킨다.

무엇 예 지금 이름에 담기나
노드와 구조 필드의 대응 (node to structure field mapping) 이 Text 노드가 entities[0].label 이고, 저 노드가 entities[0].items[0] 이고, 이 컨테이너가 entities[0] 이다 안 담긴다
카테고리 고유 속성 (property) 이 묶음의 회전은 시계 방향이다 담긴다

왜 대응이 더 근본인가. 지금 대치(binding.ts)는 이 대응을 RLSC 역할로 어림한다. Title 슬롯에 label, Description 슬롯에 items 를 넣는 식이다. 역할이 그 자리의 의미를 정확히 말해 주지 않으면 어림이 틀리고, 그것이 BAD 원인 넷 중 "매핑과 위계 오류" 다(../design/content-binding-contract.md 머리말). 대응을 실물로 뽑으면 이 어림이 사라진다. 속성은 그 대응 위에 얹히는 값 하나이지 전부가 아니다.

결정 (정원석): 이름은 바꾸지 않는다. 산출물만 둘로 넓힌다. I.I.S. (Semantic Component Property) 를 그대로 두고, 5단계 추출기 한 번의 호출이 속성(properties)과 노드 ↔ 구조 필드 대응(structure)을 함께 낸다. I.I.S. (Semantic Component Mapping) 으로 이름을 바꾸고 산출물을 iisSemanticComponentFieldMap/iisSemanticComponentProperty 둘로 가르는 안은 기각했다.

기각 근거. 이름을 바꾸면 치를 값(MOR-1932 제목, 2026-08-15 확정 기록, 이 문서의 T-7)에 비해 얻는 것이 적다. 대응이 속성보다 근본이라는 관계(위 문단)는 코드 식별자를 둘로 가르지 않아도 표 안의 두 칸(properties/structure)과 한 번의 LLM 호출로 그대로 표현된다. 속성 추출기가 경계 위에 얹히듯(1절 "속성은 경계를 포함한다"), 이번에도 새 국면을 만들지 않고 기존 국면(Property)의 범위를 넓히는 쪽을 택했다.

산출물 무엇 저장 칸
속성 (property) 카테고리 고유 표현 값. 내용과 무관 iis_semantic_component.properties
구조 대응 (structure map) 노드 ↔ 구조 스키마 entities[] 필드, 그리고 dataset 자리(2026-09-17 확장 결정, 6절 T-7 · MOR-2199). 내용마다 다르다 iis_semantic_component.structure (2026-08-23 신설, 카테고리별 칸에 dataset 키를 더한다)

어휘도 새로 만들지 않는다. 구조 필드 이름은 capability-table.md 2절 "선택 내용(optional content)" 열(capability.ts 의 reads.content) 이 그대로 정하고, 여기에 모든 카테고리가 항상 허용하는 label 과 items 를 더한다. 속성 어휘(reads.params)와 원리가 같다(2.-1절).

대신 표기 규칙을 진다. 이름을 그대로 두는 대가로 'property' 가 넓은 뜻(이 국면 전체)과 좁은 뜻(카테고리 고유 속성)으로 갈린다. 그 값을 치르지 않으려면 아래를 지킨다. SSoT 는 ../README.md 2절 "'property / 속성' 표기 규칙"이다.

쓰는 자리 이렇게 적는다 이렇게 적지 않는다
카테고리 고유 속성, 질의 쪽 params 속성, property
카테고리 고유 속성, 색인 쪽 properties 속성, property
노드 ↔ 구조 필드 대응 구조 대응 (structure map), iis_semantic_component.structure structure (질의 쪽 내용과 겹친다)
5단계 국면 전체 I.I.S. (Semantic Component Property) 풀네임 Property 모듈, 속성 모듈

T-7 완료 판정을 고친다. "노드와 구조 필드의 대응은 이 단계 범위 밖" 이라는 말을 넣지 않는다. 이제 범위 안이다. 구현 상세와 실행 현황은 6절 T-7 항목에 있다.

1.1 검색 fallback 이후 대치 계층#

로드맵 4단계와 5단계는 검색이 끝난 뒤 무엇을 어디에 넣을지를 나눈다. 이 절은 그 대치 계층을 blueprint 관점에서 정리한다. 상세 contract는 ../design/content-binding-contract.md 3.4~3.5절이 SSoT 다.

한 줄 흐름. Summary가 containment 또는 이종 nested 구조를 만들면 exact structure match가 어렵다. semantic fallback과 structure projection(P1~P8) 으로 카테고리와 structure를 맞춘 뒤, 1차는 comp-sign textTree + signature 대치(4단계 기대)다. 여기서도 속성과 매핑이 남으면 5단계 property mapping과 cross-type property projection(T-8)이 필요하다.

계층 blueprint 작업 PoC / 실서비스
Fallback + P1~P8 규약 확정 (2026-08-02) fallback.ts, projection.ts
4단계 comp-sign 대치 I절 contract, I-5 compSignService.ts, comp-sign 패키지
PoC 규약 RLSC 대치 MOR-1926 비교용 binding.ts
5단계 property projection T-8 미착수 T-7 extractProperty.ts만

Structure projection과 property projection은 다르다. P1P8은 capability-table fallback 엣지에서 structure/params를 옮긴다. T-8은 이종 type 사이 sequence, orientation 같은 카테고리 속성을 슬롯에 맞추는 설계 지점이다. P1P8만으로 T-8을 대체할 수 없다.

PoC 화면. 두 대치 경로를 컴포넌트마다 눈으로 비교하려면 /visual-summary 의 대치 경로 비교 탭을 쓴다 (poc-guide.md 5.1절).

만드는 주체는 따로 적어야 한다. I.I.S. 는 RAT pod 의 모듈인데 계열 티켓 넷이 2026년 6월 중순부터 멈춰 있다. 그래서 이 PoC 는 같은 출력을 대행 생산한다.

이 PoC 가 만드는 것 무엇을 대행하나 I.I.S. 가 풀리면
시맨틱 컴포넌트 경계 추출기 I.I.S. (Semantic Component Boundary) 출력 모양이 같으면 통째로 교체된다 (D-2)
시맨틱 컴포넌트 속성 추출기 I.I.S. (Semantic Component Property) 위와 같다

2. 설계를 바꾼 사실 여섯#

계획을 세우기 전에 실물을 확인했다. 아래 여섯이 이 blueprint 의 뼈대다.

2.-1 같은 규칙을 두 벌 만들면 반드시 어긋난다 (2026-08-16, 값비싸게 배움)#

PR 리뷰가 같은 종류의 Critical 을 다섯 냈다. 전부 "어떤 카테고리가 허용되는가"를 채점기가 두 번째로 구현한 탓이었다.

검색이 허용하는 것 채점기가 허용한 것
alternatives 를 1군에 넣는다 몰랐다
대분류를 expandToCombined 로 편다 안 폈다
대분류의 첫 중분류 행 fallback 만 2군에 넣는다 모든 행에서 최단거리를 쟀다
확정 카테고리(3군)·안전망(4군)도 후보로 잡는다 1·2군만 받았다 — 그 후보가 전부 결함이 됐다

하나를 고치면 다음 라운드에 다른 데서 어긋났다. 네 번째 정정은 "검색에서 받아 온다"로 방향을 이미 고친 뒤였는데도 틀렸다. 받아 오되 검색이 쓰는 것의 일부만 받았기 때문이다. 방향을 고치는 것으로는 부족하고, 받는 자리가 검색 모듈 안이어야 했다.

그래서 허용 집합을 만드는 함수는 검색 모듈에 하나만 둔다 (search.acceptedCategoriesFor). 채점기는 그것을 받아 포함 여부만 본다. capability.ts 에서 거리 계산 장치를 90줄 걷어냈다.

같은 뿌리에서 나온 것이 하나 더 있다. 계측이 실패했다는 것과 결함이 없다는 것을 구분하지 않으면, 깨진 경로가 오히려 좋아 보인다(BAD 대용치 구조 14% → 실제 7%, 10절). 계측기는 "모른다"를 "괜찮다"로 접으면 안 된다.

교훈은 이 문서 밖에도 적용된다. 계측기를 만들 때 "실서비스가 무엇을 하는가"를 다시 구현하고 싶어지는데, 다시 구현하는 순간 그 계측기는 실서비스가 아니라 자기 자신을 재게 된다. 받아 쓸 수 있으면 받아 쓴다.

2.0 색인 태그 어휘가 규약 어휘보다 넓다 (2026-08-15 실측)#

design_object_semantic_tags 가 실제로 쓰는 태그는 49종인데, 규약(../spec/capability-table.md)은 41종만 정의한다. 빠진 8종은 Geo_Distribution, Geo_Flow, Geo_Highlight, Geo_Location 넷과 PageNavigation_Ending, PageNavigation_Opening, PageNavigation_SectionDivider, PageNavigation_TableOfContents 넷이다.

규모가 작지 않다. 로컬 색인 2,468건 중 312건(12.6%)이 이 8종으로만 태깅돼 있다. 이것들은 성립 조건도 대체 순서도 없으므로, 규약이 대체 대상으로 인정할 수도 거부할 수도 없는 상태로 검색 풀에 들어 있다. 구조 검색은 태그를 아예 안 보므로 이 312건이 그대로 1위가 될 수 있다.

이 사실이 T-1 의 판정 규칙을 정했다. 규약은 "fallback 순서를 따라 내려가고 마지막은 항상 List"라고 정의하므로, 대체 그래프 밖에 떨어지는 것은 규약이 허용한 대체가 아니다. 판정 불가로 넘기면 정확히 잡아야 할 사례가 조용히 통과한다.

이 8종을 어떻게 할지는 D-8 에서 갈랐다. 경계 추출에는 49종 전부를 쓰고, 생성 어휘에는 PageNavigation_* 만 넣고 Geo_* 는 넣지 않는다. PageNavigation_* 의 규약 편입(성립 조건과 대체 순서)이 후속 과제로 남는다.

2.1 5단계 속성 어휘는 이미 확정돼 있다#

../spec/capability-table.md 2절 의 "선택 속성" 열이 곧 5단계가 말하는 카테고리 고유 속성이다. 41종 전부에 대해 이미 정의돼 있고 기본값까지 붙어 있다.

카테고리 선택 속성 (기본값)
ProcessFlow_Steps sequence (없으면 배열 순서), marker (number)
Progression_Staircase direction (ascending)
Cycle_Cycle center (없으면 가운데를 비움), rotation (clockwise)
Hierarchy_Pyramid apex (last), weight (없으면 층 높이 균등)
TimelineSchedule_Timeline period, orientation (horizontal)
RelationalComparison_Quadrant axisLabels (없으면 축 이름을 비움)
ScopeCoverage_NestedScope direction (inward/outward, 2026-09-04 B-34 신설. 기본값에 기대지 말고 실측한다 — 아래 근거)

direction 은 이 표의 다른 속성과 성격이 다르다. 나머지는 기본값이 틀려도 그림이 뒤집히는 정도지만, NestedScope 는 값이 배열 순서의 해석 자체를 뒤집어 안쪽·바깥쪽이 바뀐 틀린 그림이 나온다(위 schema-v0.5.md 8.2절). 그래서 추출기(T-7)는 실제 컴포넌트의 동심원을 보고 라벨 순서상 첫 항목이 안쪽인지 바깥쪽인지 실측해서 채워야 하고, 기본값(inward)으로 채우지 않는다. 값은 inward/outward 열거형으로 한정한다 — 수치나 자유 문자열로 두면 실측 결과를 코드가 판정할 수 없다.

그리고 코드에도 이미 들어와 있다. capability.ts 의 CapabilityEntry.reads 가 content / params / relations 로 갈라서 담고 있고, fallback 할 때 앞의 것은 옮기고 뒤의 것은 버린다는 규칙까지 붙어 있다(규약 3.4절).

그래서 5단계의 가장 큰 위험이 이미 제거돼 있다. 새 어휘를 발명할 필요가 없다. 만들 것은 어휘가 아니라 그 어휘를 컴포넌트 쪽에서도 채우는 추출기다.

5단계를 한 문장으로: 지금은 Summary(질의)만 direction: ascending 을 말한다. 컴포넌트(색인)는 그걸 말하지 못한다. 양쪽이 같은 어휘로 말하게 만드는 것이 5단계다.

2.2 comp-sign 은 노드 속성을 받을 자리가 없다#

buildContentReplacement 의 입력은 {textTree, variant, textMap, textNodeRoles, sheetJson} 이고 RLSC 를 아예 안 받는다. 노드 역할과 용량은 text_node_roles 가 담는데 그 타입이 {role, chars} 두 필드로 닫혀 있다.

즉 4단계와 5단계 대치를 comp-sign 위에 얹으려면 외부 패키지 API 변경이 필요하다. 그리고 그 일정은 comp-sign 을 만드는 쪽에 달려 있다.

결정 D-1 로 이어진다 (5절): 4단계판과 5단계판 결과물은 자체 검색과 대치 경로(binding.ts) 위에 만든다. comp-sign 경로는 손대지 않고 대조군으로 남긴다. 그러면 comp-sign 판올림을 기다리지 않고 두 결과물을 완성할 수 있고, 나중에 비교할 상대도 자동으로 확보된다.

2.3 comp-sign 검색은 해시 하나가 아니라 변형 최대 100개다#

buildCompSearchData(markdown) 는 입력 하나에서 변형(variant) 을 최대 100개 만들고 각각이 structure_hash 하나다. 우선순위 순서가 있다.

기본 → 키:밸류 분리 → 제목 제외 → 자식 수 확장 → 자식 병합(단계 오름차순)

각 변형은 채점에서 감점을 받는다(unwrapPenalty, expansionPenalty, mergeChildrenPenalty × 병합단계). 이는 ../../valid-component/content-visualization-search-signature-v2-blueprint.md 0절의 원칙("fallback 을 키우기보다 exact match 에 걸릴 표면적을 넓힌다")을 그대로 구현한 것이다.

이것이 이전 진단을 뒤집는다. 결함의 최대 뿌리로 지목된 자식 병합은 comp-sign 의 실수가 아니라 가장 낮은 우선순위의 마지막 수단이고 이미 벌점을 받고 있다. 거기서 결함이 나온다는 것은 앞쪽 우선순위 변형이 색인에서 아무것도 못 건졌다는 뜻이다. 원인이 "대치가 잘못한다"가 아니라 "색인에 앞 변형으로 걸릴 컴포넌트가 없다"로 바뀐다.

2.4 노드 태그의 재료 절반은 이미 있다#

4단계 노드 태그를 처음부터 라벨링할 필요는 없다.

필요한 것 지금 있는 것
노드마다의 역할 structure_json 의 RLSC role-v2 (packages/structured-layout/src/role-v2.ts, 26종 중 22종 관측)
반복 단위 그룹 patternLabel · patternDimensions — 노드 35,094개(94%) 에 이미 붙어 있다 (src/lib/post-process/pattern/addPatternToRLSC.ts)
노드 ↔ 화면 요소 결합 RLSC 노드 식별자 = sheet 요소 식별자. 색인 2,468건의 텍스트 자리 17,899개 전량에서 결합률 100% 로 확인
의미 카테고리 없다. 이것만 새로 만든다

즉 시맨틱 컴포넌트 경계 추출기가 새로 만들 것은 "이 노드 묶음이 41종 중 무엇인가" 하나뿐이고, 그 판단의 입력(역할과 반복 단위와 화면 배치)은 전부 갖춰져 있다.


3. 무엇을 만드나: 세 결과물#

mermaid
flowchart TB
  subgraph shared["공통 색인 (테이블로 적재)"]
    Sig["컴포넌트 시그니처"]
    Tag["노드 태그"]
    Prop["노드 속성"]
    Sig --> Tag --> Prop
  end

  subgraph out3["현행판 (있음)"]
    A1["컴포넌트 단위 태그로 검색"]
    A2["슬롯 순서대로 채운다"]
    A1 --> A2
  end

  subgraph out4["4단계판 (만든다)"]
    B1["노드 단위 태그로 검색"]
    B2["태그가 맞는 노드에만 채운다"]
    B1 --> B2
  end

  subgraph out5["5단계판 (만든다)"]
    C1["노드 태그 + 카테고리 속성으로 검색"]
    C2["속성이 맞는 자리에 맞는 값을 채운다"]
    C1 --> C2
  end

  Tag --> B1
  Prop --> C1

현행판은 이미 있다. 컴포넌트에 붙은 태그로 후보를 찾고, 슬롯을 순서대로 채운다. 이게 지금 PoC 의 "시멘틱 검색" 경로다.

4단계판이 더하는 것은 범위의 특정이다. 지금은 "이 컴포넌트 어딘가에 절차가 있다"까지만 알 수 있어서, 절차 4단계를 넣으면 컴포넌트의 어느 부분이 절차인지 모른 채 슬롯 순서대로 채운다. 노드 태그가 붙으면 절차에 해당하는 노드 묶음에만 채우고 나머지는 건드리지 않는다.

검수 화면이 그 사이에 있다. /visual-summary/iis-boundary 가 뽑힌 경계를 페이지 실물 위에 상자로 얹어 보여 주고 판정을 바로 저장한다. 경계가 맞았는지는 노드 식별자와 카테고리 이름을 읽어서는 판정할 수 없어서다. 그 묶음이 화면 어디를 차지하고 안에 무슨 글이 있는지를 봐야 답이 나온다. 판정은 판별로 남아 골든셋의 씨앗이 된다.

5단계판이 더하는 것은 방향과 배치의 일치다. 사용자가 "위로 올라가는 계단"을 적었는데 컴포넌트가 내려가는 계단이면 지금은 그걸 알 방법이 없다. 노드 속성이 붙으면 direction 을 맞춰 보고, 안 맞으면 후보에서 빼거나 순서를 뒤집어 채운다.


4. 테이블: 무엇을 실물로 만들고 무엇을 안 만드나#

지금 PoC 는 서버가 뜰 때 색인을 메모리에 즉석으로 만든다(2,468건에 33초, 30분 캐시). 이 방식은 세 가지를 못 한다. 규모를 못 키우고(실서비스 178,095건), 재현이 안 되고(캐시 만료 후 다시 짓는다), 노드 태그와 속성처럼 비싸게 만든 것을 못 남긴다.

노드 라벨링은 LLM 비용이 드는 일회성 작업이다. 메모리에 두면 서버를 내릴 때마다 사라진다. 그래서 테이블이 필요하다.

만드는 것 넷#

테이블 무엇을 담나 왜 실물이어야 하나
컴포넌트 시그니처 comp-sign 시그니처 (구조 해시, 텍스트 맵, 노드 역할, 채점 자질, 지문, 용량) — 이석님 comp_signs 와 같은 모양 33초 재빌드가 매 기동마다 든다. 5만 건 이상으로 못 키운다. 실서비스 테이블로 갈아탈 때의 계약을 미리 맞춰 볼 수 있다
경계 지도 (iis_semantic_component_boundary) 컴포넌트 안 어느 노드 묶음이 어느 카테고리인가 (4단계). 계약은 9.4절 추출에 LLM 비용이 든다. 사라지면 다시 돈이 든다. 사람이 고친 라벨도 여기 남아야 한다
속성 지도 (iis_semantic_component_property) 경계 안 어느 노드가 어느 카테고리 고유 속성인가 (5단계). 계약은 9.5절 위와 같다. 더해서 속성은 추출기 판올림마다 다시 뽑히므로 판별 비교가 필요하다
경계 골든셋 (iis_boundary_annotation) 사람이 확정한 경계 정답과 그 판정 상태 (accepted, corrected, unsure, needs_correction). 라벨러와 라벨 시각, 그리고 판정 당시의 LLM 예측 snapshot 을 함께 담는다 판을 올릴 때마다 경계는 다시 뽑히지만 사람이 확정한 정답은 남아야 채점만 다시 돌릴 수 있다. 판정 당시 예측을 함께 저장해 두면 "그림을 넣어서 얼마나 올랐나" 를 나중에도 표로 답할 수 있다

안 만드는 것#

평가 결과 적재 테이블은 만들지 않는다. 지금은 생성 스냅샷과 채점 결과를 파일로 저장소에 커밋하고 --diff 로 회귀를 본다. 이 방식이 이미 재현되고, 저장소 이력이 곧 변경 이력이고, 리뷰에서 눈에 보인다. 테이블로 옮기면 이 셋을 다 잃는다.

태그와 속성의 원본 라벨링 로그도 테이블로 안 만든다. 어느 프롬프트로 무엇을 물었는지는 저장소의 프롬프트 파일과 실행 스크립트가 이미 정본이다.

스키마가 지켜야 할 것#

판(version)을 반드시 담는다. 시그니처는 SIGNATURE_VERSION(현재 36), 태그와 속성은 두 추출기의 판이다. 판이 다른 행이 섞이면 비교가 성립하지 않는다. 이건 comp-sign-bridge.md 에서 이미 겪은 문제다. 평가 도구가 지문(fingerprint)을 안 맞추면 비교를 거부하도록 만든 이유가 이것이다.

출처를 담는다. 라벨이 LLM 에서 왔는지, 사람이 고친 것인지, 나중에 I.I.S. 가 준 것인지가 구분돼야 한다. I.I.S. 가 풀렸을 때 우리 대행 라벨을 통째로 교체할 수 있어야 한다.

노드 식별자는 RLSC 노드 식별자를 쓴다. 컴포넌트 안에서 유일한 것을 전수로 확인했다(9.1절). 새 식별자 체계를 만들면 sheet 요소와의 결합을 다시 증명해야 한다.

마이그레이션은 저장소 정책대로 pnpm db:migration:new 로 만든다.


5. 판단이 필요한 것#

이 절이 리뷰의 본체다. 각 항목에 제안과 그 근거, 감수해야 하는 것을 함께 적는다. 확정된 것은 누가 언제 정했는지를 제목에 남긴다.

D-1. 4단계판과 5단계판을 자체 검색과 대치 경로 위에 만든다 (2026-08-15 정원석 확정)#

확정: 만든다. 근거는 2.2절이다. comp-sign 은 노드 속성을 받을 자리가 없고, 그것을 여는 API 변경은 comp-sign 을 만드는 쪽의 일정에 달려 있다.

이것은 우회로가 아니라 갈림길이다. 자체 경로를 만들다 보면 comp-sign 을 쓸 이유가 없어질 수 있다. 노드 경계와 속성을 아는 쪽에서는 검색도 대치도 훨씬 단순해진다. 어느 묶음에 무엇을 넣을지가 이미 특정돼 있으니, 변형 100개를 만들어 해시를 맞추고 자식을 병합해 가며 밀어 넣을 필요가 없다. 그러면 자체 구현이 더 명료하고 더 짧다.

그래서 이 작업의 끝에는 결과에 따라 셋 중 하나가 온다.

결과 그 다음
자체 경로가 확실히 낫다 comp-sign 을 대체한다. 검색·대치를 자체 구현으로 가져온다
낫지만 comp-sign 의 채점·변형이 여전히 필요하다 comp-sign 을 바꾼다. 노드 속성을 실을 수 있게 API 확장을 요청한다
4·5단계가 별 차이를 못 만든다 둘 다 안 한다. 그것도 답이다

"자체 경로는 실서비스 코드가 아니다"를 약점으로 적지 않는다. 결과가 좋으면 그때 실서비스가 바뀌어야 한다. 결과를 덜 믿을 이유가 되지는 않는다. 오히려 만들어서 재는 것이 먼저다. 값이 있는지 모르는 채로 comp-sign 을 고쳐 달라고 할 수는 없다.

D-2. 시맨틱 컴포넌트 경계를 우리가 대행 생산하고, 출력 계약도 우리가 먼저 정해 제안한다 (2026-08-15 정원석 확정)#

확정: 한다. I.I.S. 계열 티켓 넷이 2026년 6월 중순 이후 멈춰 있고, 재개 시점이 안 정해졌다. 기다리면 4단계판을 못 만든다.

어떻게: LLM 으로만 한다 (2026-08-15 정원석 확정). structure_json 을 LLM 에 주고 노드 묶음마다 카테고리를 고르게 한다.

규칙 기반 경계 판정은 하지 않는다. patternLabel 과 RLSC role 로 경계의 위치를 결정적으로 뽑아 LLM 은 카테고리만 고르게 하자는 안을 냈다가 기각됐다. 규칙으로는 경계를 판단할 수 없다. 반복 단위와 역할은 배치 정보이지 의미 경계가 아니다. 같은 patternLabel 묶음이 절차일 수도 목록일 수도 있고, 반대로 의미상 한 경계가 여러 반복 단위에 걸쳐 있을 수도 있다. 규칙으로 뼈대를 먼저 고정하면 LLM 이 고칠 수 없는 자리에 오답이 박힌다.

2.4절이 "재료 절반은 이미 있다"고 적은 것은 LLM 에게 줄 입력이 갖춰져 있다는 뜻이지, 그것으로 경계를 계산할 수 있다는 뜻이 아니다.

포함 관계가 순서를 강제한다. 속성은 경계를 포함하므로(1절) 경계 라벨을 먼저 세워야 속성 추출이 딛고 설 자리가 생긴다. 4단계판을 먼저 만드는 것은 5단계판을 위해 버리는 일이 아니라 그 절반을 미리 짓는 일이다.

출력 계약은 우리가 먼저 정한다 (2026-08-15 확정). 대행 라벨이 I.I.S. 가 낼 것과 같은 모양이어야 나중에 교체가 되는데, I.I.S. (Semantic Component Boundary) 의 출력 모양이 아직 안 정해졌다. 계열 티켓 넷이 2026년 6월 중순부터 멈춰 있어 기다리면 답이 언제 올지 모른다.

그래서 우리가 먼저 정하고 RAT pod 에 제안한다. 이 순서가 협의에도 유리하다. 빈 스키마를 놓고 논의하는 대신, 실물 라벨 수천 건과 사람 검수 일치율을 근거로 들고 갈 수 있다. 규약이 이미 카테고리 41종과 어휘를 확정해 뒀으므로 새로 발명할 것도 적다.

계약의 모양: RLSC 를 손대지 않고 옆에 지도를 둔다 (2026-08-15 정원석 확정). RLSC 스키마 자체는 건드리지 않는다. 대신 RLSC 위의 노드를 가리키는 별도의 지도를 둘 만든다.

지도 무엇을 가리키나 단계
경계 지도 RLSC 의 어느 노드가 시맨틱 컴포넌트 경계인가, 그리고 그 경계가 어느 카테고리인가 4단계
속성 지도 경계 안의 어느 노드(layout container 또는 element)가 어느 속성인가, 그리고 그 값 5단계

이 모양이 두 가지를 동시에 푼다. RLSC 는 다른 여러 소비자가 함께 쓰는 것이라 스키마를 바꾸면 그쪽이 다 영향을 받는데, 지도는 옆에 붙었다 떨어졌다 할 수 있다. 그리고 I.I.S. 가 실물을 내놓으면 지도만 통째로 갈아 끼우면 된다.

지도가 갖춰야 할 것은 4절 스키마 규칙(판, 출처, RLSC 노드 식별자)과 같고, 거기에 하나를 더한다. 한 컴포넌트가 카테고리 여러 개를 품을 수 있다(README 3절: 복수 태그 260개). 경계 지도가 경계 여럿을 담을 수 있어야 한다.

D-3. 테이블은 로컬에서 설계하고, 배포는 마이그레이션이 한다 (2026-08-15 정원석 확정)#

여기에는 고를 것이 없었다. 로컬과 실서비스를 갈라 놓고 고민할 일이 아니다. 저장소의 마이그레이션이 실서비스에도 그대로 반영되므로, 로컬에서 설계하고 테스트하면 배포는 따라온다. 다른 저장소 작업과 같은 흐름이고, pnpm db:migration:new 로 만들면 된다.

남는 것은 로컬에서 얼마나 큰 색인으로 시험하느냐뿐이다. 실측으로 시그니처 빌드가 2만 건에 약 4.4분, 5만 건에 약 9.9분, 전량 173,357건에 38.6분이다. 개발 중 되돌려 가며 시험하기에는 5만 건이 현실적 상한이고, 메모리 사용량은 아직 안 재 봤다. 이건 결정이 아니라 실행할 때 확인할 사항이라 T-2 로 옮긴다.

D-8. 경계 추출 어휘는 49종 전부, 생성 어휘는 따로 정한다 (2026-08-15 정원석 확정)#

추출은 49종 전부를 쓴다. 색인 태그 어휘 49종을 그대로 경계 카테고리로 받는다. 규약 41종으로 제한하면 Geo_* 와 PageNavigation_* 로만 태깅된 312건(색인의 12.6%) 에 붙일 카테고리가 없어 전수 커버리지(9.4절)가 성립하지 않는다.

검색 풀에서 빼는 것과 추출하는 것은 별개다. 검색 풀 제외는 그대로 두더라도 경계는 뽑는다. 빼는 것과 모르는 것은 다르다.

생성(Visual Summary) 이 낼 수 있는 카테고리는 따로 정한다. 4단계와 5단계 모두 아래를 따른다.

갈래 생성이 낼 수 있나 왜
규약 41종 낸다 지금과 같다
PageNavigation_* 4종 낼 수 있어야 한다 표지·목차·구분·마무리는 사용자 내용에서 실제로 나온다
Geo_* 4종 내지 않는다 지도는 Visual Summary 가 다룰 것이 아니다

그래서 규약에 후속 과제가 하나 생긴다. PageNavigation_* 4종이 생성 어휘에 들어가려면 ../spec/capability-table.md 에 성립 조건과 선택 속성과 대체 순서가 정의돼야 한다. 지금은 없다. 이건 규약 쪽 결정이라 이 문서는 필요하다는 것만 적는다.

T-1 의 판정은 지금 틀리지 않았다. 후보 전량 기준으로 "규약 어휘 밖" 결함은 PageNavigation_* 48건, Geo_* 14건이다. 생성기가 아직 PageNavigation_* 을 못 내므로 요청 카테고리는 언제나 41종 안이고, 사용자가 목차를 요청하지 않았는데 목차 컴포넌트를 고른 것은 여전히 부당한 대체다.

바뀌는 시점은 규약 편입과 생성기 교체가 끝난 뒤다. 그때는 목차 요청 → 목차 컴포넌트가 거리 0 이 되어 결함에서 빠진다. capability.ts 에 항목이 추가되면 fallbackDistance 가 알아서 재므로 채점기는 따로 고칠 것이 없다.

D-9. 경계를 색인 전량에서 뽑는다 (2026-08-15 정원석 확정)#

평가셋이 건드리는 컴포넌트만 뽑으면 검색이 못 보는 후보가 생겨 비교가 깨진다. 로컬 색인 2,468건 전량에서 뽑는다.

D-10. 경계 추출기가 그림을 본다 (2026-08-15 정원석 확정)#

확정. 사진을 안 보면 절대로 올바르게 예측할 수 없다. 사람 검수에서 틀린 10건 중 8건이 모양으로 정해지는 카테고리였다(6절 T-4 사람 검수 결과).

컴포넌트만 잘라서 준다. 페이지 썸네일에 여러 컴포넌트가 있으면 다른 것까지 보고 판단하게 된다. 자를 좌표는 이제 있다. design_object_sheet_json.nodes[0].data.props.boundingBox 가 페이지 안에서의 컴포넌트 위치다(9.1.2절).

글을 빼지 않는다. 그림만으로는 "1단계와 2단계"와 "강점과 약점"을 가를 수 없다. 지금의 투영 JSON 과 잘린 그림을 함께 준다. 어느 쪽이 없어도 절반은 못 맞힌다.

감수하는 것: 토큰이 는다. 지금 전량 어림이 입력 약 1,520만인데 그림이 붙으면 더 커진다. 실측하고 나서 전량을 돌린다.

D-11. 이종 병치의 최상위는 Unclassified (2026-08-15 정원석 확정: 나)#

확정. 서로 다른 의미가 나란히 놓인 컴포넌트에서 뿌리 경계는 "둘을 담고 있다"는 것 외에 성질이 없는데, 어휘 49종에 그런 값이 없다. 그래서 모델이 아무거나 고르고, 그 값이 그대로 검색에 쓰이면 없는 성질로 후보를 거르게 된다.

선택지가 둘이다.

(가) Enumeration_List 를 쓴다. 규약이 "마지막 fallback 은 항상 List"라고 정의하므로(capability-table 3절) 근거가 있다. 대신 진짜 목록과 구분되지 않는다. 검색이 이 뿌리를 목록 후보로 취급한다.

(나) 어휘 밖의 표식을 하나 둔다 (Unclassified 같은 것). 진짜 목록과 섞이지 않고, 검색 풀에서 빼기도 쉽다. 대신 49종 밖이라 어휘를 하나 늘리는 결정이 된다.

(나)로 정했다. (가)는 값이 틀린 채로 조용히 검색에 흘러 들어가는데, 그건 T-1 에서 겪은 "판정 불가를 통과로 넘기는" 실수와 같은 모양이다.

값이 하나 생기니 모르는 것이 전부 그리로 흘렀다. 첫 실행에서 28건에 38번 쓰였고 깔때기까지 Unclassified 였다. 그래서 검사를 걸었다. 서로 다른 카테고리의 자식 경계가 둘 이상일 때만 허용한다(unclassifiedWithoutMix). 38 → 3 으로 줄고 뿌리 정확도는 유지됐다.

한 번 더 좁혀야 했다. 차트와 그 제목과 설명을 갈라 놓고 뿌리를 Unclassified 로 두는 경우가 있어, "하나의 표현에 딸린 제목, 설명, 범례, 수치는 그 표현의 일부" 를 프롬프트에 명시했다.

골든셋은 범위가 다르다(2026-08-30 제안). Gold annotation 에서는 경계가 v0.5 규약에 담을 자리가 있는 core 노드만 덮고, 컴포넌트 수준 제목·설명은 위성으로 경계 밖에 둔다. 위 문장은 추출기 프롬프트(boundary-v11) 계약으로만 남는다 → policy/iis-boundary-gold-core-scope.md

D-11a. Unclassified 를 "이름 없음" 에도 쓴다 (2026-08-29 정원석님 확정)#

확정. 어휘 49종 어느 이름으로도 내용 대치를 할 수 없는 컴포넌트에는 그 이름을 붙이지 않고 Unclassified 로 둔다. 이를 위해 unclassifiedWithoutMix 가 막던 자식 경계가 없는 Unclassified 를 허용한다.

위 문단의 "깔때기까지 Unclassified 였다" 와 방향이 반대로 보이지만, 목적은 같다. 두 결정 모두 틀린 이름이 조용히 검색에 흘러드는 것을 막는다. 다른 것은 무엇을 틀렸다고 보느냐다.

2026-08-15 에 막은 것 2026-08-29 에 여는 것
상황 깔때기 각 층에 글이 얹혀 있어 대치가 되는데도 "모르겠다" 고 Unclassified 로 갔다 깔때기 각 층에 글이 없어 대치가 안 되는데 Conversion_Funnel 이 붙었다
왜 틀렸나 쓸 수 있는 컴포넌트를 검색 풀에서 뺐다 못 쓰는 컴포넌트를 검색 풀에 넣었다
판정 Unclassified 가 오답 Conversion_Funnel 이 오답

두 상황을 가르는 기준은 2026-08-29 에 생겼다. 그전에는 "대치가 되는가" 를 물을 수단이 없어 자식 경계가 둘 이상인가라는 구조적 대리 지표로 도피를 막았다. 이제는 모양이 나눠 준 칸에 글이 얹혀 있는가를 직접 본다(boundary-v11 의 SLOT_BEARING_REQUIREMENTS). 칸에 글이 있으면 대치가 되므로 그 이름을 쓰고, 모양이 장식 한 장뿐이고 글이 그 밖에 있으면 대치가 안 되므로 Unclassified 다.

실측 근거는 design_object 5657451 이다. 순환 그림이 Element.Decoration box=[76,183,289,289] 한 장이고 글 두 개가 그 위(y 32)와 아래(y 578)로 떨어져 있는데 Cycle_Cycle 이 붙었다. 순환의 단계를 담은 글이 없어 이 컴포넌트로는 순환을 표현할 수 없다.

도피를 막는 장치는 그대로 둔다. 자식 경계가 하나뿐인 Unclassified 는 계속 거절한다. 그때는 그 자식의 이름을 뿌리에 쓰면 되고, Unclassified 가 그 이름을 가릴 뿐이다.

자식 경계 2026-08-15 2026-08-29
없음 거절 허용 (이름 없음)
하나 거절 거절 (그 자식의 이름을 쓴다)
둘 이상, 이름이 서로 다름 허용 허용 (이종 병치)

프롬프트 쪽 출구는 좁게 열어야 한다. 계약만 열고 프롬프트 규칙 6 을 "붙일 이름이 없을 때" 로 넓게 열었더니, design_object 6103052(chevron 칸마다 더미 글이 얹힌 ProcessFlow_Steps)가 5회 중 3회 Unclassified 로 갔다. 2026-08-15 에 막았던 도피가 프롬프트 쪽에서 그대로 되살아난 것이다. 그래서 출구를 자리 그림이 장식으로 판명된 자리에만 두었다.

한 번 더 헛디딘 것도 남긴다. 그 도피를 "글이 있으면 거의 언제나 이름이 있다" 는 문장으로 막았더니 수치는 좋아졌지만 그 문장은 사실이 아니다. 글이 많아도 꽂을 자리가 없을 수 있고, 이 결정과도 부딪힌다. 진짜 원인은 따로 있었다. 6103052 는 구분선이 다섯 칸을 긋는데 글이 얹힌 칸은 둘뿐이고, 요건을 "각 칸마다" 로 써서 빈 칸 하나에 통째로 탈락한 것이다. 요건을 "자리 중 둘 이상에" 로 고치고 그 문장은 걷어냈다. 칸을 다 채워야 하는 것이 아니다.

실측 (각 판 5회, gpt-5.6-luna low). scripts/probe-boundary-prompt-ab.ts --repeat 5 가 두 판을 같은 컴포넌트에 나란히 재실행한 결과다. 판별을 한 번씩만 재고 비교하면 모델이 흔들린 것을 프롬프트가 고친 것으로 읽는다 — 실제로 첫 1회 A/B 는 "바뀜 5건" 을 냈는데 그중 상당수가 노이즈였다.

design_object boundary-v10 boundary-v11 무엇을 보나
5657451 (순환 그림이 장식) Cycle_Cycle 5회 TextBasedCommunication_LongFormTextLayout 5회 겨냥한 오답이 0/5
6103052 (chevron 칸에 더미 글) Steps 3, LongForm 1, Unclassified 1 ProcessFlow_Steps 4, Unclassified 1 도피를 막았나
5669734 (깔때기 층에 글 없음) Conversion_Funnel 5회 Unclassified 4회 못 쓰는 이름을 뺐나
Gantt, Pictogram, Funnel 등 분포 동일 분포 동일 차트 계열 회귀 없음

6103052 는 boundary-v10 자체가 5회 중 3회만 맞히는 어려운 케이스다. v11 이 4회로 더 안정적이다.

D-4. 노드 속성을 어디까지 뽑나 (2026-08-15 보류)#

보류. 한때 41종 전부로 확정했으나, 속성은 지금 확정하지 않는다(2026-08-15 정원석). 5단계에 착수할 때 다시 정한다. 아래는 그때 쓸 재료로 남긴다.

한 안은 41종 전부의 속성을 뽑는 것이다. 어휘는 ../spec/capability-table.md 2절 의 선택 속성을 그대로 쓴다. 새로 만들지 않는다.

검증 가능성이 두 갈래로 갈린다. 이걸 표에서 갈라 놓지 않으면 "41종 다 됐다"가 실제보다 크게 읽힌다.

갈래 종수 무엇인가 이 PoC 에서
A군 25 텍스트 대치만으로 그려지는 것 — 나열·비교·순서·계층·텍스트·사람 계열 결과물로 확인된다. 속성이 틀리면 렌더에서 보인다
B군 16 matrix·measure 데이터가 있어야 그려지는 것 — 사분면·깔때기·간트·달력·큰숫자·게이지와 차트 열 종 지금은 값만 적재된다. 아래 D-7 이 이걸 바꿀 수 있다

B군에서도 텍스트로 나가는 속성(axisLabels, unit, centerLabel, weekStart 등)은 부분적으로 확인된다. 순수 표현 속성(startAngle, sizeScale, normalize)은 차트에 데이터를 넣기 전에는 확인되지 않는다.

D-7. 차트 데이터 대치를 이 PoC 범위에 넣을 것인가#

원석님 지적으로 열린 항목이다. 나는 "차트와 이미지 데이터 대치는 범위 밖"을 이미 정해진 것으로 적어 왔는데, 그 전제가 약하다.

차트는 미리캔버스 element 다. 이석님 대치 로직에 없다는 것이 "할 수 없다"는 뜻은 아니다. 그 element 에 데이터를 주입하는 방식으로 대치를 구현하면 되고, D-1 대로 자체 경로 위에 만들기 때문에 comp-sign 을 기다릴 일도 없다. 그러면 B군 16종 중 차트 계열이 A군과 같은 자격으로 검증된다.

이미지는 다르다. 그림 자체를 사용자 내용으로 바꾸는 것은 이 PoC 가 다룰 일이 아니다. 여기는 계속 범위 밖으로 둔다.

판단이 필요한 것: 차트 데이터 주입 대치를 지금 넣을 것인가, 4단계판과 5단계판을 먼저 끝내고 넣을 것인가. 넣으면 B군의 절반 이상이 결과물로 확인되지만, 대치 구간에 새 갈래가 하나 생겨 4단계판 완성이 늦어진다.

내 제안은 T-8 뒤로 미루는 것이다. 4단계와 5단계의 값을 판정하는 데 차트가 꼭 필요하지는 않다. A군 25종이면 방향, 순서, 계층이 다 덮인다. 넣는 것이 나중에도 어렵지 않기 때문이다. 다만 "범위 밖"이 아니라 "순서상 뒤"라고 적는다. 둘은 다르다.

→ 2026-09-17 후기. "element 에 데이터 주입"(어댑터) 전제는 2026-09-04 B-13 결정으로 주력 경로에서 빠졌다(../TODO.md B-13 근거) — 주력 대치 셋은 실제 컴포넌트에 내용을 채우는 방식이라 I.I.S. 구조에 의존한다. 그래서 "순서상 뒤"였던 B군의 표·차트 8종(첫 출시 P0 18종 안)은 어댑터가 아니라 T-7 의 dataset 지도로 들어온다. 6절 T-7 2026-09-17 항목, MOR-2199.

D-5. 4단계판과 5단계판의 "완료"를 무엇으로 정하나#

제안: 아래 셋을 다 만족하면 완료로 본다.

첫째, 평가셋 50건 전부가 그 판으로 끝까지 돌아간다. 검색되고, 대치되고, 렌더된다. 실패 0.

둘째, 그 판에서만 가능한 동작이 회귀 테스트로 고정된다. 4단계판이면 "태그가 안 맞는 노드는 안 건드린다", 5단계판이면 "direction 이 어긋난 후보는 빠지거나 뒤집혀 채워진다" 같은 것이다.

셋째, 세 판(현행, 4단계, 5단계)의 결과가 같은 채점기로 한 표에 나란히 찍힌다.

여기에 "현행보다 좋다"는 넣지 않는다. 그건 완료 조건이 아니라 이 결과물로 답할 질문이다. 넣으면 결과가 나쁠 때 완료를 선언할 수 없어 작업이 멈춘다.

D-6. 채점기가 의미를 못 재는 문제를 먼저 고친다 (2026-08-15 완료)#

채점기가 절차 4단계를 지도에 넣고 1점(거의 완벽)을 주고 있었다. 세 판을 나란히 놓아도 이 자로 재면 의미를 무시하는 쪽이 이긴다. T-1 에서 고쳤고 결과는 6절에 있다.

골든셋이 오면 이 판정이 한 단계 올라간다. 허재영님 쪽에서 사용자 입력 → Visual Summary 골든셋(MOR-1928)이 나오면, 지금의 의미 거리가 답하지 못하는 것을 답할 수 있다.

지금 의미 거리가 재는 것은 **"규약이 이 대체를 허용하는가"**다. 규약 문서가 정답이므로 사람 판정이 필요 없고, 그래서 싸고 빠르다. 대신 규약 자체가 틀렸을 때는 같이 틀린다.

골든셋이 재게 해 주는 것은 **"사람이 보기에 이 카테고리가 맞는가"**다. 이건 규약보다 위에 있는 기준이라, 규약의 대체 순서가 현실과 어긋나는 지점까지 드러낸다.

그래서 둘은 대체 관계가 아니라 겹쳐 쓰는 관계다. 골든셋이 오면 (1) 생성기가 고른 카테고리가 맞았는지를 직접 채점할 수 있고, (2) 의미 거리 판정이 골든셋과 얼마나 일치하는지를 재서 규약의 대체 순서 자체를 검증할 수 있다. 두 번째가 특히 값이 크다. 지금은 규약을 검증할 방법이 없다.

다만 기다리지 않는다. 골든셋 일정이 우리 것이 아니고, T-1 만으로도 두 경로의 격차가 2.4 에서 0.2 로 줄었다.


6. 할 일#

순서대로 적는다. 각 항목의 완료 판정을 함께 적는다.

순서를 한 번 바꿨다 (2026-08-15). 처음에는 시그니처 테이블(T-2)과 색인 분포 교정(T-3)을 맨 앞에 뒀는데, 다시 보니 둘 다 4단계판을 만드는 데 지금 필요하지 않다. 테이블이 필요한 이유로 적은 "메모리에 두면 사라진다"는 시그니처에 잘 안 맞는다. 시그니처는 33초면 다시 지어지고 돈이 들지 않는다. 사라지면 안 되는 것은 LLM 비용이 든 경계 지도와 속성 지도다. 시그니처 테이블이 실제로 주는 것은 규모와 기동 속도인데, 평가셋 50건은 지금 색인으로 이미 돈다.

그래서 결과물을 먼저 만들고, 바닥 공사는 세 판을 나란히 놓기 직전으로 미룬다. 비교표를 낼 때는 색인 규모와 분포가 결과를 좌우하므로 그때는 반드시 해야 한다.

1단계: 채점기 (세 판 모두가 딛는다)#

T-1. 의미 적합성 결함을 채점기에 넣는다. 완료 (2026-08-16)

audit.ts 에 결함 mapping.categoryDrift 를 더했다. 판정은 하나다. 검색이 허용한 카테고리 안에서 골랐는가. 밖에서 골랐으면 가장 무거운 등급이다.

허용 집합은 채점기가 계산하지 않는다. 검색 모듈의 search.acceptedCategoriesFor(query, candidates) 가 유일한 자리이고, 채점기는 그것을 인자로 받는다. 그 집합은 검색이 실제로 쓰는 네 군 전부다. 1군과 2군(질의 태그), 3군(확정 카테고리), 4군(안전망, 발동했을 때만).

결과: 앞서 있던 격차의 대부분이 사라졌다.

새 결함은 시멘틱 검색 0건, 구조 검색 23건이다. 시멘틱 경로가 0 인 것은 정상이다. 후보를 고른 것이 검색 자신이라 정의상 허용 밖으로 나갈 수 없다. 이 결함이 실제로 재는 것은 구조(comp-sign) 경로가 의미를 얼마나 벗어나는가다.

수치가 네 번 움직였다. 전부 내 계측 버그였다.

언제 시멘틱 구조 무슨 일이었나
T-1 이전 19.9 17.5 의미를 아예 안 쟀다
첫 보고 19.9 20.1 틀림 — auditBoundRlsc 가 판정 인자를 안 넘겨 시멘틱 경로가 판정되지 않았다
두 번째 20.2 20.1 여전히 틀림 — 경로별 지표가 두 경로 중 나은 채점을 빌려 썼다
세 번째 20.3 21.9 경로별 지표는 고쳤으나 허용 집합을 채점기가 따로 계산했다
네 번째 20.0 21.6 검색에서 받게 했으나 1·2군만 받아 3·4군 후보가 전부 결함으로 잡혔다
지금 20.0 22.0 검색이 쓰는 집합 전부를 받는다 (2.-1절)

다섯 다 PR 리뷰가 잡았다. 다섯 번 모두 원인이 같다. 같은 규칙을 두 곳에서 구현했다.

마지막 정정에서 구조 점수가 21.6 → 22.0 으로 나빠진 것이 옳다. 허용 집합이 넓어지면 과탐은 줄지만, 전에는 집합이 비어 판정을 통째로 건너뛴 컴포넌트가 있었다. 그것들이 이제 판정된다.

규약이 허용한 대체는 결함으로 세지 않는다. 처음에는 대체 그래프 위의 거리를 재서 거리 1 이상도 가벼운 등급 결함으로 넣었는데, 그러면 clean 이 규약 위반과 규약대로 물러남을 구분하지 못한다. 거리 장치(fallbackDistance 와 nearestFallbackDistance)는 이후 전부 지웠다. 7절 기각한 안 참고.

지금 수치로는 시멘틱 검색이 가중 평균에서 앞선다.

pageNoBlocker 를 경로별로 갈랐더니 숨어 있던 것이 나왔다. 예전에는 시멘틱 1위만 보고 하나의 수를 냈는데, 이 도구의 목적이 두 경로 비교라 헤드라인이 한쪽에 치우쳐 있었다.

BAD 대용치 (높을수록 좋다) 시멘틱 구조
무거운 결함이 없는 입력 비율 8% 7%
(계측 못 한 입력 — 분모에서 뺐다) 0건 4건

한때 구조가 14% 로 두 배 나아 보였는데, 그 절반이 계측 실패였다. 구조 경로의 1위 후보에 감사 결과가 없으면(후보가 없거나, comp-sign 이 던졌거나, 용량 맵이 없다) 채점기가 무거운 결함을 낼 수 없는데, 그 입력이 "무거운 결함 없음" 으로 집계됐다. 시멘틱 경로는 이런 입력이 0건이라 구조 쪽만 부풀었다. 비교가 목적인 지표에서 한쪽만 부푸는 것이 가장 나쁜 종류의 오류다.

고친 방식은 성공으로 세지 않고 분모에서 빼고, 뺀 건수를 반드시 함께 낸다이다. 조용히 빼면 분모가 줄어든 것이 안 보여 같은 착시가 다시 생긴다.

이제 두 헤드라인이 같은 말을 한다. 가중 평균도(20.0 대 22.0) BAD 대용치도(8% 대 7%) 시멘틱 검색이 낫다고 한다. MOR-1929 가 둘 중 무엇을 헤드라인으로 쓸지는 여전히 정해야 하지만, 어느 쪽을 골라도 결론이 같다는 것이 지금의 답이다.

남는 결론은 구조 검색이 17.5 로 앞서 보이던 것의 대부분이 의미를 안 잰 덕이었다는 것이고, 이건 다섯 번의 정정 내내 유지됐다.

후보가 바뀐 컴포넌트는 0건이다. 순수한 채점 규칙 변경이고 랭킹에 되먹이지 않았다는 뜻이다.

함께 고친 것. --diff 가 가중 평균을 대조 항목에서 빠뜨리고 있었다. 경로 사이 우열을 가르는 유일한 지표인데 없었다. 이번 변경이 정확히 그 값을 움직였는데도 도구는 "+0pp" 만 찍었다. 결함 종류가 새로 생기면 옛 리포트에 키가 없어 NaN 이 나와 회귀 판정이 무력해지던 것도 고쳤다(빠진 것은 0 으로 읽는다).

처음 보고한 수치가 틀렸다 (2026-08-15 리뷰에서 잡힘). auditBoundRlsc 가 허용 집합 인자를 받아만 두고 auditReplacement 로 넘기지 않아 시멘틱 경로는 아예 판정되지 않았다. 그 0 건을 "시멘틱 검색은 의미를 잘 본다"로 읽고 우열이 뒤집혔다고 보고했다. 실제로는 13 건이었다(이후 판정 방식이 바뀌어 지금은 0 건이다. 시멘틱 경로는 정의상 허용 밖으로 못 나간다).

테스트가 버그를 안고 전부 통과했다. 결함의 단위 테스트는 auditReplacement 를 직접 부르고 픽스처 테스트는 새 인자를 안 줬다. wrapper 를 거치는 경로가 비어 있었다. 지금은 auditBoundRlsc 경유 회귀 테스트 셋이 그 자리를 막고, 전달을 도로 빼면 빨개지는 것까지 확인했다.

→ 완료 판정 충족: 부당한 대체 다섯 사례가 전부 가장 무거운 등급으로 잡히고 정당한 대체는 안 잡히는 것을 회귀 테스트로 고정했다. 테스트 319건 통과.

2단계: 4단계판#

T-4a. 경계 지도의 출력 계약을 확정하고 RAT pod 에 제안한다. D-2 의 실행. 계약은 아래 9절에 적었다. → 완료 판정: 계약이 9절에 적히고, 류민님께 전달된다. 답을 기다리지 않고 T-4 로 간다. 실물 라벨이 있어야 협의가 쉬워지기 때문이다.

T-4 중간 보고 (2026-08-15). 전량 추출은 아직 안 돌렸다.

배관은 선다. 마이그레이션, 투영, 계약 검사와 추출기가 다 있고 층화 표본 20건에서 계약 검사를 20/20 통과한다(평균 시도 1.5회). 비용도 알았다. 투영이 원본의 2.6분의 1(중앙값 6.0KB → 2.3KB)이라 자르지 않고 컴포넌트당 한 번이면 되고, 전량 어림이 입력 약 1,520만과 출력 약 40만 토큰이다.

그런데 판정 품질이 전량으로 갈 만큼 나오지 않는다. 뽑힌 뿌리 경계가 그 컴포넌트의 태그와 맞는 것이 20건 중 4건이다.

이 수치를 그대로 "정확도 20%"로 읽으면 안 된다. 컴포넌트 태그는 정답이 아니다. 컴포넌트 하나에 붙은 거친 라벨이고 그것도 LLM 이 붙였으며, 그 거칢을 없애는 것이 4단계의 목적이다. 어긋난 건을 실제로 열어 보니 어느 한쪽이 일방적으로 틀리지 않았다.

컴포넌트 글 태그 추출 보기에
5637357 Sub Topic 1 + 설명 하나 Enumeration_List LongFormTextLayout 추출 쪽이 낫다 — 항목이 하나다
5637608 企画背景+본문 · 企画目的+본문 Enumeration_List LongFormTextLayout 태그 쪽이 낫다 — 두 섹션의 병렬 나열이다

그래서 다음은 사람 판정이다. 태그와의 일치는 방향을 가리키는 신호일 뿐이고, T-4 의 완료 판정은 원래부터 "층화 표본 30건 사람 검수"다. 그 검수 없이 전량을 돌리면 틀린 라벨 2,468건을 만드는 데 돈을 쓰게 된다.

두 번의 프롬프트 수정으로 확인한 것. 카테고리 이름 49개만 주었을 때 일치가 0 이었다. 단어장의 뜻을 실었지만 일치는 안 올랐다. 정의 부족이 원인이 아니다. 그리고 첫 표본이 design_object_id 연속 8건이라 사실상 한 템플릿 계열이었다. 층화로 바꾼 뒤의 수치가 위의 4/20 이다.

계약 검사가 실측으로 둘 늘었다. 프롬프트로 부탁만 해서는 안 지켜지는 것들이다.

검사 무엇을 잡았나
relationWithoutSiblings 모델이 위상 관계를 "나는 품어져 있다"는 뜻으로 자식 쪽에 달았다. 규약 8.7절은 형제 사이의 관계라 부모에 단다
redundantNestedCategory "빠짐없이 덮으라"를 모든 컨테이너에 같은 라벨을 붙여 만족시켰다(StackedBarChart ⊂ StackedBarChart ⊂ …). 다른 검사는 전부 통과하므로 이것 없이는 못 잡는다

둘째가 특히 중요하다. 커버리지 검사만으로는 경계를 찾은 것과 트리를 다시 라벨링한 것을 구분하지 못한다.

T-4 사람 검수 결과 (2026-08-15). 그림을 안 보면 원리상 못 맞힌다.

층화 표본 30건 중 19건을 정원석님이 눈으로 판정했다.

판정 건수
맞다 5
애매 4
틀렸다 10

맞다 비율 26% 다. 그런데 틀린 10건 중 8건이 모양으로 정해지는 카테고리였고, 그중 6건은 뿌리를 TextBasedCommunication_LongFormTextLayout 으로 밀어 넣었다.

실제 추출기가 고른 것 검수 메모
Conversion_Funnel ReasoningStructure_CombinedResult "funnel 아닌가?"
ProportionComposition_StackedBarChart ×2 TextBasedCommunication_LongFormTextLayout "이건 차트잖아"
RelationalComparison_BubbleChart TextBasedCommunication_LongFormTextLayout "이건 bubble chart 아닌가"
ProportionComposition_PictogramChart TextBasedCommunication_LongFormTextLayout "전용 category 가 있지 않나"

원인은 프롬프트가 아니라 입력이다. 추출기는 투영된 JSON 만 받는다. type: "SVG" 와 "Image" 는 있지만 그것이 무슨 모양인지는 없다. 깔때기 컴포넌트에서 모델이 본 것은 홍보 모델 도입, 마케팅 전략, 고객의 인식, 구매 라는 글과 상자뿐이었다. 깔때기가 깔때기라는 정보가 어디에도 없다.

49종의 절반쯤이 모양으로 갈린다. 깔때기, 피라미드, 순환, 벤다이어그램, 계단, 사분면, 특성요인도, 각종 차트와 지도. 이것들은 지금 입력으로는 원리상 맞힐 수 없다.

결정 (2026-08-15 정원석): 사진을 안 보면 절대로 올바르게 예측할 수 없다. 추출기가 그림을 봐야 한다. D-10.

두 번째로 나온 것: 이종 병치의 최상위에 붙일 카테고리가 없다. 서로 다른 의미가 나란히 놓인 컴포넌트에서 뿌리 경계는 "그 둘을 담고 있다"는 것 말고 성질이 없는데, 49종에 그런 값이 없다. 그래서 모델이 아무거나 고른다(검수 메모: "이종 병치 컴포넌트일때 최상위를 어떻게 묶어야 하는지 몰라서 그냥 아무거나 한거 같네"). D-11 로 연다.

태그 일치는 쓸 만한 신호로 확인됐다. 사람 판정과 11/15(73%) 에서 같은 방향을 가리킨다. 완전한 대체는 아니지만, 사람 검수 없이 추적하는 용도로는 쓸 수 있다.

T-4. 경계 테이블과 시맨틱 컴포넌트 경계 추출기 (I.I.S. Semantic Component Boundary 대행). 추출기는 structure_json 을 읽어 노드 묶음마다 카테고리를 고른다. LLM 으로만 한다(D-2). 어휘는 49종(D-8), 대상은 색인 전량 2,468건(D-9). → 완료 판정 (기계): 색인 전량에 경계가 붙고, 9.4절의 검사 넷을 전부 통과한다. 특히 넷째("어느 경계에도 안 속한 노드가 0개")가 "빠짐없이"의 정의다. → 완료 판정 (사람): 층화 표본 30건을 눈으로 검수해 일치율을 낸다. 이 수치 없이 다음으로 가지 않는다. 틀린 라벨 위에 세운 4단계판은 4단계가 아니라 경계 추출기의 정확도를 재게 된다.

T-4 두 번째 검수 (2026-08-16). 그림을 넣자 틀렸다가 15 → 4 로 줄었다.

판 무엇이 달랐나 맞다 애매 틀렸다
boundary-v3 글과 배치만 준다 13 2 15
boundary-v5 컴포넌트를 잘라 그림으로 함께 준다 9 10 4

v3 은 30건 전량을 내가 채점했고 v5 는 23건을 정원석님이 판정했다. 채점자가 달라 맞다 비율은 직접 비교가 안 되지만, 틀렸다가 크게 줄고 애매로 옮겨간 것은 방향이 분명하다.

남은 실패에 공통 원인이 있었다. 차트와 도해를 "여러 항목의 나열"로 읽는다. 아이콘 반복을 목록으로, 버블을 목록으로 세고, 제목 문구가 있으면 그쪽으로 끌린다. 그래서 프롬프트에 "자주 틀리는 자리" 넷을 예시로 넣었다(boundary-v6).

  1. 같은 그림이 반복되어 수량과 비율을 나타내면 목록이 아니라 그림 차트다
  2. 위에 제목과 표지 문구가 있어도 본체가 무엇인지로 정한다 (NOTICE + 달력 = 달력)
  3. 차트와 그것을 설명하는 글은 나란히 있어도 한 덩어리다
  4. 일정은 기간 막대면 간트, 시점이 찍히면 타임라인

표본 60건에서 뿌리=태그 가 32/59(54%) 다. v5 는 14/30(47%) 이었다.

계약 검사가 실측으로 넷 늘었다. 프롬프트로 부탁만 해서는 안 지켜지는 것들이고, 넷 다 다른 검사는 전부 통과하는데도 결과가 쓸모없어지는 자리였다.

검사 무엇을 잡았나
relationWithoutSiblings 위상 관계를 "나는 품어져 있다"는 뜻으로 자식 쪽에 달았다
redundantNestedCategory "빠짐없이 덮으라"를 모든 컨테이너에 같은 라벨을 붙여 만족시켰다
unclassifiedWithoutMix Unclassified 가 "모르겠다"의 도피처가 됐다 (28건에 38번, 깔때기까지). 2026-08-29 에 자식 경계가 없는 경우는 허용하도록 완화했다 (D-11a). 자식이 하나뿐인 경우는 계속 거절한다
overlapReported 규약상 나올 수 없는 값이라 나오면 경계 판정이 틀린 것이다

전량 추출은 아직 안 돌렸다. 60건으로 넓혀 팀 검수를 받는 중이고, 그 결과가 나와야 표본을 더 키울지 정한다.

T-5. 노드 태그를 쓰는 검색. 컴포넌트 단위 태그 대신 노드 단위 태그로 후보를 찾고, 매칭된 노드 묶음을 결과에 실어 보낸다. → 완료 판정: 한 컴포넌트 안에 여러 카테고리가 있을 때 요청 카테고리에 해당하는 묶음이 특정된다. README 1절의 design_object_id 4757227 예시가 회귀 케이스가 된다. → 구현 (2026-08-17). src/lib/visual-summary/iis/nodeSearch.ts. 로컬 색인(4757227 은 로컬에 없다)의 다중 카테고리 컴포넌트(예 design_object_id 5696780: ProcessFlow_Steps, Enumeration_List, HighlightedMessage 가 서로 다른 노드에 붙어 있다)로 같은 성격의 회귀를 확인했다. 카테고리별로 다른 노드가 특정된다(nodeSearch.test.ts, scripts/iis-node-search-poc.ts 실 DB 스모크). 완료 판정 자체(4757227 원본 케이스)는 아직 검증 안 됨. 그 컴포넌트가 로컬에 없다.

T-6. 노드 태그를 쓰는 대치. 매칭된 노드 묶음 안에만 채운다. 밖은 원본을 보존한다. → 완료 판정: 평가셋 50건 전량이 끝까지 돈다(실패 0). "태그 밖 노드는 안 건드린다"가 회귀 테스트로 고정된다. → 구현 (2026-08-17). src/lib/visual-summary/iis/nodeBinding.ts. "밖은 원본 보존"은 회귀 테스트로 고정했다(nodeBinding.test.ts, JSON 문자열 동치 비교). 평가셋 50건 전량 실행은 아직 안 했다. 그 실행은 완료 판정의 별도 항목으로 남는다.

3단계: 5단계판#

T-7. 속성 테이블과 시맨틱 컴포넌트 속성 추출기 (I.I.S. Semantic Component Property 대행). 41종 전부. 어휘는 capability-table 2절 선택 속성을 그대로 쓴다. 새로 만들지 않는다.

착수 순서는 A군의 방향과 순서 계열 여섯을 먼저 한다. Steps(sequence, marker), Staircase(direction), Cycle(rotation, center), Pyramid(apex, weight), Timeline(orientation), Quadrant(axisLabels). 틀리면 눈에 바로 보이는 것들이라, 추출기 구조가 맞는지를 가장 싸게 반증할 수 있다. 여기서 구조가 틀리면 나머지 35종에 같은 실수를 복제하지 않는다.

→ 완료 판정 (A군 25종): 해당 컴포넌트 전량에 속성이 붙고, 층화 표본을 렌더 결과로 검수해 정확도를 낸다. → 완료 판정 (B군 16종): 값이 적재되고 적재 정확도만 표본 검수한다. 결과물 확인은 차트 데이터 주입 대치(D-7)가 들어온 뒤로 미룬다. "못 한다"가 아니라 "아직 안 했다"이므로 표에 그렇게 적는다. → 구현 (2026-08-17). src/lib/visual-summary/iis/extractProperty.ts(property-v1). A군 6종을 먼저 착수하는 순서는 못 지켰다. reads.params 가 있는 카테고리 전부에 제네릭하게 돈다. 10건 표본 실행에서 실제로 걸린 것은 TimelineSchedule_GanttChart, Conversion_Funnel, RelationalComparison_Quadrant, ProcessFlow_Steps, ProportionComposition_PictogramChart, TimelineSchedule_Calendar 6종(A군에 속한 것도 있고 아닌 것도 있다). 로컬 DB 로 직접 확인했다. A군의 렌더 결과 검수와 B군의 적재 정확도 표본 검수는 아직 안 했다. 지금은 "값이 적재된다"까지만 확인된 상태다. → 전량 실행과 성능 확인 (2026-08-22). 경계(어느 노드 묶음이 어느 카테고리인가)가 붙은 컴포넌트 137건 전량에 속성 추출기를 두 판 연달아 돌렸다. 여기서 "판" 은 추출기 버전이고, "자격 행" 은 그 카테고리가 표현 선택을 하나라도 읽어서 속성을 뽑을 대상이 되는 경계 행이다. 결과 요약과 그 근거는 아래와 같다.

판 값이 있는 자격 행 빈 행 실패 입력 토큰 출력 토큰 버린 값
property-v2 (뜻풀이만) 109 / 144 (75.7%) 35 0 204,769 13,359 계측 없음
property-v3 (값 모양까지) 123 / 144 (85.4%) 21 0 230,076 12,310 0

판을 한 번 올린 이유는 값 어휘가 지켜지지 않았기 때문이다. property-v2 전량 적재분의 값 분포를 보니 참/거짓 속성 하나에 표기 다섯 가지와 본문 한 줄이 섞여 있었다. title 이 true×27, present×4, false×3, 있음×3, yes×1, 5 out of 5 graduates×1 이었고, SWOT 의 order 는 강점, 약점, 기회, 위협 과 true 가 섞였고, unitPerIcon 은 10% 와 10 이 섞였고, axisLabels 는 {xAxisPlus, yAxisPlus} 객체와 x: ...; y: ... 문자열이 섞였다.

이 상태로는 5단계 검색과 대치(T-8)가 성립하지 않는다. 질의가 title: true 를 말하고 컴포넌트가 present 를 말하면 같은 뜻인데 어긋남으로 세어지므로, 그 수치는 표현 선택이 맞는지가 아니라 표기가 우연히 같은지를 재게 된다.

그래서 값의 모양(참/거짓, 정해진 낱말, 수, 객체)을 어휘 표에 넣고 프롬프트에 함께 실었으며, 모양과 어긋난 값은 기계로 버리고 세게 만들었다(propertyVocabulary.ts 의 checkPropertyValues, 배치 요약의 droppedValues). property-v3 전량 실행에서 버려진 값은 0개다. 모양을 알려주면 지키더라는 뜻이고, 검사는 그 사실을 확인해 주는 자리로 남는다. 같은 실행에서 채움률이 75.7%에서 85.4%로 올랐다.

→ 표본 눈검수 (2026-08-22, 14건, 정원석 확인 전). 카테고리별로 골고루 뽑은 14건을 잘라 낸 그림과 대조했다. 맞다 12, 애매 1, 틀렸다 1.

판정 사례
맞다 12 Cycle_Cycle(center: 4 Stroke cycle, rotation: clockwise), ProcessFlow_Steps(marker: number), ProportionComposition_StackedBarChart(normalize: true, orientation: horizontal), ProportionComposition_PictogramChart(icon: 사람, unitPerIcon: 10 이 아이콘 10개에 70%=7개와 맞는다), Conversion_Funnel(conversionRate: false), 나머지 차트 계열
애매 1 Hierarchy_HierarchicalTree(orientation: vertical). 그 경계 안에 트리가 없다. 순환 도해와 원 셋이 한 경계로 묶여 있어 속성이 아니라 4단계 경계가 틀렸다
틀렸다 1 RelationalComparison_Quadrant(axisLabels). x축과 y축 이름이 서로 바뀌었다

틀린 둘 중 하나는 속성 추출기의 잘못이 아니다. 경계가 어긋난 자리에서는 속성을 옳게 뽑을 방법이 없다. 이것이 "속성은 경계를 포함한다"(1절)의 실측 사례다.

남은 것. A군의 렌더 결과 검수(대치까지 돌려 눈으로 보는 것)와 사람(정원석) 판정은 아직 안 했다. 위 14건은 에이전트 판정이다.

T-7 (이어서). 구조 대응(node ↔ structure field map) 을 같은 추출기에 더한다 (1.0.1절 결정, 2026-08-23). 이때까지 T-7 은 속성(properties)만 냈다. 노드가 entities[].label 인지 entities[].items[0] 인지는 안 뽑았다(1.0.1절 표). 이제 같은 호출이 structure 도 함께 낸다.

대상이 넓어졌다. 이전에는 reads.params 가 빈 카테고리(예: Enumeration_List, Enumeration_ListingTable 의 label/items 만 쓰는 대부분의 카테고리)를 통째로 건너뛰었다. 뽑을 속성이 없었기 때문이다. 이제 entities[] 로 내용을 담는 카테고리(dataset 이 없거나 optional인 카테고리, usesEntityStructure)는 params 가 없어도 구조 대응만이라도 뽑는다. dataset 이 필수(matrix, measure 이고 optional 아님)인 카테고리는 내용이 entities 가 아니라 dataset 행과 열에 있으므로 이번 범위에서 뺐다. 차트 데이터 대치와 같은 이유로 순서상 뒤다(0절, D-7).

어휘. 구조 필드 이름은 capability.ts 의 reads.content(capability-table.md 2절 "선택 내용") 를 그대로 읽고, 모든 카테고리에 label 과 items 를 항상 더한다. 값은 표현 값이 아니라 그 필드를 담은 RLSC 노드의 id(배열 칸은 노드 id 배열)다. LLM 이 지어낸 id 는 그 노드의 부분 트리 존재 여부로 검증해서 버린다(checkPropertyKeys 과 checkPropertyValues 와 같은 계약을 checkStructureFieldKeys 과 validateStructureNodeReferences 로 구조 쪽에도 만들었다).

→ 구현 (2026-08-23, property-v4). src/lib/visual-summary/iis/extractProperty.ts. 저장은 iis_semantic_component.structure(신설 jsonb 칸, properties 와 같은 provenance 칸을 공유) 다. property-v1→v3 가 그랬듯 실측이 나오면 값 어휘가 다시 흔들릴 수 있다. → 표본 실행 (2026-08-23, 로컬). 10건 표본으로 실 DB 배치를 돌렸다. 실패 0, 허용 밖으로 버린 키 0, 모양이 어긋나 버린 값 0, 입력 토큰 34,833 출력 토큰 3,566 이다. 적재는 경계 판 boundary-v8 위에 속성 행 30개(컴포넌트 28건)이고 그중 구조 대응이 실제로 찬 행이 15개다. 자격 행이 넓어진 것도 실측했다. 같은 경계 판에서 144 → 246 행(카테고리 23종 → 33종)이다. reads.params 가 비어도 entities[] 로 내용을 담는 카테고리가 들어온 몫이다. → merge (2026-08-23). PR #2231 (c6594866a). 코드 리뷰 6 라운드를 돌려 Critical 1건(경계 --force 재추출이 제약 위반으로 통째로 죽던 것)과 High 2건을 고쳤고, 마지막 라운드는 Critical 0 / High 0 / Low 0 이다. 문서는 mordor-wiki #131 로 먼저 merge 했다. → 남은 것. 전량 실행과 표본 눈검수(T-7 속성 쪽과 같은 절차. 지금은 표본 10건까지만 돌렸다), binding.ts 가 이 대응을 실제로 소비하도록 바꾸는 것(지금은 여전히 RLSC 역할로 어림한다), dataset 계열 카테고리로 범위를 넓힐지 판단 → 닫힘 (2026-09-17). 넓힌다 — 아래 항목, MOR-2199.

→ 2026-09-17 결정: dataset 필수 카테고리로 범위를 넓힌다 (MOR-2199, 허재영). 위 "이번 범위에서 뺐다"는 이 날짜로 거둔다 — 아래가 현행 계약이다. 추출 산출물이 규약의 structure 모양을 거울처럼 따르게 하는 것이 목적이다 — 규약(../spec/capability-table.md 2절, ../spec/schema-v0.5.md 2.1절)은 표·차트의 내용을 entities 가 아니라 dataset 행·열에 담으라고 이미 정해 두었는데, 템플릿 쪽 지도에는 그 자리를 담을 칸이 없었다. 규약은 바꾸지 않는다.

넓히는 근거 셋.

  1. 보류 근거가 소멸했다. "차트 데이터 대치와 같은 이유로 순서상 뒤"(D-7)는 차트를 element 에 데이터 주입으로 그릴 수 있다는 전제였다. 2026-09-04 B-13 결정(../TODO.md B-13 근거)으로 그 어댑터는 검색 실패 시 처음부터 합성하는 미착수 경로에만 필요하고, 주력 대치 경로 셋은 이미 있는 실제 컴포넌트에 내용을 채우는 방식이라 I.I.S. 구조에 의존한다. "I.I.S. 가 구조만 제대로 뽑으면 native 요소가 아닌 가짜 컴포넌트도 대치 가능하다." 글상자·도형으로 그린 표·차트를 살리는 길은 이 지도뿐이다.
  2. 첫 출시 범위 안이다. Visual Summary 첫 출시 생성 범위 P0 18종(MOR-2460, 2026-09-16 확정) 중 8종이 dataset 필수다 — Enumeration_ListingTable, AttributeComparison_ComparisonTable, ProportionComposition_PieChart, ProportionComposition_DonutChart, Trend_LineAreaChart, MagnitudeRanking_BarChart, Trend_ComboChart, ProportionComposition_StackedBarChart.
  3. 실측이 공급 문제를 보인다. iui 색인(iui.comp_frames_v2, 2026-09-13 빌드)은 시트 문서에 진짜 Table·Chart 요소가 있을 때만 dataset.kind = matrix 를 달고, 검색은 이 종류를 하드 필터로 맞춘다. 글상자 표는 none 이라 matrix 질의의 후보에서 애초에 빠진다. ListingTable 485행 중 428행(88%)·ComparisonTable 161행 중 143행(89%)이 그렇고, 실제 검색 가능한 진짜 표는 GC 통과 기준 10건·4건이다. 차트 6종은 대부분 진짜 Chart 요소라 4~37% 다. ListingTable 485행 전부(진짜 격자 57행 포함) structure.entities 가 비어 있다 — 템플릿 모양이 아니라 어휘 규칙 때문이다.

산출 모양 — 규약 Dataset 의 거울. 값 자리에 노드 id. structure[category] 칸에 entities·relations 옆으로 dataset 키를 더한다. 새 국면·새 표·새 칸을 만들지 않는다(1.0.1절 "국면은 유지하고 표의 칸을 넓힌다").

ts
// 규약 schema-v0.5 2.1절 Matrix / Measure 와 같은 모양. 값 대신 그 값을 담은 RLSC Text 노드 id.
type DatasetMap =
  | { shape: 'matrix'; columns: (NodeId | null)[]; rows: (NodeId | null)[][] }
  | { shape: 'matrix'; grid: NodeId }   // 경계 안에 RLSC `Table`·`Chart` 노드가 있을 때 — 셀은 시트 요소 내부라 RLSC 노드가 아니다
  | { shape: 'measure'; value: NodeId; unit?: NodeId; min?: NodeId; max?: NodeId; target?: NodeId; delta?: NodeId };
  • columns 는 헤더 행의 노드다. 헤더가 없으면(params.header = false) 전부 null 로 두되 칸 수는 유지한다. 모든 rows[i] 의 길이는 columns 와 같고, 빈 셀은 null 이다. 첫 열이 범주 축인 것은 규약 2.5절 그대로다.
  • 셀 하나는 Text 노드 id 하나다. 컨테이너·이미지·SVG 는 셀이 될 수 없다. 같은 노드가 두 셀, 또는 셀과 entities[].fields 에 동시에 들어가지 않는다.
  • dataset 필수 카테고리는 지금처럼 entities 를 내지 않는다(entities: [] 유지). 규약의 entities+columns 대안 경로(2.5.1절, 병합 표)는 질의 쪽 표기이고 템플릿 지도는 matrix 하나로 받는다 — 대치가 병합 표 질의를 matrix 로 접는 것은 P1/P2 의 일이다.
  • 검사와 프롬프트가 같은 판정을 본다(2.-1절). usesEntityStructure 와 나란히 usesDatasetStructure(= dataset 이 필수) 하나를 두고, 프롬프트의 "허용 구조 필드" 와 적재 직전 검사(checkStructureFieldKeys)가 둘 다 그 함수를 부른다. 2026-08-23 리뷰가 잡은 "프롬프트는 없음이라 하고 검사는 통과시키던" 갈림을 반대 방향으로 반복하지 않기 위해서다. 노드 id 검증은 validateStructureNodeReferences 와 같은 계약(없는 노드·Text 아닌 노드·중복은 버리고 센다)이다.

범위와 순서. 판은 property-v8. 대상은 dataset 필수 16종 전부다 — 색인은 P0 18종만 도는 것이 아니라 전량을 돌리므로, 하는 김에 같이 간다(2026-09-17 허재영). 그중 P0 8종은 모두 reads.params 가 있어 지금도 LLM 호출 대상이라 출력(지도)만 늘고, params 도 없던 KPIKeyMetric_Gauge·ProportionComposition_TreeMap 은 이번에 처음 호출 대상이 된다(hasExtractableContent 가 usesDatasetStructure 를 함께 본다 — 규약 45종 전부가 뽑을 것이 있게 됐고, 커버리지 분모와 배치 러너의 already 판정도 같이 움직인다). 표본 실행·눈검수는 공급 문제가 실측된 표 2종부터 하고, 파일럿은 운영이 쓸 판으로 돌린다.

이 문서 밖에 있는 전제 하나 — 소비. iui 색인 빌더(packages/comp-frame-index/src/frame/component-frame.ts)는 지금 진짜 격자에서만 dataset: matrix Frame 을 만들고 글상자 셀을 matrix 슬롯으로 잇는 경로가 없다. 추출이 지도를 내도 그쪽이 읽지 않으면 428행은 그대로 검색에서 빠진다. "dataset 지도 → matrix Frame(셀 슬롯·cols·rows)" 빌더와 셀 단위 대치 경로를 iui 쪽 티켓으로 따로 낸다. 위 binding.ts 소비 항목과 같은 성격의 남은 일이다.

구현. ① Eregion 추출 계약·property-v8 프롬프트(usesDatasetStructure, 어휘 뜻풀이, 검사, 저장 키) — aippt-prisonbreak PR(같은 날) ② iui 색인·대치 소비 — iui 쪽 티켓 ③ dataset 필수 16종 재추출 실행(MOR-2479 에 얹거나 별도).

T-8. 속성을 쓰는 검색과 대치. 질의의 속성과 컴포넌트의 속성을 맞춘다. 이종 type 사이에는 property projection 규칙이 필요하다. structure projection(P1~P8)과 혼동하지 않는다 (../design/content-binding-contract.md 3.5절). 안 맞을 때 어떻게 할지가 설계 지점이다. 후보에서 빼거나, 순서를 뒤집어 채우거나, 그냥 채우고 결함으로 센다. → 완료 판정: 세 정책을 다 돌려 결과를 비교한 표. 어느 하나를 미리 고르지 않는다. → 구현 (2026-08-22). 정책 셋을 다 만들었고 미리 고르지 않았다. 판정부는 src/lib/visual-summary/iis/propertyMatch.ts 하나이고, 검색은 nodeSearch.ts 의 rankNodeCandidatesByProperty, 대치는 nodeBinding.ts 의 bindNodeWithPropertyPlan 이다.

정책 무엇을 하나 무엇을 잃나
exclude 표현 선택이 어긋난 후보를 검색에서 뺀다 후보가 마른다. 남은 후보의 구조 적합이 더 나쁠 수 있다
reorder 순서를 뜻하는 어긋남은 항목을 거꾸로 채워 해소하고, 나머지는 결함으로 센다 순서로 해소되지 않는 어긋남은 그대로 남는다
ignore 그냥 채우고 전부 결함으로 센다 어긋난 채로 사용자에게 간다. 지금(4단계) 동작이 이것이다

질의 쪽 표현 선택이 검색까지 흐르게 만든 것이 선행 작업이었다. 규약 2.4절의 params(사용자가 바라는 표현 선택. 방향, 회전 같은 것)는 Component 에 있었지만 검색 질의(ComponentSearchQuery)에는 실리지 않았다. 지금은 걸러지지 않은 채로 실려 가고, 어느 키가 그 후보 카테고리의 표현 선택인가는 후보마다 pruneParamsForCategory 가 판정한다. 이 함수는 원래 투영 규칙 P2(도착 카테고리가 선언하지 않은 표현 선택을 버리는 규칙, projection.ts)가 쓰던 것이고, 그 자리를 그대로 함께 쓴 것이 요점이다. 허용 키 판정을 속성 쪽에 새로 만들면 같은 규칙이 두 벌이 된다.

순서로 해소되는 어긋남은 다섯이다. Progression_Staircase.direction, Transformation_Transformation.direction, Hierarchy_Pyramid.apex, Cycle_Cycle.rotation, MagnitudeRanking_BarChart.sort. 이 표가 이 모듈이 더하는 유일한 지식이라 자리를 하나만 두고, 카테고리와 키가 실제 어휘 안에 있는지는 테스트가 전수로 잠근다. orientation(가로, 세로)이나 weekStart 처럼 배치와 표기를 뜻하는 키는 순서를 뒤집어도 해소되지 않으므로 넣지 않았다.

→ 완료 판정 충족 (2026-08-22): 비교표가 나왔다. scripts/iis-property-binding-poc.ts 가 실 DB 로 만든다. 경계 판 boundary-v7(경계를 뽑은 추출기 버전. 컴포넌트 137건), 속성 판 property-v3(속성을 뽑은 추출기 버전. 속성 행 144개), 질의 카테고리 8종, 질의 19개 기준이다. 표의 "합의 키" 는 질의와 컴포넌트가 같은 값을 말한 표현 선택의 수이고, "모른다 키" 는 컴포넌트가 그 속성을 아직 안 뽑아 비교할 수 없는 수이며(어긋남이 아니다), "채운 자리" 는 대치가 글을 넣은 텍스트 자리 수 대 전체 자리 수다.

정책 후보 있음 후보 없음 후보 합 합의 키 어긋난 키 모른다 키 거꾸로 채움 남은 결함 채운 자리
exclude 19 0 95 11 0 5 0 0 71 / 228
reorder 19 0 95 8 8 3 4 4 76 / 232
ignore 19 0 95 8 8 3 0 8 76 / 232

읽는 법 셋.

  1. exclude 가 후보를 말리지 않았다. 질의 19개 전부에 후보가 남았다. 어긋난 노드를 빼도 풀 안에 속성이 맞거나 아직 안 뽑힌 컴포넌트가 있어서 그리로 옮겨 간다. 대신 1위 후보의 구조 적합이 나빠졌다. 채운 자리가 71/228 로 나머지 둘(76/232)보다 적다. 표현 선택을 지키는 값을 구조 적합으로 낸 셈이다.
  2. reorder 가 결함 8을 4로 줄였다. 줄어든 넷이 순서로 해소되는 어긋남(Cycle_Cycle.rotation, Hierarchy_Pyramid.apex, Progression_Staircase.direction, Transformation_Transformation.direction)이고, 남은 넷은 순서와 무관한 것들(Conversion_Funnel.conversionRate, ProcessFlow_Steps.sequence, RelationalComparison_SWOT.order, TextBasedCommunication_LongFormTextLayout.title)이다.
  3. ignore 는 아무것도 해소하지 않는다. 결함 8이 그대로 남는다. 이것이 지금 동작이므로, 이 표는 "5단계가 무엇을 더하는가" 를 결함 8 대 4 대 0 으로 읽게 한다.

아직 정책을 고르지 않는다. 표본이 19개 질의뿐이고, exclude 의 구조 적합 손실이 이 검색 풀 크기(컴포넌트 137건)에서만 나타나는 성질일 수 있다. 색인을 키운 뒤(6절 T-2 시그니처 테이블, T-3 색인 분포 교정) 다시 재야 한다.

→ 여기서 열린 것 하나. direction 같은 속성이 화면의 모양을 말하는지 채우는 순서를 말하는지가 규약에 못 박혀 있지 않다. 뜻풀이는 "계단이 오르는가" 라 화면 모양이지만, 대치는 그것을 채우는 순서로 옮겨 읽는다. 두 컨벤션이 어긋나면 뒤집기가 오히려 틀린다. 실측 사례가 있다. design_object_id 6380453 은 화면상 왼쪽 아래에서 오른쪽 위로 오르는데 노드 순서는 위에서 아래다. 규약에 "이 값은 배열 순서 기준으로 적는다" 를 명시할지가 후속 결정이다.

4단계: 바닥 공사와 나란히 놓기#

여기서부터는 색인의 규모와 분포가 결과를 좌우하므로 먼저 고른다.

T-2. 시그니처 테이블을 만들고 적재한다. 마이그레이션 + 적재 스크립트 + 조회 경로 교체. 메모리 빌드는 탈출구로 남긴다. 실행 시 로컬 색인 규모의 상한을 확인한다. 실측으로 2만 건에 약 4.4분, 5만 건에 약 9.9분, 전량 173,357건에 38.6분이고 메모리 사용량은 아직 안 재 봤다.

이 테이블은 comp-sign 을 나중에 대체하더라도 버려지지 않는다. 채점기가 두 경로 모두의 용량 근거로 comp-sign 시그니처를 쓰기 때문이다(D-1 이 어느 쪽으로 결론 나든 같다). → 완료 판정: 서버 기동에서 33초 빌드가 사라진다. 같은 평가셋을 돌렸을 때 --diff 가 변화 없음을 보고한다(적재 경로가 메모리 경로와 같은 결과를 낸다는 증명).

T-3. 색인을 실서비스 분포에 맞춘다. 적재 컬럼에 valid_component_meta 를 더해 유효 컴포넌트 여부를 로컬에서도 검사할 수 있게 한다 (9.1.1절). 지금 균형 샘플링이 목록형을 운영 75.8% 에서 로컬 2.4% 로 1/31 축소해 놓았다. 크기가 아니라 비율의 문제다. → 완료 판정: 카테고리별 로컬 비율과 운영 비율의 차이를 표로 남긴다. 색인 규모별 태그 일치율 곡선을 함께 낸다. Cycle_Cycle 처럼 운영 전량을 갖고도 안 맞는 반례가 "구조 해시가 안 맞아서"임이 드러나야 한다.

T-9. 세 판 비교표. 현행판, 4단계판, 5단계판을 같은 평가셋과 같은 채점기로 한 표에. → 완료 판정: 표가 나오고, 각 판이 무엇을 고쳤고 무엇을 못 고쳤는지가 결함 종류별로 읽힌다.


7. 기각한 안#

교집합 상위 10건을 그대로 판정 지표로 쓰는 것. 두 검색이 맨 위에서 거의 합의하지 않는다. 1위가 겹치는 컴포넌트가 65개 중 2개(3%)뿐이다. 표본이 거의 안 남는다.

"다리(markdown 변환)가 markdown 표기 노출의 원인"이라는 가설. 반증됐다. 노출 137건을 전수 대조했더니 137/137 이 * 불릿인데 우리 다리는 - 만 쓰고 * 를 한 글자도 안 내보낸다. 원인은 comp-sign 의 자식 병합이다.

색인을 키우면 태그 불일치가 풀린다는 가정. 커버율과 태그 일치율의 상관이 −0.315(무상관) 다. Cycle_Cycle 은 운영 전량 52건을 이미 갖고 있는데 태그 맞는 후보가 10개 중 1개다.

4단계와 5단계를 comp-sign 위에 지금 얹는 것. 2.2절이다. 받을 자리가 없고, 그걸 여는 것은 comp-sign 의 API 변경이다. 다만 이건 영구 기각이 아니다. D-1 대로, 결과가 좋으면 그때 comp-sign 을 바꾸거나 대체한다.

평가 결과를 테이블에 적재하는 것. 4절이다. 재현, 이력, 리뷰 가시성을 다 잃는다.

채점기가 "어떤 카테고리가 허용되는가"를 스스로 계산하는 것. 2.-1절이다. 검색과 규칙이 두 벌이 되어 PR 리뷰가 같은 종류의 Critical 을 넷 냈다. 검색이 만든 집합을 받는다.

의미 적합성을 거리(0/1/2/3)로 재는 것. 결함 판정은 이분법만 쓰고 그 거리는 어느 보고 수치에도 안 들어갔다. 장식이었다. categoryAccepted 하나로 줄였다.

경계와 속성을 서로 다른 계열의 모듈로 쪼개는 것. 속성은 경계를 포함하므로(1절) 두 이름 다 I.I.S. 계열 안에 둔다. 쪼개면 나중에 RAT pod 의 실물과 대응이 안 맞는다.


8. 다른 사람의 답을 기다리는 것#

아래는 우리가 혼자 못 정하는 것들이다. I.I.S. 본편 재개(D-5)는 2026-08-22 에 닫혀 여기 목록에서 빠졌다. PoC 가 대행 생산한다 (poc-module-map.md 4절). 나머지는 어느 것도 지금 작업을 막지 않는다.

무엇 누구 막히면 어떻게 하나
I.I.S. (Semantic Component Boundary) 의 출력 모양 RAT pod (류민님) 답을 기다리지 않는다. 우리가 먼저 정해 제안하고 진행한다 (D-2 확정, T-4a)
I.I.S. (Semantic Component Property) 를 RAT pod 가 맡나 RAT pod (류민님) / 스쿼드 이 PoC 가 대행판을 만들어 가치를 먼저 판정한다
comp-sign 4.3.8 사내 저장소 게시 이석님 4.3.5 로 간다. 파이프 표는 중첩 목록으로 우회 중
comp-sign 임시 문구에 큰따옴표가 섞이는 것 이석님 채점기가 그 문구를 알고 있어 진행에는 안 막힌다
평가셋 38 중분류 확장 (MOR-1928) 허재영님 자체 50건 셋으로 간다. 나오면 교체
노드 속성을 실서비스에 실어 보낼 경로 이석님 / 스쿼드 D-1 — 자체 경로로 먼저 가치를 판정한다


9. 경계 지도와 속성 지도 계약 (T-4a)#

RLSC 스키마는 손대지 않는다. RLSC 는 다른 소비자가 함께 쓰므로, 여기서 정의하는 것은 RLSC 노드를 가리키는 별도의 지도 둘이다. I.I.S. 가 실물을 내놓으면 지도만 갈아 끼운다.

9.1 실물에서 확인한 것 (2026-08-15)#

무엇 값
색인 컴포넌트 2,468건 · 노드 51,070개
노드 식별자가 컴포넌트 안에서 유일한가 유일하다. 중복 0건
UUID 식별자 (= sheet 요소) 33,543개 (66%)
합성 식별자 (VStack_0·Group_1 등, 추출 시 생성) 17,527개 (34%)
자식을 가진 노드 18,620개

경계는 대부분 컨테이너에 붙으므로 합성 식별자 쪽이다. 재추출로 이 식별자가 바뀔 수 있는데, 재추출을 하면 I.I.S. 도 다시 돌리므로 문제가 되지 않는다(2026-08-15 정원석 확정). 그래서 리프 해시 같은 무결성 장치를 두지 않는다.

9.1.1 우리가 다루는 것은 유효 컴포넌트다 (2026-08-15 실측)#

조건으로 걸어서가 아니라, 태깅 모집단이 그렇기 때문이다.

무엇 값
실서비스에서 design_object_semantic_tags 가 붙은 컴포넌트 178,095
그중 valid_component_meta 에 v3.5 판정이 있는 것 178,095 (전량)
그중 v3.5 유효 178,093 (99.999%)

이 수는 MOR-1871이 확정한 dv2.2 × v3.5 = 178,095 VC 와 같다. 유효 컴포넌트 정의의 현행판은 v3.5 이고 MOR-1820에서 2026-08-02 에 확정됐다.

우리 풀(indexPool.ts)은 design_object_semantic_tags 에 조인해 만들므로 유효 컴포넌트 밖으로 나갈 수 없다. 어긋난 2건은 태깅 이후 재판정에서 떨어진 것으로 보인다. 무시할 만하지만 0 은 아니다.

design_objects.validity(운영 제외 게이트)는 로컬 풀 2,468건 전부 비어 있다.

다만 로컬에서는 이것을 검증할 수 없다. 로컬 색인 2,468건 중 v3.5 판정을 가진 행이 50건뿐인데, 원인은 데이터가 아니라 적재 스크립트(scripts/visual-summary-expand-index.ts)가 valid_component_meta 칸을 복사하지 않기 때문이다. 지금 근거는 "실서비스가 그렇다"는 간접 근거뿐이라, 색인을 다시 뜰 때 무효가 섞여도 알 방법이 없다. T-3 에서 그 칸을 적재 대상에 더해 로컬에서도 검사되게 만든다.

9.1.2 컴포넌트가 페이지 어디에 있나 (2026-08-15 실측)#

design_object_sheet_json.nodes[0].data.props.boundingBox 다. RLSC 뿌리의 position 이 아니다. 그쪽은 컴포넌트 좌표계의 값이라 (0, 0) 으로 적혀 있기도 하다.

design_object RLSC 뿌리 sheet 뿌리 눈으로 잰 실제
5681639 (0%, 0%) (29.5%, 24.8%) (30%, 25%)
5657998 (22.7%, 15.2%) (23.8%, 37.6%) (23.8%, 약 35%)

RLSC 안쪽 좌표는 부모 기준 상대값이므로 누적해야 한다(sheet 요소와 대조해 확인: 누적값이 sheet 보다 정확히 뿌리 위치만큼 크다, 17/17). 누적한 뒤 RLSC 뿌리를 빼고 sheet 뿌리를 더하면 페이지 좌표가 된다.

이 값이 두 가지를 연다. 검수표에서 실물 위에 경계를 정확히 얹는 것, 그리고 D-10 에서 컴포넌트만 잘라 그림을 주는 것이다.

9.2 결정된 것 (2026-08-15 정원석 확정)#

식별자는 합성 식별자를 그대로 쓴다. 위 근거대로다.

확신도는 담지 않는다. 담을 만한 쓸모가 없다고 정했다.

내부까지 빠짐없이 경계가 특정돼야 한다. 이것이 이 계약의 핵심 요구다. 경계는 성기게 다는 주석이 아니라 컴포넌트 전체를 덮는다. 아래 9.4 가 이것을 기계로 검사한다.

추출 여부는 행의 존재로 판정한다. 해당 design_object_id 의 행이 있으면 추출된 것이고, 없으면 아직 안 한 것이다. 별도의 처리 이력 표를 두지 않는다. 전수 커버리지가 요구되므로 추출된 컴포넌트는 최소 한 행(뿌리 경계)을 갖는다. "돌렸는데 경계가 없다"는 상태가 성립하지 않는다.

9.3 이종상태는 규약의 Topological Relation 을 그대로 쓴다#

규약이 이미 정의해 두었다. ../spec/schema-v0.5.md 8.7절에 Topological Relation (위상 관계) 이 있고(2026-08-13, mordor-wiki PR #75), 값이 셋이다. 이 지도는 새 어휘를 만들지 않고 그 셋을 그대로 담는다.

값 무엇인가 실물에서
Containment 한 카테고리의 시각 영역이 다른 것 안에 완전히 들어간다 규약의 1분기/2분기 예시 (List 안에 분기마다 BigNumber)
Juxtaposition 시각 영역이 겹치지 않고 나란히 놓인다 관측된 11건이 전부 이 경우다 (좌 BigNumber + 우 List, 상 List + 하 VennDiagram)
Overlap 시각 영역이 부분적으로 겹친다 현재 RLSC 구조에서는 발생하지 않는다

Overlap 이 안 나오는 이유가 구조적이다. RLSC 트리에서 한 노드는 부모 하나 아래에만 들어가고 여러 부모에 동시에 속하는 구조가 없으므로, 영역이 겹치려면 필요한 다중 소속 자체가 성립하지 않는다. 규약은 분류의 완결성을 위해 남겨 두었을 뿐이다.

그래서 추출기는 Overlap 을 내지 않는다. 칸은 셋 다 받되, 추출기가 Overlap 을 냈다면 그것은 위상 관계가 아니라 경계 판정이 틀렸다는 신호다. 9.4 의 검사에 넣는다.

수량은 작다. 전체 183,052건에서 1,000건을 뽑아 잰 결과 이종중첩이 11건, 관측 비율 1.10%(95% 신뢰구간 0.62~1.96%), 전체 환산 약 2,014건이다(2026-08-13 수량 산출, MOR-2048, 황현진). 전수 커버리지를 요구해도 이종 처리 비용이 크지 않다는 뜻이다.

D-7 과의 관계. 이종중첩은 4단계 검색 풀에서 제외하기로 닫혀 있고(../TODO.md D-7, 2026-08-11 체크인), 규약은 그 제외가 위상 관계와 무관하게 이종중첩 전체에 적용된다고 명시한다.

그래서 추출과 검색 풀 포함을 갈라 둔다. 경계 추출은 규약이 요구하는 대로 내부까지 빠짐없이 하고, 검색 풀에 넣을지는 D-7 결정을 그대로 따른다. 이렇게 두면 D-7 을 나중에 다시 볼 때 근거 데이터가 이미 쌓여 있다. 지금 그 결정을 뒤집자는 뜻이 아니다.

9.4 경계 지도#

iis_semantic_component_boundary 다. 이름은 1절의 규칙(iis + 대상 + 국면)을 따른다.

칸 무엇을 담나
design_object_id 컴포넌트
node_id 이 경계의 뿌리 노드. RLSC 노드 식별자 (9.1 대로 컴포넌트 안에서 유일하다)
category 색인 태그 어휘 49종 저장 키 (ProcessFlow_Steps 형식). 규약 41종 + Geo_* 4 + PageNavigation_* 4 (D-8)
parent_node_id 이 경계를 품는 상위 경계의 노드. 뿌리 경계는 비어 있다
topological_relation Containment · Juxtaposition · Overlap · 비어 있음(이종이 아니다). 규약 8.7절 어휘 그대로 (9.3)
extractor_version 어느 판이 뽑았나
source llm · human · iis

키는 (design_object_id, node_id) 다. parent_node_id 가 경계들을 하나의 트리로 묶는다.

부모 외래키는 on delete cascade 를 쓰지 않는다. 경계와 속성이 한 행에 살기 때문에, 부모 경계를 지울 때 자식 행이 통째로 지워지면 비싸게 뽑은 속성이 함께 사라진다. 실제로 겪었다. 2행을 넣고 부모를 지우니 0행이 됐다. on delete set null (parent_node_id) 로 부모 참조만 비운다(복합 키라 컬럼을 지정해야 한다. 그냥 set null 이면 design_object_id 까지 비워 삭제가 실패한다). 그렇게 생긴 고아는 9.4 검사의 rootNotExactlyOne 와 parentMissing 이 잡는다. 조용히 사라지는 것보다 소리 나게 걸리는 편이 낫다.

전수 커버리지를 기계로 검사한다. 계약이 성립하려면 아래 넷이 모두 참이어야 한다. 이것이 T-4 의 완료 판정에 그대로 들어간다.

  1. 컴포넌트마다 parent_node_id 가 빈 행이 정확히 하나 있다 (뿌리 경계)
  2. parent_node_id 가 가리키는 노드가 같은 컴포넌트의 경계로 실재한다
  3. 경계 트리에 순환이 없다
  4. structure_json 의 모든 노드가 어느 경계의 뿌리이거나 어느 경계 뿌리의 자손이다. 어디에도 안 속한 노드가 0개
  5. topological_relation 이 Overlap 인 행이 0개. 나오면 위상 관계가 아니라 경계 판정이 틀린 것이다 (9.3)

넷째가 "빠짐없이"의 기계 정의다.

9.5 속성 지도#

이 절은 실물과 다르다 (2026-08-23 확인). 아래는 설계 당시의 정규화 스키마 (iis_semantic_component_property 별도 표, 행마다 prop_key/prop_value 한 쌍)이고, 구현은 iis_semantic_component 한 표의 properties jsonb 칸 하나다(경계와 같은 행에 산다). 2026-08-23 부터 그 옆에 structure jsonb 칸이 하나 더 있다(1.0.1절). 어휘 규칙 (prop_key 는 capability-table 2절 이름 그대로, 새로 만들지 않는다)은 그대로 유효하고, 표 모양만 다르다. 읽을 때는 아래 표가 아니라 마이그레이션 (supabase/migrations/20260815113725_iis_semantic_component.sql, 20260823065613_iis_semantic_component_structure_map.sql)을 정본으로 본다. 이 절을 실물에 맞춰 다시 쓰는 것은 별건으로 남긴다 — 이번 작업분이 만든 drift 가 아니다.

iis_semantic_component_property.

칸 무엇을 담나
design_object_id 컴포넌트
node_id 이 속성을 지닌 노드. layout container 일 수도 element 일 수도 있다
boundary_node_id 이 노드가 속한 경계 (9.4 의 node_id)
prop_key ../spec/capability-table.md 2절 선택 속성의 이름 그대로 (direction·rotation·apex 등). 새로 만들지 않는다
prop_value 값
extractor_version 어느 판이 뽑았나
source llm · human · iis

키는 (design_object_id, node_id, prop_key) 다.

prop_key 는 그 경계의 카테고리가 허용하는 것이어야 한다. 규약이 카테고리마다 선택 속성을 정해 뒀으므로(2.1절), 그 밖의 키가 들어오면 검사에서 걸러 낸다. 어휘가 양쪽에서 갈리는 것을 막는 장치다.


10. Changelog#

  • 2026-09-17 T-7 구조 대응을 dataset 필수 카테고리로 넓힌다 (MOR-2199, 결정 — 6절 T-7 "남은 것"의 열린 항목을 닫는다). 산출물 structure[category] 에 dataset 키(규약 Matrix/Measure 의 거울, 값 자리에 Text 노드 id)를 더한다. 판 property-v8, 대상은 dataset 필수 16종 전부(색인은 전량을 돌린다), 표본은 표 2종부터. 규약은 바꾸지 않는다 — 규약은 이미 표·차트 내용을 dataset 에 담으라 하고, 빈 곳은 추출 계약과 iui 소비였다. 근거는 B-13 결정(2026-09-04)으로 어댑터 우회가 소멸한 것과 iui 색인 실측(ListingTable 428/485·ComparisonTable 143/161 이 격자 없음이라 matrix 질의 후보에서 제외). D-7 에 후기, 1.0.1절 표에 한 줄.

  • 2026-08-29 Unclassified 를 "이름 없음" 에도 쓴다 (D-11a). 경계 프롬프트 boundary-v11. 어휘 49종 어느 이름으로도 내용 대치를 할 수 없는 컴포넌트에 그 이름을 붙이지 않는다. 카테고리는 설명 라벨이 아니라 검색과 대치의 계약이라, 못 꽂을 이름이 붙으면 그 거짓 약속이 그대로 검색에 흘러든다. 계약 unclassifiedWithoutMix 에서 자식 경계가 없는 경우를 허용했고(자식이 하나뿐인 경우는 계속 거절한다), 프롬프트에 자리를 마련하는 그림(순환·단계·깔때기·피라미드 등 17종)에는 그 자리에 글이 얹혀 있기를 요구하는 규칙과 자리 채우기 더미 글의 뜻을 근거로 쓰지 않는 규칙을 넣었다. 차트 계열은 건드리지 않았다. 실측은 D-11a 의 표에 있다. PR #2280, MOR-2104.

    • 2026-08-15 에 막았던 도피와 방향이 반대로 보이지만 목적은 같다. 둘 다 틀린 이름이 검색에 흘러드는 것을 막는다. 그때는 대치가 되는데도 Unclassified 로 간 것이 오답이었고, 지금은 대치가 안 되는데 Conversion_Funnel 이 붙은 것이 오답이다. 두 상황을 가르는 기준(모양이 나눠 준 칸에 글이 얹혀 있는가)이 이번에 생겼다. 그전에는 자식 경계 수라는 구조적 대리 지표로 막았다.
    • 프롬프트 출구를 넓게 열면 도피가 되살아난다. 규칙 6 을 "붙일 이름이 없을 때" 로 열었더니 6103052 가 5회 중 3회 Unclassified 로 갔다. 출구를 자리 그림이 장식으로 판명된 자리에만 두어 되돌렸다.
    • 판별 분포를 재는 도구를 만들었다. scripts/probe-boundary-prompt-ab.ts 가 두 판을 같은 컴포넌트에 나란히 재실행하고 --repeat 으로 분포를 낸다. 한 번씩만 재고 비교하면 모델의 흔들림을 프롬프트 효과로 오독한다.
  • 2026-08-23 (merge) T-7 구조 대응이 main 에 들어갔다. PR #2231 (c6594866a), 문서는 mordor-wiki #131. 코드 리뷰 6 라운드를 돌렸고 마지막 라운드가 Critical 0 / High 0 / Low 0 이다. 라운드마다 무엇이 나왔는지는 아래 항목들에 있다.

    • 표본 10건 실측. 실패 0, 버린 키 0, 버린 값 0, 토큰 34,833 / 3,566. 적재는 boundary-v8 위 속성 행 30개(컴포넌트 28건), 그중 구조 대응이 찬 행 15개. 자격 행은 같은 경계 판에서 144 → 246(카테고리 23종 → 33종)으로 넓어졌다. 전량 실행과 표본 눈검수는 아직이다 (6절 T-7 "남은 것").
    • 검수 화면을 300줄 규칙에 맞게 쪼갰다. IisBoundaryReviewPage/index.tsx 1,364줄을 17개 파일(최대 253줄)로 나눴다. 순수 이동 리팩터이고 로직은 안 바꿨다. 배치 표 둘을 따로 둔 이유는 분모가 다르기 때문이다. 경계는 "뽑은 컴포넌트 대비 사람 판정" 이고 속성은 "속성을 가질 수 있는 컴포넌트 대비 덮인 수" 다.
    • 미리보기 화면이 낡은 응답을 버린다. 입력을 바꾸면 결과를 지우면서도 날고 있던 요청은 그대로 둬서, 옛 응답이 도착하면 새 입력과 무관한 T-8 표가 다시 떴다. 요청마다 번호를 붙여 도착 시점에 최신일 때만 받는다. 응답을 5초 늦추고 그 사이 카테고리를 바꿔 실증했다.
    • 계측이 성공을 부풀리던 자리 둘을 막았다. 모델이 structure 를 통째로 생략해도 배치가 조용히 성공했고(structureOmittedComponents 로 센다. 단 구조를 실을 수 있는 대상이 있을 때만), 허용 밖 필드를 걷어내 껍질만 남은 항목이 "구조 있음" 으로 세어졌다(항목째 버린다). 기존 적재분에 껍질은 0건이었다.
  • 2026-08-23 (리뷰 반영) 코드 리뷰 3건을 돌려 Critical 1건과 설계 원칙 위반 2건을 고쳤다. 아래 셋은 전부 "규칙을 두 벌 만들면 갈라진다" 의 사례다.

    • Critical: 경계 재추출이 통째로 실패하는 상태였다. replaceBoundaries 는 카테고리가 바뀐 노드의 properties 계열 다섯 칸을 지우는데 새 structure 칸을 안 지웠다. 그러면 properties=null, structure='[…]', provenance=null 이 되어 이번에 새로 건 iis_semantic_component_property_provenance 의 두 갈래를 모두 어긴다. 로컬 DB 에서 23514 로 재현했다(design_object 5665439 / Group_7). structure 도 같은 조건으로 지우고, prep 조건도 (properties is not null or structure is not null) 로 맞췄다. 회귀 테스트를 store.test.ts 에 넣었다.
    • 허용 구조 필드 판정이 두 곳으로 갈라져 있었다. 프롬프트를 만드는 쪽은 usesEntityStructure 를 보고 "허용 구조 필드: 없음" 을 실었는데, 적재 직전 검사는 allowedStructureFields 를 그대로 불러 label 과 items 를 통과시켰다. 범위 밖이라고 선언한 dataset 필수 카테고리에 값이 적재될 수 있었다. 판정을 allowedStructureFields 안으로 넣어 두 호출자가 같은 답을 보게 했다.
    • 배열 칸 판정이 프로토타입 오염 경로였다. 키가 LLM 이 준 임의의 문자열인데 객체 룩업(Record)으로 판정하고 있었다. JSON.parse 가 own property 로 만드는 __proto__ 는 Object.prototype 을 돌려주므로 그 자리에 대입하면 프로토타입이 바뀌고, Object.keys 를 보는 어휘 검사가 그 키를 못 본다. Set 멤버십으로 바꿨다.
    • 눈먼 자리 하나를 계측으로 바꿨다. 모델이 structure 를 통째로 생략해도 전량 배치가 조용히 성공으로 끝났다(모든 행이 structure=[] 로 적재되며 판 스탬프가 찍혀 already 판정이 전진한다). structureOmittedComponents 를 배치 요약과 CLI 출력에 더했다.
    • 뜻풀이 표에 전수 대조 장치를 뒀다. STRUCTURE_FIELD_GLOSS 는 capability.ts 의 사본이라 조용히 갈라진다. structureFieldGlossMismatches() 를 만들어(속성 쪽 paramSpecMismatches() 와 같은 원리) 테스트가 목록이 비었음을 잠근다. 도입 직후 죽은 항목 6개를 실제로 잡았다(header 는 애초에 params 키였고 나머지 다섯은 dataset 필수 카테고리 소속이었다).
    • 검수 화면 쪽 셋. (1) 경계 상자가 pointer-events 를 잡아 렌더러의 요소 클릭(글을 눌러 노드 id 를 알아내는 경로)을 막고 있었다. 상자는 pointer-events-none 으로 두고 라벨만 누를 수 있게 했다. (2) 서버가 합성 PageJson 을 만들며 뿌리를 nodes->0 으로 가정해 adjustRlscForPreview 의 rootNodeIds[0] 규칙과 갈라져 있었다(지금 색인에서는 전수 일치하지만 규칙이 두 벌이었다). 실물 sheet JSON 을 그대로 넘기고 컴포넌트 상자도 같은 규칙으로 고른 뿌리에서 읽는다. (3) 미리보기가 고정 640px 이라 좁은 화면에서 넘쳤다. 비율 유지 축소 래퍼를 씌웠다.
    • 페이로드를 줄였다. 이 GET 은 페이지네이션이 없어 경계 판의 DO 전량이 한 응답에 온다. structure_json 을 그대로 실으면 DO 30건에서 rlsc 가 응답의 80.2% 였다. 렌더러가 실제로 읽는 칸만 남기는 pruneRlscForPreview 를 만들어 응답 301KB → 161KB (46.5% 감소) 로 줄였다(rlsc 자체는 57.1% 감소). 남은 한계: pool 을 5,000건까지 넓히면 이 응답은 여전히 수십 MB 다. 그때는 페이지네이션이 답이고, 지금 하지 않았다.
  • 2026-08-23 (후속) 검수 화면의 경계 상자를 손으로 계산하지 않는다. 상세 편집 화면과 같은 렌더러를 쓴다. 목록 화면(/visual-summary/iis-boundary)이 자체 indexTree 로 노드 절대 좌표를 계산하고 있었고, 같은 컴포넌트를 상세 편집 화면과 다르게 그리는 문제가 실측에서 나왔다(design_object 5657998 와 5681639 에서 경계 상자가 실물과 어긋나 보였다. 같은 DO 를 상세 화면에서 열면 제대로 보였다). 좌표 계산이 두 벌이었던 것이 원인이다.

    • 이제 서버가 adjustRlscForPreview(DesignObjectDetailSheet 가 쓰는 그 함수)로 보정한 RLSC 를 ReviewItem.rlsc 로 내려주고, 화면은 MiniStructuredContentRenderer + flattenElements 로 그린다. indexTree 에서 boxes 계산을 지웠고 ReviewBoundary.box 과 ReviewGoldBoundary.box 필드도 없앴다. 카테고리 라벨 상자도 같은 flattenElements 결과에서 좌표를 얻는다.
    • 구조 대응을 눌러 왼쪽 미리보기와 연동한다. entity.id 와 필드 값(노드 id)이 버튼이고, 누르면 그 노드가 렌더러에서 하이라이트된다. 반대로 미리보기의 요소와 경계 상자를 눌러도 같은 상태가 된다.
    • 노드 id 옆에 실제 글을 함께 보여준다. id 만으로는 대응이 맞는지 눈으로 판정할 수 없어서, 서버가 그 DO 의 구조 대응이 참조하는 id 에 한해 글자 미리보기(ReviewItem.structureNodeTexts)를 함께 내려준다. label=ec655049-…("홍보 모델 도입") 처럼 보인다.
  • 2026-08-23 1.0.1절 미결을 닫았다. 이름은 유지, 산출물만 둘로 넓힌다. I.I.S. (Semantic Component Property) 이름은 그대로 두고(I.I.S. (Semantic Component Mapping) 으로 바꾸는 안은 기각), T-7 이 속성(properties)과 노드 ↔ 구조 필드 대응(structure)을 한 번의 호출로 함께 낸다. iis_semantic_component 에 structure jsonb 칸을 신설했다(property_extractor_version 등 provenance 칸은 properties 와 공유). 구조 필드 어휘는 새로 안 만들고 capability.ts 의 reads.content(+ 항상 허용되는 label 과 items)를 그대로 쓴다.

    • 대상이 넓어졌다. reads.params 가 비어도 entities[] 로 내용을 담는 카테고리(usesEntityStructure, dataset 이 없거나 optional)는 구조 대응 대상이 된다. dataset 필수 카테고리(matrix 과 measure)는 이번 범위 밖이다(순서상 뒤, D-7 과 같은 이유).
    • 구현은 property-v4(extractProperty.ts). 존재하지 않는 노드 id 참조와 허용 밖 필드 이름은 속성 쪽과 같은 계약으로 버리고 보고한다(checkStructureFieldKeys 과 validateStructureNodeReferences).
    • 아직 실 DB 배치로 돌리지 않았다. 단위 테스트로 파싱, 검증, 저장 배선만 확인했다. 전량 실행과 표본 눈검수와 binding.ts 소비 전환은 6절 T-7 "남은 것"에 남겨 둔다.
  • 2026-08-22 속성을 쓰는 검색과 대치(T-8)를 만들고 세 정책 비교표를 냈다. 완료 판정이 요구한 표가 6절 T-8 에 있다. 결함이 ignore 8, reorder 4, exclude 0 이고, 대신 exclude 는 1위 후보의 구조 적합을 잃는다(채운 자리 71/228 대 76/232). 정책은 아직 고르지 않는다. 표본이 질의 19개뿐이고 후보가 마르지 않은 것이 이 풀 크기(137건)의 성질일 수 있다.

    • 판정부는 propertyMatch.ts 하나다. 질의 쪽 params 를 검색 질의에 실어 보내고(query.ts), 후보 카테고리가 그 키를 읽는지는 pruneParamsForCategory(P2 와 같은 자리)가 판정한다. 허용 키 판정을 속성 쪽에 새로 만들지 않은 것이 요점이다(2.-1절).
    • 순서로 해소되는 어긋남은 다섯 쌍이고(direction, apex, rotation, sort), 그 표가 이 모듈이 더하는 유일한 지식이라 테스트가 어휘 안에 있는지 전수로 잠근다.
    • 화면(/visual-summary 의 노드 태그 검색 탭)과 CLI(scripts/iis-property-binding-poc.ts)가 같은 함수를 부른다. 화면은 한 요청에서 세 정책을 나란히 돌려 보여준다.
    • 열린 것: direction 같은 값이 화면의 모양을 말하는지 채우는 순서를 말하는지가 규약에 없다. 두 컨벤션이 어긋나면 뒤집기가 오히려 틀린다(실측 사례 design_object_id 6380453).
  • 2026-08-22 속성 추출(T-7)을 전량 돌리고 값 어휘를 못 박았다 (property-v2 → property-v3). 137 컴포넌트 전량에서 실패 0, 자격 행 144개 전부 적재됐고 값이 있는 행이 75.7%에서 85.4% 로 올랐다.

    • 판을 올린 이유는 값 어휘가 지켜지지 않았기 때문이다. property-v2 실측에서 참/거짓 속성 하나에 true, present, false, 있음, yes 와 본문 한 줄이 섞였고, unitPerIcon 에 10% 가, axisLabels 에 {xAxisPlus, yAxisPlus} 와 문자열이 섞였다. 그 상태로는 T-8 이 표기 차이를 재게 된다.
    • 그래서 어휘 표에 값의 모양(참/거짓, 정해진 낱말, 수, 객체)을 넣고 프롬프트에 실었으며, 어긋난 값은 기계로 버리고 센다(checkPropertyValues). property-v3 전량에서 버려진 값은 0개다. 모양을 알려주면 지킨다는 뜻이다.
    • 표본 14건 눈검수는 맞다 12, 애매 1, 틀렸다 1 이다(에이전트 판정, 사람 확인 전). 문제 둘 중 하나는 경계가 틀린 자리여서 속성 추출기의 잘못이 아니다.
    • 도구 둘이 함께 생겼다. scripts/iis-property-report.ts(적재 현황과 값 분포, 허용 밖 키 검사)와 scripts/iis-property-sample-crops.ts(눈검수용 컴포넌트 크롭과 판정 서식).
  • 2026-08-22 경계 골든셋 적재 경로가 생겼다 (T-4 완료 판정용 도구, MOR-2110, PR #2190). 지금까지 검수 화면은 맞다, 애매, 틀렸다 판정과 자유 메모만 남겼고 "무엇이 정답인가" 는 남기지 않았다. 새 표 iis_boundary_annotation 이 판정 상태 넷(accepted, corrected, unsure, needs_correction)과 사람이 확정한 경계 행을 함께 담고, 판정 SSoT 가 iis_boundary_review 에서 이쪽으로 옮겨갔다(옛 표는 호환용으로만 남는다).

    • LLM 사전 태깅 후 사람 수정 방식이다. Design Object 상세에 경계 편집 Drawer 가 붙어, 추출기가 낸 경계를 기본값으로 두고 카테고리(Major 별 chip, 다중 라벨), 부모 경계, 위상 관계를 고친다. 저장 전에 계약 검사(9.4절)를 다시 통과해야 하고, 위반하면 어느 노드가 문제인지 함께 돌려준다.
    • 추출물과 정답을 분리했다. 티켓의 "검토할 것" 이 남긴 가설대로 별도 표를 뒀다. Gold 행마다 판정 당시의 LLM 예측 snapshot(preannotation_*)과 그 해시를 함께 저장하므로, 현재 예측 행이 재추출로 덮여도 "무엇을 보고 확정했나" 가 남는다. 라벨러(reviewer)와 라벨 시각(reviewed_at)도 행마다 남는다.
    • 판정 키가 모델과 추론 강도를 구분하지 못하는 문제도 같이 닫았다. 옛 iis_boundary_review 는 키가 (design_object_id, extractor_version) 뿐이라 같은 판을 다른 모델로 재추출해 판정하면 앞 판정을 덮었다. 새 표는 판, 모델, reasoning effort, 예측 해시 넷을 provenance 로 들고 있어 검수 목록과 배치 정답률이 그 넷을 모두 맞춰 붙인다.
    • 확정 Gold 를 실수로 덮지 않는다. Gold 를 대체하는 문은 편집 Drawer 하나이고(목록의 인라인 판정은 Drawer 를 연다), 낙관적 잠금(reviewed_at 되돌려주기)을 통과해야 저장된다. 판을 올린 뒤 같은 컴포넌트를 다시 라벨링할 때는 화면이 어느 판의 Gold 를 대체하는지 먼저 보여준다.
    • 확정된 Gold 를 훑는 목록 화면(/visual-summary/iis-boundary-gold)도 함께 붙었다. accepted 와 corrected 비율을 그 자리에서 볼 수 있어 확인 편향의 크기(6절 T-4)를 추적할 수 있다.
    • 범위 밖(다음 할 일로 남긴 것): 표본 500건 전량 라벨링은 아직 0건에서 시작하지 않았다. 골든셋 내보내기와 그것으로 판을 바꿔 가며 자동 채점하는 경로도 없다. 둘 다 MOR-2110 의 완료 기준에 남아 있다.
  • 2026-08-22 PoC 대치 3경로와 평가 pool bake 우선순위. binding.ts(규약 RLSC)는 실서비스와 내용시각화 BE 경로가 아님을 contract 3.4·poc-module-map.md 2.5절에 명시. comp-sign(4단계) / property(T-8, 5단계) / RLSC(PoC)를 같은 DO subset에서 비교하려면 signature + I.I.S. 경계와 속성 사전 bake가 선행(content-binding-contract.md 3.6절, TODO I-8, I-9). blueprint T-2는 평가 pool subset과 전량 178k를 구분. PoC 우선순위: I-8 → I-7 → I-9 → contract, 검수, 통합 (poc-module-map.md 3절).

  • 2026-08-17 (추가) I.I.S. 두 종(경계와 속성) 모두 화면에서 직접 돌릴 수 있게 됐다. 지금까지는 검수 화면(/visual-summary/iis-boundary)이 이미 적재된 결과를 읽기만 했고, 추출 자체는 터미널에서 scripts/iis-extract-boundary.ts 과 scripts/iis-extract-property.ts 를 돌려야 했다. 두 스크립트의 대상 조회와 적재 루프를 src/lib/visual-summary/iis/runExtraction.ts 로 뽑아 공용 함수(runBoundaryExtractionBatch 과 runPropertyExtractionBatch)로 만들고, 새 API(POST /api/visual-summary/iis-extract)와 검수 화면의 "새로 추출" 패널이 같은 함수를 부른다. 스크립트와 화면이 대상 선정 규칙을 따로 구현하지 않는다. 화면에서 판(경계/속성)과 건수를 고르고 "실행"을 누르면 그 자리에서 뽑혀 iis_semantic_component 에 적재되고, 목록이 자동으로 새로고침된다. HTTP 요청 하나가 배치를 동기로 기다리므로 서버가 건수를 40건으로 자른다. 더 큰 배치는 여전히 터미널 스크립트를 쓴다. 브라우저로 직접 클릭해 경계와 속성 둘 다 동작을 확인했다(경계: 대상 2건 강제 재추출 성공, 속성: 새 대상 2건 적재 성공).

  • 2026-08-17 T-5(노드 태그 검색), T-6(노드 태그 대치), T-7(속성 추출기) 구현. 정원석 지시로 사람 검수를 기다리지 않고 세 결과물을 한 번에 만들었다. 원래 순서(T-4 사람 검수 → T-5/T-6)를 건너뛴 것이니 품질 검수는 여전히 별도로 필요하다.

    • T-5 src/lib/visual-summary/iis/nodeSearch.ts: 경계 노드마다 그 서브트리를 독립된 IndexComponent 로 만들어(buildNodeIndex) 기존 search.ts rankComponents() 를 그대로 재사용한다. 태그 군과 채점 규칙을 새로 만들지 않았다(2.-1절의 교훈). IndexComponent/RankedCandidate 에 nodeId?: string 를 더해 두 경로(컴포넌트 단위와 노드 단위)가 같은 함수를 공유한다.
    • T-6 src/lib/visual-summary/iis/nodeBinding.ts: 매칭된 경계 서브트리에만 bindComponentToRlsc(기존 대치 함수, 미변경)를 돌리고 원래 위치에 이어붙인다(bindNodeToRlsc). "밖은 원본 보존" 불변식을 회귀 테스트로 고정했다(nodeBinding.test.ts).
    • T-7 src/lib/visual-summary/iis/extractProperty.ts + propertyPrompt.ts(PROPERTY_EXTRACTOR_VERSION = property-v1): 속성 어휘를 capability-table.md 2절 사본이 아니라 capability.ts reads.params 를 그대로 읽어 쓴다(같은 이유로 새 어휘를 만들지 않았다). 경계가 이미 있는 노드에만 속성을 붙이는 v1 범위다. 새 PK 행을 만들지 않고 기존 경계 행을 UPDATE 만 한다(store.ts replaceProperties/loadProperties). 검수 화면(/visual-summary/iis-boundary)이 카테고리 배지 아래에 속성을 바로 보여준다.
    • 표본도 넓혔다. boundary-v7 로 재추출해 116개 컴포넌트(경계 행 232개) 로 늘었다(기존 boundary-v6 59건에서 상향). 다만 이 판의 사람 검수는 아직 0건이다. T-4 완료 판정("층화 표본 30건 사람 검수")이 아직 안 채워진 채로 T-5/T-6/T-7 을 얹은 상태다.
    • PoC 화면에 새 탭 "노드 태그 검색 (T-5 과 T-6)"(/api/visual-summary/iis-node-search)을 추가했다. I.I.S. 경계 카테고리 2종 이상 섞인 컴포넌트 풀에서, 컴포넌트 태그가 아니라 노드 태그로 검색과 대치하는 것을 화면에서 바로 확인할 수 있다. scripts/iis-node-search-poc.ts 는 같은 것을 실 DB 데이터로 확인하는 CLI 스모크다.
    • 범위 밖(다음 할 일로 남긴 것): T-5/T-6 는 아직 service.ts(메인 파이프라인의 comp-sign 과 순수 RLSC 경로와 나란히 비교하는 자리)에는 안 얹었다. 지금은 독립된 탭/스크립트다. T-7 은 A군 6종(Steps, Staircase, Cycle, Pyramid, Timeline, Quadrant) 검증 우선순위를 못 박지 않고 reads.params 가 있는 모든 카테고리에 제네릭하게 돈다(41종 중 이번 10건 표본에서 6종 실측). property-v1 판의 정확도 검수(6절 T-7 완료 판정의 "결과물로 확인" A군/"적재만" B군 구분)는 아직 안 했다.
  • 2026-08-16 BAD 대용치의 구조 14% 가 허수였다. 실제 7%. 구조 경로의 1위 후보에 감사 결과가 없는 입력(4건)이 "무거운 결함 없음" 으로 집계되고 있었다. 계측 실패가 성공으로 세어진 것이고, 시멘틱은 그런 입력이 0건이라 구조 쪽만 부풀었다. 이제 분모에서 빼고 뺀 건수를 함께 낸다(pageNoBlockerUnmeasured). 결과적으로 두 헤드라인이 같은 말을 하게 됐다. 가중 평균도 BAD 대용치도 시멘틱이 낫다. 6절 T-1.

  • 2026-08-16 T-1 다섯 번째 정정. 허용 집합을 "검색에서 받는다"로 고친 뒤에도 1군과 2군만 받고 있어 3군(확정 카테고리)과 4군(안전망)으로 고른 후보가 전부 결함으로 잡혔다. 집합을 만드는 함수를 검색 모듈에 하나 두고(search.acceptedCategoriesFor) 거기서만 만든다. 구조 21.6 → 22.0, BAD 대용치 구조 18% → 14%. 전에는 집합이 비어 판정을 통째로 건너뛴 컴포넌트가 있었고, 그것들이 이제 판정된다. 회귀 테스트 셋으로 3군과 4군을 잠갔다.

  • 2026-08-16 채점기가 허용 집합을 계산하지 않게 바꿨다 (2.-1절). PR 리뷰가 같은 종류의 Critical 을 거듭 낸 원인이 "규칙을 두 벌 만든 것" 이었다. capability.ts 90줄과 테스트 24건이 줄었다. 부모 외래키의 on delete cascade 도 걷어냈다(속성이 딸려 지워진다).

  • 2026-08-16 T-4 두 번째 검수. 그림을 넣자 틀렸다 15 → 4. 남은 실패의 공통 원인(차트를 나열로 읽는다)을 프롬프트 예시 넷으로 보정(boundary-v6). 표본 60건, 뿌리=태그 32/59. 계약 검사가 넷 늘었다. D-11 확정(Unclassified + 남용 방지 검사).

  • 2026-08-16 검수 화면(/visual-summary/iis-boundary)과 판정 표(iis_boundary_review) 신설. 정적 HTML 로 두기엔 아까운 물건이라 PoC 안으로 옮겼다. 골든셋을 만들 때 계속 쓴다.

  • 2026-08-15 T-4 사람 검수 19건 (정원석). 맞다 5, 애매 4, 틀렸다 10. 틀린 10건 중 8건이 모양으로 정해지는 카테고리였다. D-10 확정(추출기가 그림을 본다), D-11 신설(이종 병치의 최상위 카테고리). 태그 일치 신호는 사람 판정과 11/15 로 쓸 만하다.

  • 2026-08-15 T-4 중간. 마이그레이션, 투영, 계약 검사와 추출기 구현. 층화 표본 20건에서 계약 20/20 통과하나 태그 일치 4/20 이라 전량 추출은 보류했다. 계약 검사를 실측으로 둘 늘렸다(relationWithoutSiblings 과 redundantNestedCategory).

  • 2026-08-15 D-8 과 D-9 확정 (정원석). 경계 추출 어휘는 49종 전부(검색 풀 제외와는 별개), 생성 어휘에는 PageNavigation_* 을 넣고 Geo_* 는 뺀다. 추출 대상은 색인 전량. D-2 에 규칙 기반 경계 판정 금지, LLM 전용을 못 박았다. D-4(속성 범위)는 보류로 되돌렸다.

  • 2026-08-15 9.1.1 신설. 우리가 다루는 것이 유효 컴포넌트(v3.5) 임을 실측으로 확인했다. 조건으로 건 것이 아니라 태깅 모집단이 178,095 VC 와 같기 때문이다. 다만 적재 스크립트가 valid_component_meta 를 복사하지 않아 로컬에서는 검증이 안 된다. T-3 에 넣었다.

  • 2026-08-15 mordor-wiki 최신화 후 9.3 정정. 이종상태 어휘를 내가 만들 필요가 없었다. 규약 8.7절이 Topological Relation(Containment / Juxtaposition / Overlap)을 이미 정의해 뒀고(PR #75), 수량 실측도 나와 있었다(PR #71, 1.10%, 약 2,014건). 칸 이름을 composition 에서 topological_relation 으로 바꾸고 규약 어휘를 그대로 쓴다. Overlap 은 구조상 발생하지 않으므로 나오면 경계 판정 오류로 보는 검사를 더했다.

  • 2026-08-15 T-4a 계약 확정 (정원석). 9절 신설. 합성 식별자 그대로 사용(재추출 시 I.I.S. 도 다시 돌린다), 확신도 미수록, 내부까지 전수 커버리지, 추출 여부는 행 존재로 판정. 이종 컴포넌트의 갈래와 D-7 과의 관계를 적었다 (어휘는 같은 날 바로 아래 항목에서 규약 것으로 교체했다).

  • 2026-08-15 순서 변경. 시그니처 테이블(T-2)과 색인 분포 교정(T-3)을 맨 앞에서 T-9 직전으로 미뤘다. 둘 다 4단계판을 만드는 데 지금 필요하지 않고, 결과물이 먼저 나오는 편이 낫다.

  • 2026-08-15 리뷰 반영 (정원석). D-1 을 "우회로"가 아니라 갈림길로 다시 적었다(comp-sign 대체, 변경, 중단 셋). D-2 계약을 RLSC 를 손대지 않는 별도 지도 둘(경계 지도와 속성 지도)로 못 박았다. D-3 은 고를 것이 없어 결정에서 내렸다(로컬 설계 → 마이그레이션이 배포). 차트 데이터 대치를 "범위 밖"에서 **"순서상 뒤"**로 바꾸고 D-7 로 열었다. 골든셋과 의미 거리의 관계를 D-6 에 적었다. 코드 식별자 규칙을 iis<대상><국면> 으로 두고 호칭을 전체 이름으로 통일했다.

  • 2026-08-15 T-1 완료. 의미 거리 결함 mapping.categoryDrift 신설. 두 경로 우열이 뒤집혔다(구조 17.5 → 20.1). 색인 어휘 49종 대 규약 41종 격차 발견(2.0절). --diff 의 가중 평균 누락과 신규 결함 NaN 도 함께 고쳤다.

  • 2026-08-15 D-2 확정 (정원석). 경계를 대행 생산하고, 출력 계약도 우리가 먼저 정해 RAT pod 에 제안한다. 답을 기다리지 않는다. T-4a 신설.

  • 2026-08-15 D-1 확정 (정원석). 자체 대치 경로 위에 만든다. comp-sign 은 대조군으로 남긴다.

  • 2026-08-15 D-4 확정 (정원석). 속성은 41종 전부. 검증 가능성이 A군 25종(결과물 확인), B군 16종(적재만)으로 갈리는 것을 표에 명시.

  • 2026-08-15 I.I.S. 두 갈래 표기 확정 (정원석). I.I.S. (Semantic Component Boundary) 4단계, I.I.S. (Semantic Component Property) 5단계, 속성이 경계를 포함한다. 1절.

  • 2026-08-15 최초. 4단계판과 5단계판을 각각 완성하는 것으로 범위를 잡았다. 확인한 사실 넷(속성 어휘 기확정 / comp-sign 이 노드 속성을 못 받음 / 검색은 변형 최대 100개 / 노드 태그 재료 절반 존재)을 근거로 삼는다.

↑ 목차로 · 5부 머리로

26장속성 정의와 이종 컴포넌트 구조 대치 리뷰 초안초안

원문 plan/property-definition-projection-review-draft.md GitHub 에서 보기 35 KB 원문 제목 「속성 정의와 이종 컴포넌트 구조 대치 리뷰 초안」

이 문서의 역할 이 문서는 두 가지를 본다. (1) semantic category 41종의 선택 속성(property) 정의가 실제로 다 마련됐는지. (2) 서로 다른 semantic category 가 한 컴포넌트 안에 섞여 있는 이종(異種) 컴포넌트를 검색하고 대치할 때, 내용을 잃지 않고 표현을 완수하는 기법이 무엇이고 어디까지 정의됐는지. 판단은 아직 내리지 않는다. 정원석이 문서를 읽으며 잘못된 지점을 지적하는 것이 다음 단계다.

2026-08-23 정정. 초판은 (2)를 "property projection(T-8)"으로 좁혀서 다뤘다. 리뷰 중 "내가 원한 건 이종 컴포넌트 간 structure mapping이었을 수 있다"는 지적을 받고 6절을 다시 썼다. property 값 하나(rotation: clockwise)의 모양과, 이종 컴포넌트 전체의 구조를 어떻게 옮기는가는 서로 다른 문제였다.

2026-08-23 리뷰 중 추가 정정 (6절). entity.components[]에 서로 다른 카테고리를 여러 개 한꺼번에 매다는 시도를 여러 번 거쳤다(스키마는 통과하지만 전례가 없는 방식, 스키마에 없는 가상 wrapper, 색인 전용 Unclassified 재사용 시도, label/items 값 자체를 component로 대체하는 제안까지). 전부 시행착오였고, 최종 결론은 "entity는 항상 자기 label을 갖고, components는 entity 하나에 정확히 1개만 매단다. 병치되는 여러 컴포넌트는 entities[]를 늘려 항목마다 하나씩 붙인다"이다(이유: Enumeration.List 같은 카테고리는 "이름 붙은 것들의 나열"이 그 자체의 뜻이라, 항목에서 label을 없애면 그 카테고리의 의미가 깨진다). 결론만 보고 싶으면 6.1절의 "중첩이 매달리는 자리는..." 문단과 7절 항목 10·11만 읽으면 된다. 시행착오 전체 과정은 6.1·6.6·7절에 순서대로 남겨 뒀다.

오너 정원석 (MORDOR) · 상태 DRAFT, 리뷰 대기 · 작성 2026-08-23 관련 capability-table.md 2절, 3.4절 · schema-v0.5.md 1절, 8.7절 · content-binding-contract.md 3.4절, 3.5절 · node-tag-and-property-poc-blueprint.md · TODO.md D-7, I-7

이 문서 훑기

절 무엇에 답하나
1 semantic category 와 property 어휘가 어떻게 나뉘어 있나
2 25종 property 정의가 빠짐없이 실렸는가
3 25종 전량 문서 대 코드 대조표
3.1 25종 전량, 질의 YAML과 실제 추출값 예시
4 문서가 이미 닫은 결정과 코드가 다른 사례
5 자동 테스트가 못 잡는, 사람이 봐야 하는 것
6 이종 컴포넌트란 무엇이고, 대치 기법이 어디까지 정의됐나
6.6 dummy Visual Summary로 포함위상, 병치위상, 그 실패 대안 넷을 실제 YAML로 보기
7 정원석이 답해야 다음 단계가 정해지는 질문들

1. 배경#

색인이 실제로 쓰는 semantic category 태그는 49종이다(boundaryPrompt.ts 14~71행). 그중 41종만 capability-table.md가 성립 조건과 선택 속성을 정의한다. 나머지 8종은 Geo_* 4종(지도 데이터 필요, 스펙 범위 밖)과 PageNavigation_* 4종(스펙 2.6절이 "성립 조건 미정"이라고 명시, C-4 결정 대기)이다. 여기에 이종 병치 표식 Unclassified 1종을 더하면 경계 추출 어휘는 총 50종이다.

41종 중 표현 선택(코드에서 params, 스펙에서 "선택 속성" 열의 일부)이 있는 카테고리는 25종이고, 16종은 표현 선택이 없어 정의할 속성 자체가 없다(스펙 2절, capability-table.md 61~140행 전량 대조로 확인).

이 25종의 속성 어휘는 src/lib/visual-summary/iis/propertyVocabulary.ts의 PROPERTY_PARAM_SPEC에 카테고리마다 키, 뜻풀이(gloss), 값의 모양(kind: boolean / enum / number / text / object), enum이면 허용 값(values)을 담는다.

2. 구조는 완전하다 (자동 검증됨)#

PROPERTY_PARAM_SPEC의 카테고리 목록이 capability.ts의 reads.params가 비어 있지 않은 카테고리 목록과 정확히 일치하는지는 propertyVocabulary.test.ts 29~34행("reads.params 가 있는 카테고리는 전부 실린다")이 전수로 잠근다. 방금 돌려 본 결과 이 파일 관련 테스트 66개가 전부 통과한다(propertyVocabulary.test.ts, capability.test.ts).

즉 키가 빠진 카테고리는 없다. 이 절에서 더 볼 것은 없고, 문제는 3절부터다.

3. 값 모양은 문서에 없고 코드에만 있다#

capability-table.md 2절의 "선택 속성 (기본값)" 열은 각 속성의 기본값만 적는다. 어떤 값들이 허용되는지(enum의 values)는 적지 않는다. 값 모양(kind, values)은 2026-08-22에 property-v2 전량 실행 결과가 지저분했던 것(예: 참/거짓 속성 하나에 true, present, false, 있음, yes, 본문 한 줄이 섞임)을 보고 나서 코드에만 사후적으로 추가됐다(propertyVocabulary.ts 126행 머리말, node-tag-and-property-poc-blueprint.md 570574행).

아래는 25종 전량을 문서 원문과 코드로 나란히 놓은 것이다. 일치는 문서 문구에서 코드의 kind가 그대로 읽히는 것, 코드 확장은 문서가 기본값만 적었는데 코드가 그 밖의 허용 값을 새로 정한 것, 재해석은 문서 문구와 코드의 값 모양이 서로 다른 것을 뜻할 수 있는 것이다.

카테고리 키 문서 원문 (capability-table.md) 코드 kind / values 대조
Enumeration_ListingTable header header (첫 행이 머리글) boolean 일치
AttributeComparison_ComparisonTable header header (첫 행이 머리글) boolean 일치
AttributeComparison_ComparisonTable axis axis (첫 열이 기준 축) text, 뜻풀이 "첫 열이 기준 축이라는 라벨" 재해석 의심. 문서 문구는 header와 같은 형식이라 "이 열이 축인가"를 뜻하는 boolean으로도 읽힌다. 코드는 "축의 이름을 적는 글자"(text)로 정했다. 둘 중 어느 쪽이 맞는지 문서만으로는 판별 안 됨
AttributeComparison_RadarChart maxValue maxValue (계열 최대값이 기본) number 일치
RelationalComparison_SWOT order order (강점, 약점, 기회, 위협 순이 기본) boolean, 뜻풀이 "네 항목이 그 순서로 놓였는가" 재해석. 문서 문구는 "어떤 순서인가"를 묻는 것처럼도 읽히는데, 코드는 "그 고정 순서인지 아닌지"를 묻는 boolean 하나로 좁혔다. property-v2 실측에서 강점, 약점, 기회, 위협(글자 그대로)과 true가 섞여 나온 것을 boolean으로 강제 수렴시킨 것(블루프린트 570행)
RelationalComparison_Quadrant axisLabels axisLabels (없으면 축 이름을 비움) object, objectKeys: ['x', 'y'] 일치
RelationalComparison_BubbleChart sizeScale sizeScale (linear가 기본) enum, ['linear', 'log'] 코드 확장. log는 문서에 없음
Transformation_Transformation direction direction (forward가 기본) enum, ['forward', 'backward'] 코드 확장. backward는 문서에 없음
ProcessFlow_Steps sequence sequence (없으면 배열 순서) boolean 일치
ProcessFlow_Steps marker marker (number가 기본) enum, ['number', 'letter', 'dot'] 코드 확장. letter, dot은 문서에 없음
Progression_Staircase direction direction (ascending이 기본) enum, ['ascending', 'descending'] 코드 확장. descending은 문서에 없음
TimelineSchedule_Timeline orientation orientation (horizontal이 기본) enum, ['horizontal', 'vertical'] 코드 확장. vertical은 문서에 없음
TimelineSchedule_GanttChart unit unit (month가 기본) enum, ['day', 'week', 'month'] 코드 확장. day, week는 문서에 없음
TimelineSchedule_Calendar weekStart weekStart (Mon이 기본) enum, ['Mon', 'Sun'] 코드 확장. Sun은 문서에 없음
Hierarchy_HierarchicalTree orientation orientation (vertical이 기본) enum, ['vertical', 'horizontal'] 코드 확장. horizontal은 문서에 없음
Hierarchy_Pyramid apex apex (last가 기본) enum, ['first', 'last'] 코드 확장. first는 문서에 없음
ScopeCoverage_NestedScope shape shape (circle이 기본) enum, ['circle', 'rectangle'] 코드 확장. rectangle은 문서에 없고, 이 둘로 충분한지도 확인 안 됨
Cycle_Cycle center center (없으면 가운데를 비움) text 일치
Cycle_Cycle rotation rotation (clockwise가 기본) enum, ['clockwise', 'counterclockwise'] 코드 확장. counterclockwise는 문서에 없음
MagnitudeRanking_BarChart orientation orientation (vertical이 기본) enum, ['vertical', 'horizontal'] 코드 확장. horizontal은 문서에 없음
MagnitudeRanking_BarChart sort sort (입력 순서가 기본) enum, ['asc', 'desc', 'none'] 일치. 다만 asc/desc 표기가 문서에 없어 확인 필요
ProportionComposition_StackedBarChart orientation orientation (vertical이 기본) enum, ['vertical', 'horizontal'] 코드 확장
ProportionComposition_StackedBarChart normalize false 로 고정. true 는 렌더러 미지원으로 스펙에서 뺐다 (2026-08-17 결정) boolean (제약 없음) 모순. 4절에서 따로 다룬다
ProportionComposition_PieChart labelPosition labelPosition (outside가 기본) enum, ['inside', 'outside'] 코드 확장. inside는 문서에 없음
ProportionComposition_PieChart startAngle startAngle (0이 기본) number 일치
ProportionComposition_DonutChart centerLabel centerLabel (없으면 가운데를 비움) text 일치
ProportionComposition_DonutChart labelPosition, startAngle PieChart 와 같음 PieChart 와 같음 위와 동일
ProportionComposition_PictogramChart icon icon (없으면 기본 아이콘) text 일치
ProportionComposition_PictogramChart unitPerIcon unitPerIcon (1이 기본) number 일치
Trend_LineAreaChart fill fill (false가 기본, 참이면 area) boolean 일치
Trend_ComboChart type(계열별) type (bar가 기본) enum, ['bar', 'line'] 코드 확장. line은 문서에 없음
Trend_ComboChart axis axis (left가 기본) enum, ['left', 'right'] 코드 확장. right는 문서에 없음
Conversion_Funnel conversionRate conversionRate (없으면 비율 표기 생략) boolean 일치
TextBasedCommunication_LongFormTextLayout title title (없으면 본문만) boolean 일치

요약 25종 33개 키 중 14개가 "코드 확장"이다. 문서에는 기본값만 있고, 그 반대값이나 나머지 허용값은 코드에서 처음 정해졌다. 대부분은 "기본값의 반대"로 자연스럽게 유도되는 것들(ascending↔descending, clockwise↔counterclockwise)이라 위험이 낮아 보이지만, 문서(SSoT라고 선언된 capability-table.md)에는 반영되지 않은 채 코드에만 있다는 사실 자체가 검토 대상이다. axis(text vs boolean)와 order(boolean로 좁힘)는 문서 문구와 코드의 해석이 갈릴 수 있는 자리라 확인이 더 필요하다.

3.1 카테고리별 데이터 예시: 질의 YAML과 실제 추출값#

3절의 대조표는 값의 모양(kind, values)만 보여준다. 실제로 어떤 데이터가 들어가는지 눈에 보이도록, 25종 전량에 질의 쪽 YAML 조각(type + params, 필요하면 structure 일부)과 값 하나를 붙인다.

"실제 추출값"이라고 적은 것은 property-v3 137건 전량 실행 표본 검수 14건에서 나온 값이다(node-tag-and-property-poc-blueprint.md 578~582행). 그 14건에 없는 카테고리는 "구성 예시"라고 적었고, 이건 이 문서를 쓰며 값 모양에 맞춰 만든 것이지 실측이 아니다. 질의 쪽 YAML 자체가 이미 yaml-examples.md에 있는 카테고리는 그 문서 절 번호를 인용했다.

나열과 비교#

Enumeration_ListingTable (구성 예시)

yaml
type: Enumeration.ListingTable
params: { header: true }

AttributeComparison_ComparisonTable (구성 예시. axis는 3절에서 지적한 재해석 의심 지점이다)

yaml
type: AttributeComparison.ComparisonTable
params: { header: true, axis: 기준 }

AttributeComparison_RadarChart (구성 예시)

yaml
params: { maxValue: 100 }

RelationalComparison_SWOT (구성 예시. property-v2 실측에서는 이 키 하나에 강점, 약점, 기회, 위협이라는 문자열과 true가 섞여 나와서 boolean으로 좁혔다)

yaml
params: { order: true }

RelationalComparison_Quadrant (질의 예시는 yaml-examples.md 2.4절 실물)

yaml
type: RelationalComparison.Quadrant
params: { axisLabels: { x: 시장 점유율, y: 성장률 } }

실제 추출값(틀린 사례, 블루프린트 582행): x축과 y축 이름이 서로 바뀐 채로 뽑혔다. 값 모양(object, x/y 키)은 맞아도 내용이 틀릴 수 있다는 사례다.

RelationalComparison_BubbleChart (구성 예시)

yaml
params: { sizeScale: log }

Transformation_Transformation (구성 예시)

yaml
params: { direction: backward }

순서와 흐름#

ProcessFlow_Steps (marker는 실제 추출값, 블루프린트 580행. sequence는 구성 예시)

yaml
params: { sequence: false, marker: number }

Progression_Staircase (구성 예시)

yaml
params: { direction: descending }

TimelineSchedule_Timeline (구성 예시)

yaml
params: { orientation: vertical }

TimelineSchedule_GanttChart (구성 예시)

yaml
params: { unit: week }

TimelineSchedule_Calendar (구성 예시)

yaml
params: { weekStart: Sun }

Cycle_Cycle (실제 추출값, 블루프린트 580행, "맞다"로 판정됨)

yaml
params: { center: "4 Stroke cycle", rotation: clockwise }

계층과 범위#

Hierarchy_HierarchicalTree (실제 추출값, 블루프린트 581행. 다만 "애매 1"로 판정됐다. 경계 안에 실제 트리가 없는데 속성만 뽑힌 사례라, 값 자체보다 경계 판정을 먼저 의심해야 한다)

yaml
params: { orientation: vertical }

Hierarchy_Pyramid (질의 예시는 yaml-examples.md 5.1절 실물)

yaml
type: Hierarchy.Pyramid
params: { apex: last }

ScopeCoverage_NestedScope (구성 예시)

yaml
params: { shape: rectangle }

수치#

MagnitudeRanking_BarChart (구성 예시)

yaml
params: { orientation: horizontal, sort: desc }

ProportionComposition_StackedBarChart (실제 추출값, 블루프린트 580행, "맞다"로 판정됨)

yaml
params: { normalize: true, orientation: horizontal }

이것이 4절 모순의 실측 증거다. 스펙은 normalize를 false 고정으로 정했는데(렌더러가 true를 못 그린다는 이유), 실제 추출은 이미 true를 뽑았고 눈검수에서 화면과 맞는 값이라고 판정됐다. 렌더러가 못 그리는 값을 속성 추출기는 이미 읽고 있다는 뜻이다.

ProportionComposition_PieChart (구성 예시)

yaml
params: { labelPosition: inside, startAngle: 90 }

ProportionComposition_DonutChart (구성 예시)

yaml
params: { centerLabel: "합계 100%", labelPosition: outside, startAngle: 0 }

ProportionComposition_PictogramChart (실제 추출값, 블루프린트 580행, "맞다"로 판정됨. 아이콘 10개가 실제 화면의 70%=7개와 맞아떨어졌다)

yaml
params: { icon: 사람, unitPerIcon: 10 }

Trend_LineAreaChart (구성 예시)

yaml
params: { fill: true }

Trend_ComboChart (질의 예시는 yaml-examples.md 2.3절 실물. params가 아니라 dataset.columns 열마다 붙는다는 점이 다른 24종과 다르다)

yaml
dataset:
  columns:
    - { label: 매출, role: value, unit: 억원, axis: left, type: bar }
    - { label: 성장률, role: value, unit: "%", axis: right, type: line }

텍스트와 사람#

Conversion_Funnel (실제 추출값, 블루프린트 580행)

yaml
params: { conversionRate: false }

TextBasedCommunication_LongFormTextLayout (구성 예시. property-v2에서 이 키 하나에 true×27, present×4, false×3, 있음×3, yes×1, 본문 한 줄×1이 섞여 나온 것이 boolean으로 좁힌 계기였다)

yaml
params: { title: true }

4. 확정된 결정과 코드가 어긋난 사례: StackedBarChart.normalize#

fallback-map-review-2026-08-13.md 7절이 2026-08-17에 이렇게 확정했다.

StackedBarChart의 normalize 옵션은 참이면 전체 막대 높이가 항상 100%가 되도록 비율로 다시 그리고, 거짓이면 원래 수치 그대로 쌓는다. 이지선이 백업 시트 코멘트에서 지금 렌더러가 normalize: true 버전을 만들지 못한다고 지적했고, 정원석이 확인했다: 미리캔버스 엔진에 stacked bar 요소 자체가 없어 summary 생성 시 지원 불가가 맞다. ... true 옵션을 스펙에서 뺀다. 렌더러가 실제로 지원할 때까지 normalize는 항상 false다.

그런데 지금 코드는 세 군데 모두 normalize를 여전히 자유 선택값처럼 다룬다.

  • capability.ts: ProportionComposition_StackedBarChart의 reads.params에 normalize가 그대로 들어 있다 (버릴 대상이 아니라 읽을 대상으로 선언돼 있다)
  • propertyVocabulary.ts: normalize: { gloss: '전부 100%로 맞춰 비율만 보여주는가 (기본 false)', kind: 'boolean' }. boolean이라 true도 유효한 값으로 통과한다
  • checkPropertyValues(같은 파일)는 이 스펙대로 검사하므로, 추출기가 true를 뽑아 오면 그 값을 버리지 않고 그대로 통과시킨다

정확히 문서가 막은 값을 코드가 그대로 허용하고 있다. 실제로 true가 추출된 적이 있는지는 확인 전이지만, 통과 여부를 가르는 검사(checkPropertyValues)가 이 값을 걸러내지 못하는 것은 사실이다.

5. 사람 확인이 아직 안 된 지점#

자동 테스트는 "키가 다 실렸는가"만 잠근다. 값이 실제로 맞는지, 팀이 합의했는지는 별개이고, 다음 세 가지가 문서에 그대로 남아 있다.

  1. 추출 결과 표본 검수가 사람 확인 전이다. property-v3 137건 중 14건 눈검수 결과가 "맞다 12, 애매 1, 틀렸다 1"인데, node-tag-and-property-poc-blueprint.md 576행이 이를 "정원석 확인 전"이라고 명시한다. 판정은 에이전트가 했다.
  2. fallback 순서 45건이 팀 전원 확인 전이다. capability-table.md 8절: "45건 전량이 2026-08-17에 스쿼드(정원석)과 사용자 결정으로 확정됐다. 김수현과 류민 전원 확인은 아직 없다." property 자체는 아니지만 같은 표를 정원석 단독으로 확정하고 넘어간 전례라 property 값 모양(3절의 "코드 확장" 14건)도 같은 리스크를 의심할 근거가 된다.
  3. 카테고리별 decoration 축 기본값이 41종 중 0종. 같은 문서 8절: "이 표에 장식 언급이 0건이다... 카테고리 기본값을 41종 전량에 채우는 것이 이 표의 몫이다" (B-18, B-21, 아직 열림). property 표는 아니지만 "카테고리마다 채워야 할 속성"이라는 틀에서 보면 같은 미완성 항목이다.

6. 이종 컴포넌트 구조 대치: 다시 정의부터#

6.1 이종 컴포넌트가 무엇인가 (이미 정의됨)#

**이종중첩 컴포넌트(heterogeneous nested component)**는 하나의 컴포넌트 경계 안에 서로 다른 semantic category가 함께 있는 경우다(schema-v0.5.md 8.7절). 예를 들어 Enumeration.List 항목 하나하나가 그 안에 KPIKeyMetric.BigNumber를 품고 있는 모양이다. 스키마는 이걸 담을 자리로 entity.components[]를 v0.4 때부터 열어 뒀지만, 생성기는 아직 이 필드를 채우지 않는다(같은 절, "표현 가능성만 열어 두고 켜는 시점은 생성기 정책이 정한다").

중첩이 매달리는 자리는 entity 전체이지, entity 안의 값 필드(label, items 등)가 아니다. entity.components[]는 entity 객체 자신에 딸린 별도 배열 필드다(entitySchema, schema.ts 84~105행). label은 항상 문자열이고(z.string().min(1)) items는 항상 문자열 배열이라서(z.array(z.string())), 그 값 자리에 component를 직접 넣을 수는 없다(직접 실행해 확인: items에 component 객체를 넣으면 expected string, received object로 거부됨). 8.7절 원본 전례는 entity 하나에 component 정확히 1개를 붙이는 모양이다. entity는 자기 label/items를 그대로 갖고, 그 밑에 하위 component를 하나 더 얹는 것이지, entity 하나에 여러 개를 몰아넣는 전례는 없다. 여러 카테고리를 병치하고 싶으면 entities[]를 늘려서 항목마다 자기 component를 하나씩 갖게 한다(6.6절 (3) 참고).

이종 컴포넌트 안에서 서로 다른 카테고리 영역이 공간을 어떻게 나눠 쓰는지는 **Topological Relation(위상 관계)**이라는 이름으로 이미 세 가지로 분류돼 있다(같은 절, 2026-08-13 확정).

분류 정의 지금 데이터에서
Containment (포함) 한 카테고리의 시각 영역이 다른 카테고리의 시각 영역 안에 완전히 들어간다 예: List 전체 안에 분기마다 BigNumber가 들어간 모양
Overlap (중첩) 서로 다른 카테고리의 시각 영역이 부분적으로 겹친다 관측되지 않는다. RLSC 트리에서 한 노드는 부모 하나에만 속하므로 다중 소속이 전제되는 이 경우가 구조적으로 발생하지 않는다
Juxtaposition (병치) 서로 다른 카테고리의 시각 영역이 겹치지 않고 나란히 놓인다 2026-08-13 수량 산출에서 관측된 11건 전부가 이 경우였다 (예: 좌측 BigNumber + 우측 List)

이 분류는 실측으로 뒷받침돼 있다. 전체 183,052개 컴포넌트에서 1,000개를 뽑아 태깅 프롬프트로 이종 여부를 판정한 결과, 이종중첩은 11건(1.10%, 95% 신뢰구간 0.621.96%, 전체 환산 약 2,014개)이었다. 11건의 조합은 BigNumber + List 4건, HighlightedMessage + List 2건, 나머지 다섯 조합이 각 1건씩이었다(같은 문서 12절).

"담기만 하는 묶음"에는 이미 실제 이름이 있다. 이종 병치의 뿌리, 즉 자기 고유 성질 없이 서로 다른 자식만 담는 노드는 색인 태깅 어휘에서 Unclassified로 표시한다(node-tag-and-property-poc-blueprint.md D-11, 2026-08-15 정원석 확정). BOUNDARY_VOCABULARY_SET이 경계 태깅 어휘 49종에 Unclassified를 더해 50종을 이룬다(boundaryPrompt.ts 8389행). 각 경계 행은 category(무슨 카테고리인가)와 별도로 topologicalRelation 필드에 부모와의 위상 관계(Juxtaposition 등)를 담는다(annotationStore.test.ts 2627행).

다만 이 어휘는 색인(기존 디자인을 태깅하는) 쪽 것이다. 생성기가 쓰는 질의 스키마의 type은 SEMANTIC_CATEGORIES(taxonomy.ts 81행, 21+41=62값)라는 다른 enum이고, 여기에는 Unclassified가 없다. 실제로 validateVisualSummaryYaml에 type: Unclassified를 넣어 돌려 보면 스키마 검증에서 그대로 걸린다(components[0].type이 62값 enum이 아니라며 reject, 직접 실행해 확인). 색인 쪽 Unclassified를 질의 YAML의 type으로 그대로 재사용할 수는 없다는 뜻이다. 6.6절 (3)이 이 문제를 실제로 겪는 자리다.

여기서 짚어야 할 것. Topological Relation은 "이게 이종이다"를 관찰하고 분류하기 위한 라벨이지, "이종을 만나면 이렇게 대치한다"는 처리 기법이 아니다. 이 구분이 다음 두 절의 핵심이다.

6.2 지금 정의된 기법은 평탄화(구조 강등) 하나뿐이다#

entity.components[]를 못 읽는 컴포넌트를 만났을 때 무엇을 하는지는 규약이 명시적으로 정해 뒀다.

중첩을 못 읽는 컴포넌트는 자식의 entities를 부모 items로 편다 (schema-v0.5.md 1절 "중첩은 평탄화된다", 8.7절 예시)

8.7절의 worked example을 그대로 옮기면, List 안에 분기마다 BigNumber({ label: 매출, value: 12, unit: 억원 })가 중첩된 입력을 평탄화하면 1분기 항목의 items가 [신규 계약 12건, 매출: 12억원]이 된다. BigNumber라는 정체성(큰 숫자로 강조해서 그리는 표현)은 사라지고, 값이 평범한 텍스트 한 줄로 내려간다. 이것이 사용자가 말한 "component 하위의 컴포넌트였지만 structure로 강등해서 내용을 대치하는 경우"에 정확히 해당한다. 이 기법은 이미 개념이 정의돼 있고, 구조 투영 코드(projection.ts의 P3, flattenChildren/moveUnreadContent)가 fallback 그래프 안에서 같은 패턴(children을 items로 접는 것)을 실제로 구현하고 있다. 다만 entity.components[] 자체를 평탄화하는 코드는 아직 없다. 생성기가 그 필드를 채운 적이 없어서 소비할 입력 자체가 나온 적이 없다(6.4절).

6.3 검색은 이미 있다(T-5). 없는 것은 오케스트레이션이다#

정정(2026-08-23 리뷰). 초판은 "병치로 떼어내는 기법은 어디에도 없다"고 적었는데, 검색 쪽은 이미 있고 테스트도 있다. nodeSearch.ts의 T-5(node-tag-and-property-poc-blueprint.md 6절, "노드 태그를 쓰는 검색")가 정확히 이 문제를 푼다. 컴포넌트 전체가 아니라 경계 노드 하나하나를 색인 단위로 삼아서(buildNodeIndex), 뿌리가 이종(Unclassified)이라도 그 안의 서로 다른 카테고리 서브트리(예: Enumeration_List, KPIKeyMetric_BigNumber)를 각각 독립적으로 찾아낸다. 실제 production 사례(design_object_id 4757227)는 Comparison Table, Hierarchical Tree, List, Highlighted Message 넷이 한 컴포넌트에 함께 붙은 경우이고, nodeSearch.test.ts가 List와 BigNumber 둘로 최소 재현해 "같은 컴포넌트인데 질의에 따라 다른 노드가 뽑힌다"를 검증한다(같은 파일 181~203행, "T-5 완료 판정"). 뿌리(Unclassified) 자신은 이 검색에서 후보로 절대 안 걸린다는 것도 같은 파일 205행이 확인한다. D-7의 "이종중첩 컴포넌트를 후보로 안 보여준다"는 결정과 정확히 들어맞는 동작이다.

다만 T-5가 푸는 것은 "이미 독립적으로 던져진 단일 카테고리 질의를, 이종 컴포넌트 안에서도 찾아낸다"는 것뿐이다. 아직 없는 것은 그 앞뒤 단계다. 질의 쪽 entity.components[]가 병치를 담고 있을 때 그것을 "쪼개야 한다"고 인식하고, 쪼갠 조각마다 T-5를 호출하고, 결과를 다시 원래 구조로 조립하는 코드는 저장소 어디에도 없다. 그리고 T-5 자체도 아직 메인 파이프라인(service.ts)에 얹히지 않았다. 지금은 검수 화면의 독립 탭("노드 태그 검색")과 CLI 스모크 스크립트(scripts/iis-node-search-poc.ts)로만 존재한다(node-tag-and-property-poc-blueprint.md 873행이 이를 "범위 밖"으로 명시).

content-binding-contract.md 3.4절이 이 미결정 상태를 이미 정확히 짚어 뒀다.

질의 쪽 containment를 단일 type으로 flatten한 뒤 fallback하는지, entity.components[]를 유지한 채 node 태그(I.I.S.)로 대치 범위를 자르는지는 아직 한 문서에 못박지 않았다.

2026-08-28 갱신 이 인용은 당시 상태이고 그 뒤 닫혔다. 유지하는 쪽으로 정했고 순서는 ../spec/capability-table.md 3.6절의 N1~N5 다. 아래 서술 중 이 항목이 열려 있다고 전제한 부분은 그만큼 낡았다.

6.4 지금 당장 문제가 되지 않는 이유#

두 가지가 이 문제를 아직 "급하지 않게" 만들고 있다.

  1. 생성기 프롬프트가 entity.components[]를 아예 못 쓰게 못박아 뒀다. generator/prompt.ts 23행이 "id, decoration, entity.id, entity.components 는 절대 쓰지 않는다 (기계가 채우거나 예약된 자리다)"라고 명시한다. 이건 "이종중첩 입력이 나온 적이 없어서 못 봤다"는 관찰이 아니라, 입력이 무엇이든 LLM이 이 필드를 못 쓰게 시스템 프롬프트가 항상 막는다는 뜻이다. entity.components를 프로그램이 대신 채우는 코드도 저장소에 없다(대입 코드를 찾은 결과 없음). 대치 쪽 코드(assignIds.ts 71~105행)는 이 필드가 들어올 경우를 대비해 재귀 순회를 이미 갖추고 있지만, 생성 쪽이 절대 채우지 않으니 실제 입력을 받아 본 적이 없다.
  2. 검색 풀에서 기존 이종중첩 템플릿을 이미 뺐다. TODO.md D-7이 이종중첩 컴포넌트를 검색 후보에서 제외하기로 확정했고, 그 근거가 된 2026-08-13 수량 산출 33절은 "제외로 잃는 양이 신뢰구간 상한(1.96%)이어도 대안을 안 꺼내도 결정이 성립한다"고 결론지었다.

이 두 결정은 서로 다른 질문에 답한다는 점이 중요하다. D-7은 "색인에 이미 있는 이종 템플릿을 후보로 보여줄까"에 대한 답(안 보여준다)이고, 6.2~6.3절이 다루는 것은 "사용자 입력 자체가 이종 구조를 요구하면 무엇으로 그릴까"라는, 아직 아무도 답하지 않은 질문이다. entity.components[]가 켜지는 순간 이 질문이 그대로 살아난다.

6.5 내용 유실 없이 표현을 완수하는 기법 전체 목록#

지금 저장소에 있는 "내용을 잃지 않고 옮긴다"는 기법을 전부 모으면 여섯이다. 이종 컴포넌트 문제에 직접 관련된 것은 둘(중첩 평탄화, T-5 노드 태그 검색)이고, 나머지 넷은 다른 문제를 푼다.

기법 무엇을 푸는가 이종 컴포넌트 문제를 푸는가
structure projection (P1~P10) — capability-table.md 3.4절, projection.ts 같은 컴포넌트가 카테고리 fallback으로 강등될 때 구조(entities, matrix, relations)를 재배치. 내용은 안 버리고 표현(params)만 버린다(P2) 아니다. 이건 "한 컴포넌트가 다른 카테고리로 바뀔 때"의 문제이지 "한 컴포넌트 안에 여러 카테고리가 섞였을 때"의 문제가 아니다
중첩 평탄화 — schema-v0.5.md 1절, 8.7절 entity.components[]를 못 읽는 컴포넌트가 자식의 내용을 부모 items로 편다 그렇다. 다만 6.4절대로 아직 실전에서 쓰인 적이 없다
T-5 노드 태그 검색 — nodeSearch.ts, node-tag-and-property-poc-blueprint.md 6절 컴포넌트 전체가 아니라 경계 노드 단위로 색인해서, 뿌리가 이종이어도 그 안의 단일 카테고리 서브트리를 찾는다 부분적으로 그렇다(6.3절 정정). 이종 컴포넌트 안에서 원하는 조각을 찾는 검색 자체는 이미 되지만, 메인 파이프라인에는 아직 안 얹혔고 질의 쪽에서 병치를 쪼개는 오케스트레이션도 없다
comp-sign 변형 (최대 100개: 키:밸류 분리, 제목 제외, 자식 수 확장, 자식 병합) — node-tag-and-property-poc-blueprint.md 2.3절 개수(용량) 불일치. 항목이 슬롯보다 많거나 적을 때 아니다. 이건 같은 카테고리 안에서 항목 수를 맞추는 기법이다
D-7 색인 풀 제외 — TODO.md D-7 이종중첩 기존 템플릿을 검색 풀에서 미리 뺀다 회피이지 해법이 아니다. 이종 구조를 만나지 않게 만드는 것이지, 만났을 때 어떻게 할지는 답하지 않는다
property policy (exclude/reorder/ignore) — propertyMatch.ts 같은 카테고리 안에서 질의와 후보의 속성값이 어긋날 때 아니다. 카테고리가 하나로 같다는 전제 위에서 동작한다

"병치로 떼어내기"라는 오케스트레이션은 이 여섯 중 어디에도 없다. 다만 그 핵심 재료인 검색(T-5)은 이미 있으므로, 완전히 새로 만들 기법이 아니라 T-5 위에 얹을 오케스트레이션 층 하나로 좁혀진다. 이 기법은 포함위상이 실패했을 때 쓰는 대안이다. 자식을 부모 안에 넣으려 했는데(예: List 안에 BigNumber) 그 구조를 그대로 소화하는 후보가 없으면, 자식을 부모에서 분리해 최상위 병치 컴포넌트로 승격시킨다. 6.2절의 평탄화도 같은 상황(포함위상 실패)의 대안이지만, 평탄화는 자식을 텍스트 한 줄로 강등시키고 떼어내기는 자식의 컴포넌트 정체성을 그대로 유지한다는 차이가 있다.

병치위상 자체가 실패했을 때는 또 다른 갈래가 필요하다(2026-08-23 리뷰 지적). 병치위상은 처음부터 부모자식 관계가 아니라 나란히 놓인 대등한 관계이므로, 평탄화가 접어 넣을 부모 items 자리 자체가 없다. 맞는 후보를 못 찾으면 각 하위 컴포넌트를 처음부터 독립적으로 병렬 검색해서 따로 대치해야 하는데, 이 개별 검색 자체는 T-5가 그대로 재사용된다. 없는 것은 "언제 이 갈래를 타야 하는지 인식하는 판단"과 "결과를 다시 조립하는 코드"뿐이다.

포함위상이 실패했을 때, 자식 쪽 내용 자체가 이미 이종으로 병치돼 있으면 떼어내기가 한 단계 더 갈라진다(2026-08-23 리뷰에서 나온 세분화, 아직 미확정인 제안이다). 예를 들어 부모 Enumeration.List가 서로 다른 두 항목(entity)을 갖고, 한 항목은 KPIKeyMetric.BigNumber를 하나 매달고 다른 항목은 또 다른 Enumeration.List를 하나 매단 경우다(8.7절 전례대로 항목마다 components는 정확히 1개, 6.6절 (3) 참고). TODO.md D-7 근거가 예시로 든 "리스트 안에 리스트와 차트가 동시에 포함"과 같은 모양이다. 이때 검색은 세 단계로 갈릴 수 있다.

  1. 먼저 이 구조 그대로(부모와 병치된 자식 둘)를 통째로 담은 기존 조합 컴포넌트(같은 조합을 이미 갖고 있는 실제 색인 템플릿)를 찾는다. 다만 D-7이 정확히 이런 이종중첩 템플릿을 검색 풀에서 뺐으므로(TODO.md D-7, 2026-08-11 체크인), 경계 태깅 임계값을 넘겨 진짜로 이종 표식이 붙은 템플릿은 이 방법으로 찾히지 않는다(nodeSearch.test.ts 205행, "Unclassified 뿌리는 어느 질의에도 후보로 안 걸린다"). 풀에 남아 있는 것은 태깅 임계값 미달이라 단일 카테고리(List)로만 표식된, 겉보기엔 비슷해도 이종으로 분류되지 않은 템플릿뿐이다.
  2. 그래도 못 찾으면, 부모에 남는 고유 내용(residue)이 있는지로 갈린다. 부모 List가 이 두 자식을 담는 것 말고도 자기 항목을 하나라도 갖고 있으면, 부모는 그대로 두고 자식 둘만 최상위 컴포넌트로 떼어낸다(6.5절 떼어내기와 같은 모양이고, 자식이 하나가 아니라 둘이라는 점만 다르다). 부모가 이 두 자식을 담는 것 외에 자기 내용이 전혀 없으면(껍데기만 있고 항목 자체가 이 둘이 전부라면), 부모를 버리고 자식 둘을 완전히 독립된 최상위 컴포넌트로 검색한다. 이 완전 분해 결과는 8.6절 "다중 컴포넌트"의 최종 YAML 모양과 같아진다.
  3. 떼어낸 자식마다 검색은 T-5(rankNodeCandidates)를 그대로 쓴다. 자식이 원래 어느 design object 안에 이종으로 박혀 있었든, T-5는 경계 노드 단위로 색인하므로 그 자식의 서브트리를 독립적으로 찾아낸다(6.3절). capability-table.md의 fallback 배열은 언제나 단일 카테고리 ID 하나만 담고(예: 'KPIKeyMetric_BigNumber', 'Enumeration_List', 같은 문서 456행, 463행, 470행), List.BigNumber처럼 두 카테고리를 이어 붙인 결합 값은 어휘에 없으므로 새로 만들 것도 없다.

검색(T-5)은 이미 있고 테스트도 있다. 아직 없는 것은 이 문단이 제안하는 판단 규칙(부모에 residue가 남는가로 완전 분해/부분 분해를 가른다)과, 그 규칙에 따라 실제로 쪼개고 T-5를 호출하고 결과를 다시 조립하는 오케스트레이션 코드뿐이다. 확인할 것은 최소 둘이다. 경계 태깅 임계값 미달로 풀에 남아 있는 "겉보기 이종" 템플릿이 실제로 몇 건이나 있는지, 그리고 residue 판정 기준(부모 entities/items가 비었는지 여부)을 무엇으로 셀지다.

6.6 예시로 보는 네 흐름#

같은 도메인 예시 하나를 넷으로 나눠, 6.1~6.5절의 개념을 실제 YAML로 보여준다. 앞 둘은 포함위상이 실패했을 때의 두 대안(평탄화 대 떼어내기)이고, 뒤 둘은 병치위상 입력과 그 실패 시 대안(병렬 독립 검색)이다. 넷 다 지금은 생성되지 않는 dummy 값이다. 6.4절대로 생성기가 entity.components[]를 절대 안 쓰므로, 아래 입력 YAML은 "만약 켜진다면"을 가정한 것이다.

(1) 포함위상 입력. 정본은 schema-v0.5.md 8.7절이고, 여기서는 그대로 옮긴다.

yaml
version: "0.5.3"
title: 분기별 사업 현황
components:
  - type: Enumeration.List
    structure:
      entities:
        - label: 1분기
          items: [신규 계약 12건]
          components:
            - type: KPIKeyMetric.BigNumber
              structure:
                entities:
                  - { label: 매출, value: 12, unit: 억원 }
        - label: 2분기
          items: [신규 계약 15건]
          components:
            - type: KPIKeyMetric.BigNumber
              structure:
                entities:
                  - { label: 매출, value: 15, unit: 억원 }

Enumeration.List(부모)의 1분기 항목 영역 안에 KPIKeyMetric.BigNumber(자식)의 영역이 완전히 들어가 있는 모양이다. 이게 포함위상이다.

(2-a) 평탄화로 대치했을 때 (6.2절, 유일하게 정의된 기법).

yaml
version: "0.5.3"
title: 분기별 사업 현황
components:
  - type: Enumeration.List
    structure:
      entities:
        - { label: 1분기, items: [신규 계약 12건, 매출 12억원] }
        - { label: 2분기, items: [신규 계약 15건, 매출 15억원] }

매출 12억원이 큰 숫자로 강조되는 BigNumber의 정체성을 잃고, 신규 계약 12건과 똑같은 글자 한 줄이 됐다. 이게 6절 서두에서 말한 "구조는 안 잃지만 표현은 잃는" 강등이다.

(2-b) 떼어내기로 대치했을 때 (6.3절, 아직 저장소에 없는 기법).

yaml
version: "0.5.3"
title: 분기별 사업 현황
components:
  - type: Enumeration.List
    structure:
      entities:
        - { label: 1분기, items: [신규 계약 12건] }
        - { label: 2분기, items: [신규 계약 15건] }
  - type: KPIKeyMetric.BigNumber
    structure:
      entities:
        - { label: 1분기 매출, value: 12, unit: 억원 }
        - { label: 2분기 매출, value: 15, unit: 억원 }

List는 그대로 두고, 분기마다 안에 있던 BigNumber 둘을 부모에서 떼어내 최상위 components[]의 새 항목 하나로 승격시켰다. BigNumber의 강조 표현은 그대로 살지만, "이 매출이 1분기 항목 바로 옆에 붙어 있다"는 원래의 공간적 연결은 사라진다. 평탄화와 떼어내기 중 어느 쪽이 내용을 더 잘 보존하는지는 사례마다 다를 수 있다.

(3) 병치위상 입력. 정원석이 지적한 구조를 그대로 따르면 표기는 이미 정해져 있다. 새 필드나 wrapper가 필요 없다. 병치에는 자연스러운 형태가 둘이다.

뿌리가 없는 병치(예: BigNumber와 List가 다른 무엇에도 속하지 않고 나란히 있는 경우)는 문서/페이지 최상단의 components: [component, component, ...], 곧 8.6절 "다중 컴포넌트"다. 이미 결정돼 있고 생성기가 이미 쓴다(generator/prompt.ts 57행). 별도의 "병치위상 입력 표기"가 따로 있는 게 아니라, 뿌리 없는 병치는 처음부터 다중 컴포넌트 그 자체다 ((4)의 첫 YAML과 같다).

yaml
version: "0.5.3"
title: 이번 분기 하이라이트
components:
  - type: KPIKeyMetric.BigNumber
    structure:
      entities:
        - { label: 총매출, value: 152, unit: 억원 }
  - type: Enumeration.List
    structure:
      entities:
        - { label: 신규 고객 320건 }
        - { label: 재구매율 68% }
        - { label: 해외 매출 비중 22% }

뿌리가 있는 병치(예: Enumeration.List의 항목마다 서로 다른 type이 붙는 경우)는 entity.components[]를 쓰되, 8.7절 원본 전례 그대로 항목(entity)마다 하나씩 매단다. entities[]가 이미 배열이라 항목을 늘리는 데 새 필드가 필요 없다. 이전 판이 시도한 "entity 하나에 서로 다른 type 둘을 몰아넣기"는 8.7절에 전례가 없었다.

yaml
version: "0.5.3"
title: 분기별 핵심 지표
components:
  - type: Enumeration.List
    structure:
      entities:
        - label: 총매출
          components:
            - type: KPIKeyMetric.BigNumber
              structure:
                entities:
                  - { label: 총매출, value: 152, unit: 억원 }
        - label: 핵심 성과
          components:
            - type: Enumeration.List
              structure:
                entities:
                  - { label: 신규 고객 320건 }
                  - { label: 재구매율 68% }
                  - { label: 해외 매출 비중 22% }

entities[0]과 entities[1]이 List의 서로 다른 두 항목이고, 각 항목은 8.7절과 똑같이 자기 components를 정확히 1개씩만 갖는다.

남는 질문은 표기가 아니라 다른 층에 있다. 하나는 색인 태깅 쪽에는 이종 병치 뿌리를 가리키는 이름(Unclassified + topologicalRelation: Juxtaposition, 6.1절)이 이미 있는데 질의 스키마의 type enum에는 대응하는 값이 없다는 것이다(7절 항목 9). 다른 하나는 리뷰에서 나온 더 근본적인 질문으로, 구조 값이 들어갈 자리(label, items 등)에 항상 component가 대신 들어갈 수 있어야 하는가다(7절 항목 11). 지금 스키마는 그렇게 안 돼 있다. entitySchema(schema.ts 84105행)에서 label은 항상 z.string().min(1)이고 items는 항상 z.array(z.string())이다. 값이 들어갈 자리에 component를 직접 넣는 것은 지금 스키마의 어떤 필드로도 안 되고(직접 items에 component 객체를 넣어 validateVisualSummaryYaml을 돌려 확인함, expected string, received object로 거부), 유일한 중첩 통로는 components:라는 별도 배열 필드다(8.7절, assignIds.ts 71행과 9899행이 이 배열을 순회하는 것으로 확인).

(4) 뿌리가 있는 병치가 실패했을 때, 병렬 독립 검색으로 대치한 결과 (T-5 검색은 이미 있음, 오케스트레이션은 아직 없음, 6.3절과 6.5절 참고). (3)의 두 번째 YAML(List 항목마다 BigNumber/List가 하나씩 붙은 것)과 정확히 같은 구조를 담은 후보가 색인에 없을 때다. 6.5절 residue 판정대로, 이 List가 두 항목 말고 자기 고유 항목이 없다면(위 예시가 그 경우다) List 자체를 버리고 두 항목의 components를 각각 최상위로 떼어낸다.

yaml
version: "0.5.3"
title: 분기별 핵심 지표
components:
  - type: KPIKeyMetric.BigNumber
    structure:
      entities:
        - { label: 총매출, value: 152, unit: 억원 }
  - type: Enumeration.List
    structure:
      entities:
        - { label: 신규 고객 320건 }
        - { label: 재구매율 68% }
        - { label: 해외 매출 비중 22% }

BigNumber와 List를 각자 독립된 최상위 컴포넌트로 T-5가 따로 찾아 대치한 결과다. 뿌리 없는 병치((3)의 첫 번째 YAML)와 최종 모양이 똑같다는 점이 눈에 띈다. 계기만 다르다. 뿌리 없는 병치는 처음부터 이 모양이 정답이었고, 뿌리가 있던 병치는 "묶어서 찾았지만 실패해서" 이 모양으로 물러난 것이다.

또한 이 결과는 schema-v0.5.md 8.6절 "다중 컴포넌트"(generator/prompt.ts 57행이 생성 시점에 지시하는 것)와 구조적으로 같다. 차이는 시점이다. 8.6절/57행은 생성 시점에 처음부터 이렇게 나누라는 지시이고, (4)는 뿌리가 있던 List를 대치 시점에 검색이 못 찾아서 나중에 쪼갠 것이다.

요약

위상 정상 대치 실패 시 대안 결과 YAML
포함(Containment) 맞는 후보를 찾아 entity.components[] 그대로 대치 평탄화(6.2절, 있음) 또는 떼어내기(오케스트레이션 없음, 검색은 T-5 재사용, 6.3절) (2-a) 또는 (2-b)
병치, 뿌리 없음 처음부터 8.6절 다중 컴포넌트 해당 없음, 이미 최종 형태 (3) 첫 번째 YAML
병치, 뿌리 있음 entities[] 항목마다 entity.components[] 1개씩 대치 병렬 독립 검색(검색은 T-5로 있음, 오케스트레이션 없음, 6.3·6.5절) (4)

7. 미결 확인 사항#

  1. 이종 컴포넌트 대치 기법 자체를 지금 설계할지, entity.components[]가 켜질 때까지 미룰지. 6.4절대로 아직 실전에 나온 적이 없어 급하지 않다는 근거가 있지만, content-binding-contract.md 3.4절은 이미 "못박지 않았다"고 열린 문제로 적어 뒀다. 지금 개념만 정의해 둘지, 실제 설계는 생성기가 entity.components[]를 채우기 시작할 때로 미룰지.
  2. "병치로 떼어내기"가 실제로 필요한 전략인지. 6.1절 실측(이종중첩 11건 전부가 List를 한쪽에 낀 Juxtaposition)을 보면, 평탄화 하나만으로 감당이 안 되는 사례가 실제로 있는지, 아니면 D-7의 판단대로 "대부분 조합 Fallback(=평탄화)으로 해결"될 것으로 보고 병치 추출은 아예 만들지 않을지. 6.3절 정정대로 검색(T-5)은 이미 있으므로, 이 질문은 "기법을 새로 만들지"가 아니라 "T-5 위에 오케스트레이션을 지금 얹을지"로 좁혀졌다. 6.5절 말미의 세분화(조합 컴포넌트 탐색, 부모에 남는 residue 기준의 완전 분해/부분 분해)도 같은 미결정 상태다.
  3. entity.components[] 평탄화를 fallback의 P3와 같은 코드로 묶을지, 별도로 둘지. 두 규칙이 "자식을 items로 접는다"는 같은 모양이라, 하나의 함수로 합치는 편이 나은지 실물을 보고 정해야 한다.
  4. property projection(같은 카테고리 안 대조)과 이종 컴포넌트 구조 대치가 TODO.md I-7 하나로 같이 묶여 있는데, 분리할지. propertyMatch.ts(같은 카테고리 안 대조)는 이미 완료됐고, 이종 컴포넌트 구조 대치(6절)는 설계도 시작 전이다. 하나의 작업 항목에 완료된 것과 시작 전인 것이 섞여 있으면 "I-7 진행 중"이라는 말이 무엇을 가리키는지 알 수 없다.
  5. StackedBarChart.normalize를 어떻게 좁힐지. 3절에서 다룬 것과 같은 항목이다. 스펙대로 false 고정값으로 검사를 바꿀지, 렌더러가 지원할 때까지 어휘에서 뺄지.
  6. enum 값 목록 14건을 문서에 역반영할지. 3절 참고. axis(text/boolean 재해석)와 order(boolean으로 좁힌 것)부터 정원석의 확인이 필요하다.
  7. PageNavigation_* 4종의 C-4 결정이 지금 어디까지 왔는지. 이 문서 작성 시점 기준 스펙 문서상으로는 여전히 "진행 중"이다.
  8. (2026-08-23 갱신) "위상별로 별도 모드를 설계해야 한다"는 리뷰 초반의 전제가 6.6절 정리로 줄었다. 초판은 포함위상과 병치위상 각각에 별도 모드가 필요하다고 봤다. 6.6절에서 정리한 대로 뿌리 없는 병치는 이미 8.6절 다중 컴포넌트 그 자체이고(새 모드 불필요, 생성기가 이미 씀), 뿌리 있는 병치는 entities[]를 늘려 항목마다 entity.components[] 1개씩 붙이는 것으로 충분하다(항목 10). 그러니 실제로 남는 결정은 "entity.components[](entity당 정확히 1개)를 생성기가 채우도록 허용할지"라는 단일 스위치이지, 위상별 모드 셋이 아니다. 켰을 때 포함위상은 평탄화(있음, 6.2절) 또는 떼어내기(오케스트레이션 없음, 검색은 T-5, 6.3절)로, 병치와 뿌리 있음은 병렬 독립 검색(오케스트레이션 없음, 검색은 T-5, 6.3절과 6.5절)으로 물러난다는 것이 지금까지 정리된 전부다. 생성기 지시(generator/prompt.ts 57행, "성격이 다른 덩어리가 여럿이면 컴포넌트를 여러 개로 나눈다")는 이것과 다른 층이다. 이 지시는 생성 시점에 최상위 document.components[]를 나누는 것이고, 여기서 다루는 것은 entity.components[]로 이미 하나로 묶어 생성한 것을 대치 시점에 못 찾았을 때 무엇으로 대신할지의 문제라, 최상위 분리로 대신 풀리지 않는다.
  9. Unclassified를 질의 스키마의 SEMANTIC_CATEGORIES(62값)에도 추가할지. 색인 태깅 쪽에는 이종 병치 뿌리를 가리키는 이름이 이미 있는데(Unclassified, D-11), 질의 쪽 type enum에는 대응하는 값이 없다(6.1절, 6.6절 (3)). 바깥 component의 type을 생략하는 것으로 당장은 스키마를 통과시킬 수 있지만(실측 확인함), 검색이 "type 없는 component"를 어떻게 다룰지는 코드에 없다.
  10. (해결, 2026-08-23) entity.components[]는 entity 하나에 항상 1개만 붙이고, 병치되는 여러 컴포넌트는 entities[]를 늘려 항목마다 하나씩 붙인다. 6.6절 (3) 참고. entity.components[]에 서로 다른 카테고리 여러 개를 몰아넣는 것은 8.7절 전례에 없고, entities[]가 이미 배열이라 새 필드 없이 항목을 늘리는 것만으로 충분하다.
  11. (해결, 2026-08-23) 구조 값이 들어갈 자리(label, items 등)에 component가 직접 들어갈 수 있어야 하는가. 리뷰 중 제안됐다가 리뷰 안에서 스스로 기각됐다. 처음 제안은 entities[]의 원소 자체가 Entity(자기 내용을 가진 행) 아니면 Component(다른 카테고리로 통째로 렌더링되는 행) 중 하나일 수 있는 union이었다. 이러면 라벨 없이 조각을 병치할 수 있어 좋아 보였지만, 부모 카테고리의 의미를 깨뜨린다는 반론으로 기각됐다. Enumeration.List의 성립 조건은 "entities 1개 이상"뿐이지만(capability.ts Enumeration_List), List라는 카테고리 자체의 뜻은 "이름 붙은 것들을 나열한다"는 것이다. entities[]의 한 자리를 label 없는 Component로 바꾸면 그 자리는 "이 리스트 안에서 무엇이라 불리는 항목인지"를 잃고, List가 스스로 정의하는 "나열되는 것마다 이름이 있다"는 계약이 깨진다. 그래서 지금 스키마(entity가 항상 label을 갖고, 그 밑에 components로 실제 렌더링을 위임하는 것, 항목 10)가 코드 제약이 아니라 의미상으로도 맞는 구조다. label은 "이 항목이 무엇에 관한 것인가"(List의 나열 계약)를 책임지고, components는 "그것을 실제로 어떻게 그릴 것인가"를 책임지는 것으로 역할이 분리된다. 값 자리에 component를 직접 넣는 union 타입 자체는 여전히 지금 schema.ts에 없지만(직접 실행해 확인: items에 component 객체를 넣으면 거부됨), 이제는 "만들어야 하는데 아직 안 만든 기능"이 아니라 "만들면 안 되는 이유가 있는 설계"로 결론이 난다.

참고#

↑ 목차로 · 5부 머리로

27장component.type fallback map v2 정본MOR-1909 · 49행 전체

원문 plan/fallback-map-review-2026-08-13.md GitHub 에서 보기 23 KB 원문 제목 「component.type fallback map v2 (MOR-1909)」

이 문서의 역할 MOR-1909 fallback map 초안(v1, 김수현·허재영 작성, Google Sheet)을 정원석이 검토해 만든 v2와 그 근거를 기록한다. ../spec/capability-table.md 3절, 4절의 개정안이다. 2026-08-17 2차 반영으로 정원석이 capability-table.md에 대부분 직접 반영했다 — 김수현·류민 전원 확인을 기다리지 않고 진행했다(정원석 판단). 이 문서는 그 반영의 상세 근거 기록으로 남는다. 오너 정원석 (MORDOR) · 상태 CONFIRMED (기본 확정, 2026-09-05) — capability-table.md에 2026-08-17 3차 반영 완료(45종, 엣지 128개, 이후 B-27/B-30 등으로 130개까지 갱신). 2026-08-21에 5절 ComboChart 조건부 규칙이 폐기돼 엣지 131개가 됐다(5절 상자). 원저자 확인 대기 항목은 전부 소진됐다: Funnel(6.2절)은 사용자가 최종 순서를 확정, 나머지는 원칙 반영으로 종료. v7 우선순위 동기화(6.6절)도 완료. 김수현·류민 전원 확인은 기다리지 않고 진행한다(2026-09-05 정원석) — 문제가 생기면 그때 개별 수정한다. · 최종 갱신 2026-09-05 관련 MOR-1909 · fallback map v1 시트 · ../spec/capability-table.md 3절, 4절 · ../reference/semantic-taxonomy-v7.md 단어장 정의 · 허재영 1차 확인 Slack

이 문서 훑기

절 무엇에 답하나
1 이번 리뷰에서 정한 원칙 넷
2 v1을 v2로 고친 기준. 안전망을 왜, 어떤 것으로 붙이는가
3 49종 전체의 정의와 개수 조건
4 v1과 v2를 나란히 둔 49행 전체, 행마다 근거
5 ComboChart만 다른 형태인 이유
6 원저자 확인이 필요한 곳, 열려 있는 질문
7 아직 안 정한 것

1. 원칙 넷#

정원석이 2026-08-13 리뷰에서 정했다.

  1. List 금지 원칙을 채택하지 않는다. 류민이 2026-08-05 계통도 분석에서 제안한 "페이지역할, 단일메시지, 인물, KPI 11종은 List 폴백 금지"는 참고 자료로 남기고 규칙화하지 않는다. 대안 없는 금지는 쓰지 않는다는 것이 근거다. 콘텐츠가 유실되거나 대치 자체가 안 되는 것이, List로 대치되는 것보다 나쁘다고 판단했다.
  2. 우선순위는 근접도로 정한다. 의미가 가까운 유형을 먼저 둔다. 의미의 근거는 v7 단어장 정의문이 공유하는 핵심 개념이다(3절). 대분류 일치는 근접도의 강한 신호지만 배제 조건은 아니다. Funnel과 Steps는 대분류(Conversion, ProcessFlow)가 다르지만 "단계"라는 핵심 개념을 공유해 가깝다고 본다. 반대로 개수나 축 형태가 닮았다는 구조적 이유만으로는 우선순위를 정하지 않는다.
  3. 모든 행이 반드시 무언가로 끝난다. v1에서 완전히 공란이었던 6행(Cycle, FlowChart, Opening, SectionDivider, Ending, Flow)을 채운다. 서로만 가리켜서 상대가 없으면 둘 다 막히는 상호참조 4쌍(BigNumber↔Gauge, Pyramid↔NestedScope, Calendar↔GanttChart, Location↔Highlight)도 각자 안전망을 붙여 해소한다. 페이지역할 4종(Opening, TableOfContents, SectionDivider, Ending)도 예외를 두지 않는다.
  4. 대체 후보 수 상한을 폐지한다. capability-table.md 4절의 "최대 두 칸" 규칙은 없앤다. 근접도 순서만 지키면 몇 칸이든 허용한다.

바꾸지 않는 것 하나. capability-table.md 3.0.1절의 그래프 정의는 유지한다. 각 행은 그 유형 하나의 후속 우선순위 목록이고, 검색 모듈은 자기 행만 왼쪽부터 따라가며 도착 유형의 행을 이어 붙이지 않는다. 상호참조 쌍(원칙 3)이 순환하지 않는 근거가 이 정의다.

2. 안전망을 어떻게 정하나#

2.1 사슬이 막히는 두 가지 이유#

fallback은 그 유형의 후보가 없을 때 발동한다. 사슬의 어느 후보든 두 가지 이유로 막힐 수 있다.

이유 무엇인가 판정 근거
구조 조건 미충족 그 유형의 성립 조건(개수, matrix, 특정 값)을 콘텐츠가 못 채운다 capability-table.md 2절
색인 공급 없음 성립 조건은 채워도 그 유형으로 태깅된 컴포넌트가 색인에 없거나 얕다 유형별 색인 분포 실측이 필요하다. 이 문서는 실측을 갖고 있지 않다. semantic-taxonomy-v7.md 3절의 빈도는 질의(수요) 분포라 공급 근거가 아니다

ListingTable(행 2 이상), ComparisonTable(열 2 이상), Versus(정확히 2), Steps(2 이상), Pyramid(3 이상) 같은 유형은 첫 번째 이유로 막힐 수 있고, 4절 표의 행별 이유에 적은 개수 조건이 이것이다. 두 번째 이유는 어느 후보에나 걸릴 수 있어 행별로 따지지 않는다. 대신 모든 사슬의 끝을 항상 성립하는 종단(2.2절)으로 맺어 대비한다.

2.2 항상 성립하는 종단 셋과 대표값 표기#

capability-table.md 3.0절이 이미 갈라 둔 것이 있다. 성립 조건이 개수 조건 하나뿐이라 콘텐츠 모양에 관계없이 셋 중 하나는 항상 성립하는 유형이다.

유형 개수 조건 언제 고르나 (3.0절 선택 규칙)
List 1 이상 entities 2개 이상일 때 기본값
LongFormTextLayout 1 이상 entities 1개이고 본문이 여러 문장일 때
HighlightedMessage 정확히 1 entities 1개이고 한 문장으로 끝날 때

4절 표의 종단 표기는 대표값이다. capability-table 3.0절이 쓰는 방식 그대로다. 그 절은 표의 마지막 자리를 List로 적어 두고 "실제 마지막 fallback은 셋이고 그중 하나를 내용 모양이 고른다"고 정했다. 이 문서도 같다. 4절 표가 마지막 자리에 List나 HighlightedMessage를 적은 것은 그 유형의 전형적인 내용 모양에서 선택되는 값이고, 실제 최종 선택은 위 선택 규칙이 한다. BigNumber(대표값 HighlightedMessage)도 entities가 여럿이면 같은 규칙으로 List가 된다. 이 규칙이 없으면 HighlightedMessage(정확히 1)로 끝나는 사슬이 entities 2개 이상에서 막힌다.

대표값을 고르는 기준은 원래 유형의 전형적인 콘텐츠 모양이다. BigNumber, Gauge처럼 v7 정의가 "가장 중요한 수치를 크게 강조"인 유형은 entities가 보통 1개인 강조형이라, 나열형인 List보다 강조형인 HighlightedMessage를 대표값으로 적었다.

2.3 안전망이 뒤에 있다고 앞의 후보가 나쁘다는 뜻이 아니다#

4절 표에서 사슬 끝에 안전망을 붙인 행이 많다. 이건 앞선 후보가 부적절해서가 아니다. 앞선 후보 대부분은 실제로 거의 항상 성공한다. 안전망은 2.1절의 두 이유(구조 조건, 색인 공급) 중 하나가 드물게 발생하는 극단적인 경우에 대비한 마지막 장치다. 예를 들어 RadarChart가 ComparisonTable로 물러나는 사슬은, RadarChart 자체가 축 3개 이상을 요구해 ComparisonTable의 열 2개 이상 조건을 사실상 항상 채우므로 ComparisonTable에서 대부분 끝난다.

2.4 v2는 v1이 가진 후보를 지우지 않는다#

근접도(원칙 2)로 v1이 이미 고른 후보를, 구조적 안전성 때문에 빼는 것은 원칙에 어긋난다. v2가 사슬을 고친 행은 후보를 지운 게 아니라 뒤에 안전망을 덧붙인 것이다. 지운 행은 없다. 후보를 앞에 추가한 행이 하나 있다(Funnel, 6.2절).

예외가 여섯 있다. v1이 이미 항상 성립하는 유형(List, HighlightedMessage, LongFormTextLayout)을 사슬 끝이 아닌 자리에 둔 행이다. 예를 들어 List 행은 v1에서 ListingTable로 이어지는데, List는 entities 1개 이상이면 그 자체로 항상 성립해 이 사슬은 원래 발동할 일이 없다. v1을 쓴 사람이 이렇게 적어 둔 이유를 정원석은 모른다. v2는 지우거나 고치지 않고 v1 값을 그대로 두었다. HighlightedMessage, LongFormTextLayout, SWOT, TableOfContents, ProfileCard에도 같은 패턴이 있다. 6.1절에 모아 원저자 확인을 요청한다.

성립 조건 자체는 이 문서의 범위 밖이다. 신규 8종(페이지역할 4종, 지도전용 4종)이 실제로 어떤 콘텐츠 조건을 만족해야 그 유형으로 뽑히는지는 capability-table.md 2절에 아직 없다. 이 문서는 fallback 순서(capability-table.md 3절 해당분), 즉 후보가 없을 때 어디로 물러나는지만 다룬다.


3. semantic category 정의 참고#

semantic-taxonomy-v7.md 1절, 2절 원문에서 정의문을 그대로 옮겼다. 대분류가 같은 유형끼리는 근접도가 높다고 본다(원칙 2).

카테고리 대분류 정의 (v7 원문 축약)
List Enumeration 순서나 우열 없이 병렬 나열
ListingTable Enumeration 목록 정보를 행과 열로 표로 정리
ComparisonTable AttributeComparison 여러 기준·속성을 행열로 정리해 나란히 비교
Versus AttributeComparison 2개 대상을 대응시켜 직접 대조
RadarChart AttributeComparison 동일 평가 기준 여러 개로 강약점·프로파일 비교
SWOT RelationalComparison 내부/외부, 긍정/부정 4영역으로 전략 상황 정리
VennDiagram RelationalComparison 집합 간 공통점·차이를 원형 교차로
Quadrant RelationalComparison 두 축 기준 위치를 4영역으로
BubbleChart RelationalComparison 두 축 위치와 버블 크기로 상대적 위치·규모
Transformation Transformation 동일 대상의 전후 상태·변화
Steps ProcessFlow 절차를 순차 단계로
FlowChart LogicFlow 조건·판단·분기에 따라 흐름이 달라짐
Staircase Progression 상태·수준이 단계적으로 상승
Timeline TimelineSchedule 시간축 기준 사건·작업
GanttChart TimelineSchedule 작업별 시작·종료·기간·병렬을 일정표로
Calendar TimelineSchedule 월 단위 일정·이벤트를 달력으로
HierarchicalTree Hierarchy 상위-하위 계층적 연결 관계
Pyramid Hierarchy 넓은 기반에서 좁은 정점으로, 층 너비가 위계 암시
NestedScope ScopeCoverage 중심에서 바깥으로 포함 범위가 확장
Cycle Cycle 반복·순환·재귀 구조
Fishbone ReasoningStructure 문제·결과의 원인을 여러 갈래로 분해
CombinedResult ReasoningStructure 여러 요소 결합이 하나의 결과로
ProblemSolution ReasoningStructure 문제와 대응방안·기대효과를 짝으로
BigNumber KPIKeyMetric 가장 중요한 수치·KPI를 크게 강조
Gauge KPIKeyMetric 목표 대비 달성률·진행률을 게이지로
HighlightedMessage TextBasedCommunication 하나의 핵심 문장·주장을 강조
LongFormTextLayout TextBasedCommunication 하나의 주제를 여러 문장 본문으로
QnACard QnACard 질문과 답변을 대응
DialogueChat Utterance 2명 이상 대화를 말풍선·채팅으로
QuoteTestimonial Utterance 후기·인용·핵심 발언을 강조
ProfileCard PortraitProfile 인물 사진·이름·역할을 카드로 소개
ResumeCV PortraitProfile 경력·학력·역량·이력을 이력서 형식으로
Opening PageNavigation 발표 맨 앞장
TableOfContents PageNavigation 발표 전체 구성·섹션·순서 안내
SectionDivider PageNavigation 장 전환·섹션 시작
Ending PageNavigation 발표 마지막 장, 종결
PieChart ProportionComposition 전체를 조각으로 나눠 구성 비율
DonutChart ProportionComposition 중앙 공간을 활용하며 구성 비율
StackedBarChart ProportionComposition 항목별 전체값 안의 구성 비중을 막대 세그먼트로
TreeMap ProportionComposition 면적 크기로 전체 대비 구성 비율
PictogramChart ProportionComposition 아이콘 반복·채움으로 비율·수량
LineAreaChart Trend 시간·순서에 따른 수치 변화·추세
ComboChart Trend 같은 축 위에 다른 성격 지표를 겹쳐서
BarChart MagnitudeRanking 항목 간 수치 크기·순위를 막대 길이로
Funnel Conversion 단계별로 줄어들거나 선별·전환
Distribution Geo (지도 전용) 지역별 수치·밀도 분포
Location Geo (지도 전용) 특정 장소·지점·거점 표시
Highlight Geo (지도 전용) 특정 지역·권역 강조
Flow Geo (지도 전용) 지역 간 이동·물류·유입유출 흐름

4. v1 대 v2, 49행 전체#

카테고리 v1 v2 변경 유형 이유
List ListingTable ListingTable v1 확정 반영 (2026-08-17) entities 1개 이상이면 구조적으로 항상 성립하지만, 색인에 List 후보가 없으면(2.1절 "색인 공급 없음") ListingTable로 물러난다
ListingTable ComparisonTable → List ComparisonTable → List 동일 -
ComparisonTable ListingTable → Versus ListingTable → Versus → List 안전망 추가 Versus는 개수 조건이 정확히 2다. 그 밖의 개수를 대비해 List를 더했다
Versus ComparisonTable → List ComparisonTable → List 동일 -
StackedBarChart BarChart → LineAreaChart BarChart → LineAreaChart → List 안전망 추가 LineAreaChart도 matrix 데이터가 필요하다. 없는 경우를 대비해 List를 더했다
SWOT List → ListingTable List → ListingTable v1 확정 반영 (2026-08-17) List가 1순위, ListingTable은 색인 공급 부족 시 안전망
ComboChart ComparisonTable → ListingTable 조건부, 5절 참고 조건부 신설 계열별 type 필드로 판정하는 규칙으로 바꿨다
Gauge BigNumber → PictogramChart → BarChart BigNumber → HighlightedMessage v1 후보 정정 (2026-08-17) PictogramChart·BarChart는 matrix 필수라 measure 전용인 Gauge에서 변환할 projection 규칙이 3.4절에 없다. PictogramChart↔Gauge 문제와 대칭이다. 근거는 6.2절
Transformation Steps → Versus → ProblemSolution Steps → Versus → ProblemSolution → List 안전망 추가 사슬 끝 ProblemSolution이 종단이 아니라 종단을 더했다(2.1절)
Steps Timeline → Staircase → List Timeline → Staircase → List 동일 -
Staircase Steps → Timeline → Pyramid Steps → Timeline → Pyramid → List 안전망 추가 Pyramid는 개수 조건이 3개 이상이다
Cycle 공란 Steps → List 공란 채움 기존 41종 규칙을 복원했다
Timeline Steps → Staircase → List Steps → Staircase → List 동일 -
HighlightedMessage QuoteTestimonial → List → LongFormTextLayout QuoteTestimonial → List → LongFormTextLayout v1 확정 반영 (2026-08-17) entities 1개·한 문장이면 구조적으로 항상 성립하지만, 색인 공급 부족 시 List나 LongFormTextLayout으로 물러난다
LongFormTextLayout List List v1 확정 반영 (2026-08-17) entities 1개·여러 문장이면 구조적으로 항상 성립하지만, 색인 공급 부족 시 List로 물러난다
PieChart DonutChart → BarChart → ComparisonTable DonutChart → BarChart → ComparisonTable → List 안전망 추가 ComparisonTable은 열 2개 이상이 조건이다
DonutChart PieChart → BarChart → ComparisonTable PieChart → BarChart → ComparisonTable → List 안전망 추가 위와 같다
LineAreaChart BarChart → ComparisonTable BarChart → ComparisonTable → List 안전망 추가 위와 같다
BarChart LineAreaChart → ComparisonTable → ListingTable LineAreaChart → ComparisonTable → ListingTable → List 안전망 추가 ListingTable은 행 2개 이상이 조건이다. BarChart 자신이 다른 유형을 거치는 순서와 다른 차트의 경유지 역할이 충돌하지 않는다는 것은 6.3절에서 해소했다
HierarchicalTree Pyramid Pyramid → List 안전망 추가 Pyramid는 개수 조건이 3개 이상이다
BigNumber Gauge Gauge → HighlightedMessage 안전망 추가 BigNumber도 KPIKeyMetric, "가장 중요한 수치를 크게 강조"다. entities 1개짜리 강조형이라 List보다 HighlightedMessage가 가깝다. Gauge와 상호참조라 각자 안전망이 없으면 둘 다 막힌다
VennDiagram Versus Versus → ComparisonTable → List 안전망 추가 Versus는 정확히 2가 조건이다. ComparisonTable(같은 AttributeComparison 대분류) 경유를 더하고, 열 2개 이상 조건이 안 채워질 때를 대비해 List를 더했다
Pyramid NestedScope → Staircase NestedScope → Staircase → List 안전망 추가 Staircase는 2~8개가 조건이다. NestedScope와 상호참조
NestedScope Pyramid → Staircase Pyramid → Staircase → List 안전망 추가 위와 같다. Pyramid와 상호참조
DialogueChat QuoteTestimonial → QnACard QuoteTestimonial → QnACard → List 안전망 추가 사슬 끝 QnACard가 종단이 아니라 종단을 더했다(2.1절)
QuoteTestimonial DialogueChat → HighlightedMessage DialogueChat → HighlightedMessage 동일 QuoteTestimonial도 Utterance, "핵심 발언을 강조"라 entities 1개 강조형이다. HighlightedMessage가 이미 맞는 안전망이다
CombinedResult Steps → ProblemSolution → List Steps → ProblemSolution → List 동일 -
ProblemSolution CombinedResult → Transformation → Steps CombinedResult → Transformation → Steps → List 안전망 추가 Steps는 개수 조건이 2개 이상이다
RadarChart BarChart → LineAreaChart → ComparisonTable BarChart → LineAreaChart → ComparisonTable → List 안전망 추가 RadarChart와 ComparisonTable은 같은 AttributeComparison 대분류다. RadarChart가 축 3개 이상을 요구해 ComparisonTable의 열 2개 이상 조건은 사실상 항상 채워지고, 대개 ComparisonTable에서 끝난다. List는 이론상 마지막 대비다
Funnel BarChart → LineAreaChart → ComparisonTable Steps → BarChart → LineAreaChart → ComparisonTable → List 순서 변경, 2026-08-17 확정 Funnel(Conversion, "단계별로 줄어들거나 전환")은 BarChart·LineAreaChart(MagnitudeRanking·Trend, 크기·추세 비교)보다 Steps(ProcessFlow, "절차를 순차 단계로")와 정의가 더 가깝다. v1의 차트 후보 셋은 지우지 않고 안전망으로 뒤에 살렸다. 대분류가 다른 재배열이라 원저자 확인 대기였는데, 2026-08-17에 사용자가 이 순서로 확정했다(6.2절)
PictogramChart Gauge → BigNumber → BarChart BarChart → List v1 후보 정정 (2026-08-17) Gauge·BigNumber는 matrix가 아니라 measure(단일 값) 기반이라 PictogramChart(matrix, 여러 값)에서 변환할 projection 규칙이 3.4절에 없다. 근접도(원칙 2)와 구조 둘 다 맞는 것은 같은 matrix 기반인 BarChart뿐이다. 근거는 6.2절
TableOfContents List → ListingTable List → ListingTable v1 확정 반영 (2026-08-17) List가 1순위, ListingTable은 색인 공급 부족·항목 과다 시 안전망(2.1절, 6.1절)
SectionDivider 공란 HighlightedMessage → List 유형별 재지정 (2026-08-17) 장 전환은 제목 한 줄짜리 단일 메시지형이라 List 대신 HighlightedMessage를 1순위로 뒀다(6.4절)
Ending 공란 HighlightedMessage → List 유형별 재지정 (2026-08-17) 종결 메시지는 단일 메시지형이라 List 대신 HighlightedMessage를 1순위로 뒀다(6.4절)
QnACard DialogueChat DialogueChat → List 안전망 추가 DialogueChat은 개수 조건이 2개 이상이다
Opening 공란 HighlightedMessage → List 유형별 재지정 (2026-08-17) 맨 앞장은 제목 한 줄짜리 단일 메시지형이라 List 대신 HighlightedMessage를 1순위로 뒀다(6.4절)
ProfileCard ResumeCV → List → ListingTable ResumeCV → List → ListingTable v1 확정 반영 (2026-08-17) ResumeCV가 1순위, List·ListingTable은 색인 공급 부족 시 안전망(2.1절, 6.1절)
ResumeCV ProfileCard → Timeline → ListingTable ProfileCard → Timeline → ListingTable → List 안전망 추가 ListingTable은 행 2개 이상이 조건이다. ProfileCard와 상호참조
Quadrant ComparisonTable ComparisonTable → List 안전망 추가 ComparisonTable은 열 2개 이상이 조건이다
FlowChart 공란 Steps → List 공란 채움 기존 41종 규칙을 복원했다
Fishbone ProblemSolution ProblemSolution → List 안전망 추가 Fishbone과 ProblemSolution은 같은 ReasoningStructure 대분류다. 구규칙의 HierarchicalTree(Hierarchy, 다른 대분류)보다 v1 선택이 정의상 더 가깝다
BubbleChart PieChart → DonutChart → BarChart PieChart → DonutChart → BarChart → List 안전망 추가 BarChart는 matrix 데이터가 필요하다
GanttChart Timeline → Calendar → ListingTable Timeline → Calendar → ListingTable → List 안전망 추가 ListingTable은 행 2개 이상이 조건이다
Calendar Timeline → ListingTable → GanttChart Timeline → ListingTable → GanttChart → List 안전망 추가 GanttChart도 matrix 데이터가 필요하다. GanttChart와 상호참조
TreeMap PieChart → DonutChart PieChart → DonutChart → List 안전망 추가 TreeMap과 PieChart, DonutChart는 같은 ProportionComposition 대분류다. 구규칙의 BarChart(MagnitudeRanking, 다른 대분류)보다 v1 선택이 정의상 더 가깝다
Distribution Highlight → BarChart → Gauge Highlight → BarChart → Gauge → List 안전망 추가 Gauge는 measure.value가 필요하다
Location Highlight Highlight → List 안전망 추가 신규 8종이라 개수 조건이 아직 없다(2절). Highlight와 상호참조
Highlight Location Location → List 안전망 추가 위와 같다. Location과 상호참조
Flow 공란 Location → List 공란 채움 인접 지도전용 유형을 거쳐 안전망을 신설했다

5. ComboChart는 조건부 규칙이다#

폐기됨 (2026-08-21). 아래 조건부 규칙은 더 이상 유효하지 않다. ComboChart의 fallback은 ComparisonTable → ListingTable → List 고정 목록이고, 규칙 본문과 근거는 ../spec/capability-table.md 3.2절 상자가 정본이다.

왜 뒤집혔나. 아래 규칙이 읽는 "계열이 전부 bar/line" 케이스는 성립 조건상 언제나 이중 축이다 — 계열 type이 1종이면 capability-table.md 2절 32번의 "type 2종 이상 또는 axis 2종 이상"을 axis로만 채울 수 있기 때문이다. 그런데 이중 축은 ComboChart에서만 지원되므로(김수현, 2026-08-21) 막대나 선으로 내리면 반드시 축 하나가 사라진다. 즉 이 분기는 발동할 때마다 데이터를 깨는 규칙이었다. area 계열을 다루지 못하는 구멍도 있었다.

아래 본문은 2026-08-13 검토 당시의 판단 기록으로 남긴다.

v1은 ComboChart의 fallback을 ComparisonTable → ListingTable로 고정했다. 김수현이 백업 시트 코멘트(2026-08-07)에서 "가능하면 바 또는 라인으로 분리 표시, 불가능하면 컴패리즌테이블과 리스팅테이블"이라는 조건을 남겼는데, v1 표에는 그중 "불가능하면" 분기만 반영돼 있었다.

capability-table.md 2.4절 32번(ComboChart)이 이미 계열별 type(bar, line)과 axis를 선택 속성으로 갖고 있다. 계열 타입이 인스턴스 데이터에 이미 있으므로 "가능한지"는 정책으로 미리 정할 문제가 아니라 그 인스턴스의 계열 타입을 읽어 판정할 문제다. 미리캔버스 엔진에 ChartPropsVersionMigrator 등 차트 타입 간 변환 로직이 있어(issue-reports/rlsc-chart-datavizprops-extraction-failure.md 참고) 렌더링도 지원된다.

v2 규칙은 이렇다.

  • 계열이 전부 bar면 BarChart를 먼저 시도한다.
  • 계열이 전부 line이면 LineAreaChart를 먼저 시도한다.
  • 계열 타입이 섞여 있으면 ComparisonTable → ListingTable → List로 간다.

이건 4절 표의 다른 행과 달리 고정 목록이 아니라 판정 규칙이다. capability-table.md 3.4절의 P1부터 P8까지의 projection 규칙과 같은 층에 둔다.


6. 확인이 필요한 것#

6.1 v1이 항상 성립하는 유형을 사슬 끝이 아닌 자리에 둔 행 (6개, 2026-08-17 확정 반영)#

List, SWOT, HighlightedMessage, LongFormTextLayout, TableOfContents, ProfileCard가 여기 해당한다. 공통점은 이렇다. v1의 첫 번째나 중간 후보가 이미 항상 성립하는 유형(List 또는 HighlightedMessage)이라서, 그 뒤에 적힌 후보는 이론적으로는 거의 발동하지 않는다.

하지만 실제로 발동하지 않는다는 뜻은 아니다. List나 HighlightedMessage가 구조 조건(개수)은 채워도, 색인에 그 유형으로 태깅된 컴포넌트가 없거나 얕으면(2.1절 "색인 공급 없음") 뒤 후보가 실제로 쓰인다. 2026-08-17에 원저자 확인 없이 이 원칙으로 확정했다. 뒤 후보는 색인 공급 부족 대비용이고, v1 값을 그대로 반영한다.

  • List → ListingTable
  • SWOT → List → ListingTable
  • HighlightedMessage → QuoteTestimonial → List → LongFormTextLayout
  • LongFormTextLayout → List
  • TableOfContents → List → ListingTable
  • ProfileCard → ResumeCV → List → ListingTable

6.2 정원석이 v1 순서에 의견을 단 곳 (2026-08-17 전부 확정)#

  • Funnel (2026-08-17 사용자 결정). v1은 BarChart → LineAreaChart → ComparisonTable로 차트 계열을 1순위로 뒀다. v7 정의로 보면 Funnel(Conversion, "단계별로 줄어들거나 전환")은 Steps(ProcessFlow, "절차를 순차 단계로")와 "단계"라는 핵심 개념을 공유하는데, BarChart와 LineAreaChart는 크기와 추세 비교(MagnitudeRanking, Trend)로 다른 개념이다. 대분류가 다른 재배열이라 원저자 확인 대기였는데, 사용자가 최종 순서를 Steps → BarChart → LineAreaChart → ComparisonTable → List로 확정했다. Steps를 1순위로 하되 v1의 차트 후보 셋도 안전망으로 전부 살렸다. 4절 표, capability-table.md 3.2절과 3.5절 반영 완료.

PictogramChart (2026-08-17 정정, 4절 표 반영 완료). v1은 Gauge → BigNumber → BarChart였고, 첫 판 v2는 "셋 다 matrix 기반이라 구조적으로 호환된다"며 이 순서를 참고로 남겼는데 이 판단이 틀렸다. Gauge(#24)와 BigNumber(#23)는 matrix가 아니라 measure(단일 값) 기반이다(capability-table.md 2.4절). PictogramChart는 matrix(여러 값) 기반이라 Gauge 와 BigNumber로 물러나려면 여러 값을 하나로 접는 변환이 필요한데, 3.4절 P1~P8 어디에도 그런 규칙이 없다. PictogramChart와 근접도(원칙 2)와 구조가 둘 다 맞는 것은 같은 matrix 기반이자 이미 정의된 안전망인 BarChart뿐이다. v2는 Gauge, BigNumber를 후보에서 뺀다. 4절 표를 BarChart → List로 되돌렸다.

반대 방향인 Gauge도 대칭으로 고쳤다. 첫 판은 Gauge의 사슬을 BigNumber → PictogramChart → BarChart → HighlightedMessage로 뒀는데, PictogramChart 과 BarChart는 matrix 필수라 measure 전용인 Gauge에서 변환할 규칙이 없기는 마찬가지다(허재영님 "Gauge 경로" 제안과 일치). Gauge를 BigNumber → HighlightedMessage로 되돌렸다. 처음엔 PictogramChart 쪽만 고치고 이 대칭을 놓쳤다가 뒤늦게 잡았다.

6.3 BarChart (2026-08-17 해소)#

capability-table.md 3.1절 원 설계에서 BarChart는 다른 차트 9종(PieChart, DonutChart, TreeMap, PictogramChart, StackedBarChart, LineAreaChart, ComboChart, RadarChart, BubbleChart)의 공통 경유지였다. 색인이 차트 중 가장 두꺼워서다(색인 236건, 질의 120건). v1은 BarChart 스스로도 LineAreaChart와 ComparisonTable을 거치게 했다.

충돌이 아니다. 3.0.1절 그래프 정의대로 검색 모듈은 출발 카테고리의 자기 행만 왼쪽부터 따라간다. PieChart가 BarChart를 거쳐 List로 가는 것과, BarChart 자신이 없을 때 LineAreaChart → ComparisonTable을 거치는 것은 서로 다른 행이라 순회 경로가 겹치지 않는다. BarChart가 다른 차트의 경유지 역할과 자기 자신의 fallback 목적지를 동시에 갖는 데 구조적 문제가 없다. 열린 질문에서 뺀다.

6.4 페이지역할 4종의 실효성은 C-4에 걸려 있다#

Opening, TableOfContents, SectionDivider, Ending의 fallback 행은 이 4종이 검색 풀에 실제로 있을 때만 발동한다. 페이지역할 태그를 검색 풀에서 어떻게 다룰지 자체가 ../TODO.md C-4(정원석, 진행 중)로 열려 있다. C-4가 검색 풀 제외로 닫히면 이 4행은 발동할 일이 없고, 포함으로 닫히면 성립 조건 정의(2.4절 마지막 문단의 범위 밖 항목)가 선행돼야 한다.

범위 확인 (2026-08-17). ../TODO.md B-24(2026-08-06 확정)가 이미 답을 정해 뒀다. 페이지 역할 4종은 어휘에 넣어 쓰고, 지도 전용 4종(Distribution, Location, Highlight, Flow)은 capability 성립 불가로 표시해 당분간 summary 생성 대상에서 뺀다. 그래서 이 문서와 capability-table.md의 49종 반영 작업은 페이지역할 4종만 대상이고, 지도 4종은 이번 반영 범위 밖이다. Opening/TableOfContents/SectionDivider/Ending의 실제 성립 조건은 C-4가 닫혀야 확정된다.

6.5 개수 상한 초과의 처리는 B-27에 걸려 있다#

두 용어부터 정의한다. 예시: 항목 20개짜리 Steps가 최대 15개인 Timeline으로 물러날 때.

용어 뜻 이 예시에서
탈락 (규칙 1, 규약 5절 1번) 성립 조건(개수)을 못 채우면 그 카테고리 자체를 후보에서 아예 뺀다 Timeline은 후보에서 빠지고, Steps의 사슬은 다음 칸(Staircase)으로 넘어간다. 20개 항목은 그대로 유지된다
접기 (P8, capability-table.md 3.4절) 개수 상한을 넘겨도 후보로 받아들이고, 초과분을 마지막 항목의 items(하위 목록)로 뭉친다 Timeline을 후보로 받아 15개를 개별 표시하고, 초과한 5개는 15번째 항목의 items 안에 접어 넣는다

같은 상황에 답이 둘이라 어느 쪽이 우선인지는 ../TODO.md B-27로 미결이다.

이 문서는 사슬 순서(어느 카테고리 다음에 어느 카테고리가 오는가)만 정한다. 초과 시 실행 동작(규칙 1 vs P8)은 B-27이 닫히기 전까지 미확정으로 남긴다. 사슬 순서 자체는 이 결정과 독립적으로 성립한다.

6.6 이번에 반영하지 못한 것#

허재영님이 같은 회신에서 제기한 항목 중 이번 반영에서 빠진 것들이다.

  • v7 우선순위(P1~P4) 문서 동기화 해소 (2026-08-17). 로컬 Claude Code(Google Drive MCP)로도 13MB xlsx가 읽기/다운로드 한도를 넘어 막혔다가, 사용자가 로컬 사본 경로를 줘서 openpyxl로 직접 파싱해 semantic-taxonomy-v7.md 3절 구현 우선순위 열 49종 전량을 채웠다.
  • 신규 fallback edge의 projection 전량 계산: 6.4절 범위 확인대로 페이지역할 4종을 capability-table.md에 반영할 때, 그 신규 edge마다 3.4절 P1~P8 규칙을 계산해 3.5절에 추가해야 한다. C-4가 닫혀 성립 조건이 정해진 뒤의 작업이다.

7. StackedBarChart normalize (2026-08-17 결정, 08-17 2차 확인)#

StackedBarChart의 normalize 옵션은 참이면 전체 막대 높이가 항상 100%가 되도록 비율로 다시 그리고, 거짓이면 원래 수치 그대로 쌓는다. 이지선이 백업 시트 코멘트에서 지금 렌더러가 normalize: true 버전을 만들지 못한다고 지적했고, 정원석이 확인했다: 미리캔버스 엔진에 stacked bar 요소 자체가 없어 summary 생성 시 지원 불가가 맞다. 추정이 아니라 확정 사실이다.

true 옵션을 스펙에서 뺀다. 렌더러가 실제로 지원할 때까지 normalize는 항상 false다. capability-table.md 2.4절 26번을 이 결정에 맞춰 고쳤다.


8. 참고#

↑ 목차로 · 5부 머리로

28장골든셋 v1 설계 정본제작 경로와 자동 게이트

원문 plan/golden-set-blueprint.md GitHub 에서 보기 19 KB


title: Visual Summary 골든셋 v1 설계 description: 45종 × 10건 = 450행 골든셋을 만드는 경로. 정답은 규약 v0.5 YAML 한 덩어리이고, input 제작 → 다른 모델로 YAML → 자동 게이트 → 사람 보정 순으로 간다. 2026-08-24 싱크 결정 3번의 실행 설계. 450행 전량이 게이트를 통과했고 남은 것은 규약 미결 넷이다.#

Visual Summary 골든셋 v1 설계#

버전: 1.2 · 작성일: 2026-08-25 · 상태: Active · 구현 위치: 산출물 src/lib/visual-summary/generator/golden-sets/golden-set-v1.csv, 판정 src/lib/visual-summary/generator/lint.ts (제작 스크립트는 MOR-2079 에서 제거)

먼저 볼 것 450행이 다 서 있고 게이트를 전량 통과한다. 남은 것은 행이 아니라 규약 결정 넷이고 9절에 있다. 제작 과정에서 배운 것은 reference/golden-set-production-notes.md 로 갈라 두었다.

0. 목표#

Visual Summary 생성기의 성능을 숫자로 말할 수 있게 만든다. 지금은 못 한다. 평가셋이 17 중분류만 덮고, 실사용 어휘는 45종이며, 희귀 카테고리는 표본이 아예 없어 "되는지 안 되는지"조차 모른다.

그래서 만드는 것이 45종 × 10건 = 450행의 정답지다. 한 행은 input_text 하나와 규약 v0.5 YAML 정답 하나다.

완료 기준#

  • 450행이 CSV 한 장에 있고, 45종이 정확히 10건씩 차 있다
  • 450행 전부가 자동 게이트 5종을 통과한다 (스키마, 어휘 45종, 카테고리 일치, 누설 없음, 중복 없음)
  • 게이트가 튕긴 행을 사람이 한 번 훑었고, human_status 가 unreviewed 인 행이 0이다. 383행 남음 (7.3절)
  • 이 골든셋으로 생성기를 돌려 태그 정확도와 구조 일치 두 수치가 나온다
  • 그 수치를 낸 조건(모델, 프롬프트 판, 날짜)이 함께 기록된다

채점은 LLM as judge 로 한다 (2026-08-25 황현진). 생성기 산출과 정답 YAML 을 문자열로 맞대지 않고 심판 모델이 견준다. title 은 채점에서 뺀다 (같은 날 결정, 9절).

이 목표가 지키는 것#

정답이 생성기와 독립이어야 한다. 골든셋이 생성기의 거울이 되면 수치가 "생성기 실력"이 아니라 "같은 모델의 재현성"을 잰다. 채점을 심판 모델이 하더라도 이 문제는 남는다. 정답이 거울이면 심판이 공정해도 잴 것이 없다. 이 요건이 4절의 모델 배치와 8절의 기각 하나를 낳는다.

45종이 균등해야 한다. 실사용 분포를 그대로 쓰면 Enumeration.List 가 절반을 먹고 희귀 12종은 영원히 0건이다. 균등해야 "어느 카테고리가 약한가"를 볼 수 있다.

수치가 어디서 왔는지 되짚을 수 있어야 한다. source, gen_model, difficulty 를 열로 남기는 이유다 (6절).

1. 배경#

2026.08.24 Visual Summary 싱크 결정 3번이 골든셋을 "AI로 초벌 생성 뒤 사람이 보정, CSV 로 관리" 로 정했다. 같은 회의 결정 1번이 생성기 어휘를 45종으로 확정했다. 색인 태그 49종에서 Geo_* 4종을 뺀 것이고 커널 41종 + PageNavigation 4종과 같은 집합이다. I.I.S. 는 49종 전량을 계속 쓴다.

MOR-2165 [4-8] 이 "평가셋을 45 중분류로 확장한다" 이고 이 문서가 그것을 푸는 설계다.

2. Non-goals#

  • 마크다운 요약을 정답 열로 만들지 않는다. 요약은 입력 쪽 산출물이지 정답 쪽 산출물이 아니다 (2026-08-24 황현진: "기존 마크다운 서머리는 제작할 필요없음"). 실측으로도 기각됐다 (5.2.1절).
  • v1 은 단일 컴포넌트만 담는다. 병치와 멀티페이지는 v2 로 미룬다 (9절).
  • 바운더리 정확도는 소관이 아니다. I.I.S. 경계 골든셋(MOR-2110)이 잰다.
  • 생성기 프롬프트 개선은 범위 밖이다. 다만 골든셋을 만들 수 없게 만드는 결함은 고쳤다 (9.1절).

3. 초안 1,000건 실측 (2026-08-24)#

원본은 지웠고 회수는 끝났다. 되쓸 값은 reference/golden-set-draft-recovery.md 에 있고 그 문서가 SSoT 다. 여기서는 이 설계를 낳은 두 사실만 적는다.

45종 × 10 을 초안에서 뽑을 수 없다. 각 종 최대 10건씩 취해도 196 / 450 (43.6%) 만 찬다. 13종만 10건 이상이고 12종은 표본이 0건이다 (RadarChart, SWOT, Quadrant, BubbleChart, Calendar, Pyramid, Cycle, HighlightedMessage, LongFormTextLayout, TreeMap, PictogramChart, TableOfContents). 동시에 Enumeration.List 는 508건 중 498건을 버리게 된다. 초안이 실사용 분포를 그대로 담고 있어서 균등 450 이라는 목표와 정면으로 어긋난다.

초안의 라벨은 우리가 풀려는 문제의 답이 아니다. 판정문이 "화살표로 순차 연결되어 있으며", "2×2 카드 그리드로 나열" 처럼 시각 정보를 근거로 쓴다. 우리는 텍스트만 본다. 그래서 초안 정답률(어휘 밖 60행을 걷어내면 86.3%)을 게이트 통과율의 기준선으로 쓸 근거가 약하다. 회수 문서 0절이 이것을 다룬다.

4. 제작 경로: 교차 방식#

① 카테고리 지정      45종 × 10 = 450 슬롯
                     난이도 배분 easy 6 / ambiguous 3 / boundary 1
   ↓
② input_text 제작    모델 A. 카테고리별 10건 배치 → 45 콜
   ↓
③ 정답 YAML 생성     모델 B (생성기 앵커와 다른 계열) → 450 콜
   ↓
④ 자동 게이트        코드. 5종 (5절)
   ↓
⑤ 사람 보정          게이트가 튕긴 행만

③ 만 다른 모델을 쓴다. 거울 회피가 필요한 자리가 여기뿐이기 때문이다 (0절). input_text 는 정답이 아니라 문제지라서 ② 는 앵커를 써도 된다.

자리 모델 왜
생성기 앵커 gpt-5.6-luna 2026-08-20 확정
② input 제작 gpt-5.4 아무거나 좋다
③ 정답 YAML gpt-5.4 2026-08-25 확정. 앵커와 다르다
채점 심판 미정 [4-21]. 앵커와도 gpt-5.4 와도 달라야 한다 (9절)

gpt-5.4 는 거울 회피를 절반만 만족한다. 그것을 알고 받아들인다. 앵커와 다른 모델이므로 "같은 모델의 재현성" 은 아니지만 같은 provider 계보라서 실패 모양을 공유할 수 있다. gen_model 열이 그 사실을 450행 전부에 남기므로 나중에 수치를 읽을 때 이 조건을 되짚을 수 있다.

문서 0.2판이 후보로 적은 gemini-2.5-flash 는 쓰지 않는다. 호출 자체가 막혀 있었다. .env.local 의 VERTEX_PROJECT_ID, VERTEX_LOCATION, GOOGLE_CLOUD_PROJECT 가 값 길이 0 이고 Bedrock 도 AWS_ACCESS_KEY_ID 가 비어 있다 (2026-08-24 확인).

② 를 왜 카테고리부터 지정하는가. 실데이터 우물이 없다 (3절). 카테고리를 슬롯으로 먼저 못 박아야 45종 균등이 보장되고 표본 0건인 12종을 채울 수 있다.

③ 의 산출이 ① 의 지정과 어긋나면 input 을 고친다. 불일치는 "모델이 틀렸다" 가 아니라 "input 이 애매하다" 는 신호로 읽는다. 골든셋의 input 은 정답이 하나로 읽히는 것이어야 한다. 이 규칙이 실측으로 검증됐다 (7.2절). 다만 한 종의 열 건이 같은 방향으로 어긋나면 규칙이 적용되지 않는다. 그것은 규약 문제이고 9절이 다룬다.

5. 자동 게이트#

새로 만들 것이 거의 없었다. 45종 검증기가 이미 코드로 서 있다.

검사 구현
스키마 visual-summary/validate.ts + spec/schema/visual-summary-0.5.schema.json
어휘 45종 generator/vocabValidate.ts — outsideVocabulary 가 비어야 함
카테고리 일치 generator/lint.ts 의 checkCategoryMatch
정답 누설 generator/lint.ts 의 checkLeak
중복 generator/lint.ts 의 checkDuplicates

다섯을 엮는 것은 lintGeneration 이고 테스트는 generator/__tests__/lint.test.ts 다.

원래 generator/golden-sets/gates.ts 였고 MOR-2079 에서 제작 비계를 걷어내며 판정 로직만 옮겼다. 함수 이름과 동작은 그대로다.

5.1 구현에서 이 설계와 달라진 것#

평가 순서가 스키마부터가 아니다. 입력 쪽 판정(누설, 중복)을 먼저 본다. 고치는 단위가 다르기 때문이다. 입력이 새는 행은 input 을 다시 쓰고 그러면 정답 YAML 도 다시 만들어야 한다(4절). YAML 만 깨진 행은 YAML 만 다시 만든다. 앞의 것을 먼저 드러내지 않으면 고칠 필요가 없어질 YAML 을 고치게 된다.

누설 판정이 한국어 구조어를 낱말로 막지 않는다. "단계", "비교", "비율" 은 일상 한국어에서 너무 흔하다. 초안의 Steps 실유저 입력 70건 중 3건이 "성장단계별" 처럼 자연스럽게 쓰고 그 셋이 전부 PASS 였다. 막는 것은 순서를 표기로 드러낸 것(1단계, STEP 1, ①)과 카테고리 이름 45종과 표현 지시어다. 실유저 75건에 걸어 순서 표기 4건만 적발하고 오탐이 0이었다.

중복 상한이 0.25 다. 실측이 값 하나가 아니라 띠를 준다. 초안 실유저 75건 2,775 쌍의 문자 3-gram Jaccard 에서 서로 다른 쌍의 최대가 0.143 이고 실제 중복의 최소가 0.273 이라 그 사이가 비어 있다.

처음에 0.15 를 골랐다가 올렸다. SWOT 제작 input 10건에서 두 쌍(0.180, 0.160)이 걸렸는데 겹치는 3-gram 이 강점:, 약점:, 기회:, 위협: 이었다. 그 종의 필수 어휘다. 0.15 는 필수 어휘가 없는 Steps 에서 잰 값이라 어휘가 고정된 종에 그대로 쓸 수 없다.

더 올릴 수도 없다. PageNavigation.Ending 제작 input 10건에서 네 쌍이 0.255~0.274 로 걸렸고 겹치는 3-gram 이 "해 주셔서 고맙습니다" 였다. 맺음말이라는 종의 내용 공간이 좁아서 나는 값이다. 낮추면 어휘 고정 종이 걸리고 올리면 실제 중복을 놓친다. 이 지표가 여기서 한계에 닿았고, 그 자리는 사람 판정으로 넘긴다.

5.2 두 계보를 섞는다#

8절이 기각한 "초안 input 재활용" 을 뒤집었다 (2026-08-25 황현진: "이게 user data라 가치있는 데이터가 생성한거보다"). 실사용 입력의 지저분함과 원본 마크다운 요약을 얻고 계보의 단일함을 내놓는 거래다. 그래서 source 열이 생겼다.

source 행 input_text markdown_summary gold_yaml
user 87 초안의 실유저 입력 초안의 summary 원문 (배포된 FT 산출) input_text 로 모델 B 가 만든 것
crafted 363 모델 A 나 사람이 쓴 것 현행 Summary 생성기가 만든 것 같음

45종 × 10 은 무조건 지킨다 (2026-08-25 황현진: "근데 균등하긴해야해.무조건"). 계보만 섞이고 슬롯 수는 그대로다.

실유저 행은 easy 슬롯에만 넣는다. ambiguous 와 boundary 는 인접 카테고리를 일부러 겨냥한 자리인데 초안 라벨은 "이 입력이 그 카테고리다" 만 말하고 "Timeline 을 닮은 Steps 다" 는 말하지 않는다.

거둘 때 버리는 것이 넷이다. 초안이 PASS 로 보지 않은 행, 30자 미만, 누설 판정에 걸리는 것, 같은 종 안의 중복.

첫째 조건을 처음에 빼먹었고 대가가 컸다. 초안의 FAIL 은 판정자가 그 라벨을 거부했다는 뜻이다. 거부된 라벨을 지정 카테고리로 앉히면 모델 B 가 튕기는 것이 당연하다.

출신 게이트 통과
초안 PASS 23 / 68 (33.8%)
초안 FAIL 4 / 42 (9.5%)

무엇이 들어와 있었는지가 더 분명하다. SectionDivider 6건이 전부 서사나 주장이었고 ProfileCard 4건에 사람 프로필이 하나도 없었다. 조건을 넣으니 거둔 것이 123 → 99건, 슬롯에 앉은 것이 110 → 87행으로 줄었다.

5.2.1 요약을 정답의 입력으로 쓰지 않는다. 실측으로 기각#

한 번 그렇게 해 봤고 되돌렸다.

정답의 입력 통과
input_text 395 / 450 (87.8%)
markdown_summary 216 / 450 (48.0%)

실패 234건이 몰린 자리가 원인을 말한다. 병치 60, ListingTable 45, List 45, ComparisonTable 41. 평탄화가 만든 모양들이다. 한 행이 그것을 다 보여 준다. 입력 신입 교육 이수율이 62%에서 91%로 올랐다 가 요약에서 두 행짜리 표가 되면 Transformation 의 신호가 사라지고, 모델은 표를 보고 표라고 답한다. 옳다.

요약은 정보를 잃는 단계이고 Visual Summary 는 그 손실을 메우려고 만든 표현이다. 요약을 정답의 입력으로 쓰면 그 손실을 정답에 새긴다. 제품도 그렇게 동작하지 않는다 (2026-08-25 황현진: "실제제품은 요약을 받아서 만들진 않을건데").

5.2.2 요약은 내용의 기준으로 쓴다#

요약이 값진 이유는 배포된 FT 모델의 산출이라는 것이다. "이 입력에서 무엇을 뽑아야 하는가" 에 대해 신뢰할 수 있는 답이다.

그 신뢰를 쓸 자리는 생성기 입력이 아니라 채점 기준이다. 게이트 5종은 전부 구조만 본다. "이 YAML 이 입력의 내용을 실제로 담고 있나" 를 보는 판정이 없다. 그것이 이 골든셋의 가장 큰 구멍이다.

어디 무엇
내용 보존 검사 (신규) 정답 YAML 이 요약이 짚은 내용을 담았나. LLM 이 판정
사람 검수 보조 미검수 행을 읽을 때 요약을 나란히 놓으면 무엇이 빠졌는지 바로 보인다
[4-21] 심판 프롬프트 심판에게 요약을 함께 줘 내용 기준을 잡아 준다

내용 보존 검사를 ④ 에 섞지 않는다. 게이트 5종의 성질이 "무료와 결정적" 이라 몇 번 돌려도 같은 값이 나온다. LLM 판정을 그 안에 넣으면 그 성질이 깨진다. ⑤ 보조 단계로 따로 둔다.

6. CSV 열#

한 장짜리 CSV 로 간다 (결정 3번). 초안 CSV 가 여러 줄 마크다운 1,000행을 정상 파싱한 것으로 이 방식이 되는 것은 확인됐다.

id            GS-ProcessFlow_Steps-03
category      45종 저장키(밑줄) 표기. 정답이 아니라 제작 시 지정값이다
difficulty    easy | ambiguous | boundary
input_text    사용자 원문
gold_yaml     정답 YAML 본문
gen_model     ③ 을 만든 모델. 손으로 쓴 것은 hand (거울 회피 기록)
auto_status   pass | schema_fail | vocab_fail | mismatch | leak | dup
auto_note     판정이 남긴 사유
human_status  unreviewed | ok | fixed | dropped | pending_spec
human_note    사람 코멘트
markdown_summary 같은 입력을 현행 Summary 생성기가 낸 마크다운. 참고 열이고 정답이 아니다
source           user | crafted (5.2절)

열이 열둘이고 붙이는 것은 뒤에만 한다. 순서를 바꾸면 이미 만든 CSV 를 읽을 때 열이 밀린다. 뒤에 붙인 둘(markdown_summary, source)은 없어도 읽힌다. 그 열이 생기기 전에 만든 파일은 낡은 파일이지 잘못된 파일이 아니다.

카테고리 열을 정답으로 두지 않는다. 카테고리가 YAML 안(components[0].type)에 있으므로 채점기가 거기서 뽑는다. 그래서 태그 정확도와 구조 일치를 같은 열에서 잰다. 태그는 코드가 문자열로 맞대고 구조는 심판 모델이 견준다.

7. 실측#

7.1 파일럿 2종#

ProcessFlow.Steps(손으로 쓴 input)와 RelationalComparison.SWOT(모델 A) 각 10건이다.

무엇 Steps SWOT
게이트 단발 통과 9 / 10 9 / 10
튕긴 사유 카테고리 불일치 1 병치 1
input 을 고쳐 다시 만든 뒤 10 / 10 10 / 10

② 의 모델 A 경로가 여기서 검증됐다. SWOT 은 초안 표본이 0건인 12종 중 하나라 근거가 초안에 없는 가장 어려운 경우인데, 1~2 콜로 10 / 10 이 나왔다.

손으로 쓴 파일럿과 체계적으로 유도한 계획이 같은 곳에 떨어졌다. 파일럿 10건을 먼저 손으로 쓰고 45종 슬롯 계획을 나중에 세웠는데 Steps 의 네 축과 순번 배치가 정확히 일치했다. 나머지 44종에 같은 방법을 쓸 근거다.

슬롯 축의 근거를 셋으로 두고 위에서부터 썼다. ① 초안 FAIL 을 손으로 읽어 방향까지 가른 12종의 실측, ② prompts/system.md 의 갈림길 표, ③ capability.ts 의 fallback[0]. fallback 을 첫 근거로 쓰지 않은 이유는 그것이 "성립 조건을 못 채웠을 때 무엇으로 내려가나" 이고 오분류 축은 "무엇과 헷갈리나" 여서 둘이 다르기 때문이다. 실측이 있는 11종으로 대조하니 6종만 fallback[0] 과 맞았다.

파일럿의 90% 를 45종으로 옮기면 안 된다. Steps 는 어휘 안에서 쉬운 종이고 초안 정답률이 940행 평균보다 7.0포인트 높았다. 다음 절이 그 경고가 맞았음을 보인다.

7.2 45종 전량: 생성으로 간 데까지#

45종 450행을 만들고 게이트를 돌린 값이다. 판마다 좋아지지만 수렴한다.

판 무엇을 다시 만들었나 통과
1 처음 만들기 259 (57.6%)
2~4 종 단위로 input 과 정답 316 (70.2%)
5 프롬프트 결함 넷을 고치고 전량 재생성 346 (76.9%)
6~9 행 단위로 고치기 384 (85.3%)

행 단위로 고치는 것이 종 단위로 다시 만드는 것보다 낫다. 종 단위로 다시 만들면 좋아지는 종과 나빠지는 종이 함께 나온다. CombinedResult 는 0에서 8로 올랐지만 LongFormTextLayout 은 5, 3, 0 으로 내려갔다. 열 건 중 여덟이 이미 통과한 종을 다시 만들면 통과한 것까지 바뀐다.

4절의 규칙이 검증됐다. 흩어진 21행의 input 을 손으로 다시 쓰니 스무 행이 통과했고, 스물한 건 전부 모델이 맞고 input 이 지정 카테고리와 실제로 어긋난 경우였다. 예를 들어 GS-Enumeration_List-10 의 원문 "접수 확인 후 명찰 배부, 입장하면 좌석 안내" 는 나열이 아니라 절차다.

난이도가 실패를 설명하지 못한다. easy 83 / 270, ambiguous 53 / 135, boundary 16 / 45 실패다. 어려운 슬롯이 아니라 어려운 종이 있다.

여기서 생성으로 갈 수 있는 데까지 갔다. 남은 66행은 다시 태워도 열리지 않았다. 원인이 input 이 아니라 규약에 있어서다 (9절).

7.3 남은 66행은 손으로 썼다. 450 / 450#

게이트가 튕긴 행은 모델을 다시 태우는 자리가 아니라 사람이 정답을 적는 자리다. 4절의 교차 방식이 그렇게 적혀 있는데 실행에서 이 단계를 건너뛰고 프롬프트를 고쳐 전량 재생성하기를 여덟 판 반복했다 (2026-08-25 황현진: "auto note를 보고 니가 수정하라니까 LLM 태우지말고", "이건 생성기로 생성하는게 아니라 골든셋 제작하는거야").

재생성이 제자리를 도는 이유가 있다. 생성이 비결정적이라 통과한 행의 YAML 도 바뀐다. 고친 만큼 다른 곳이 무너져 총합이 355 → 365 → 364 를 돌았다. 손으로 쓰자 세 묶음이 서로 영향 없이 383 → 393 → 412 → 450 으로 올랐다.

무엇을 손으로 썼나. mismatch 가 주 대상이다. 모델이 스키마에 맞는 YAML 을 냈는데 카테고리가 지정과 다른 경우이고, 그때 고칠 것은 components[0].type 과 그 종에 맞는 structure 다. 제목과 항목의 문장은 모델 산출을 그대로 쓴다. 정답을 처음부터 다시 쓰는 것이 아니라 틀린 자리만 갈아 끼운다.

최종 상태.

auto_status pass 450 / 450
gen_model gpt-5.4 383 · hand 67
source crafted 363 · user 87
human_status fixed 67 · unreviewed 383
종당 건수 45종 전부 정확히 10

gen_model: hand 로 남기는 이유. 거울 회피의 기록이 그 열의 목적이므로 사람이 쓴 것도 그 사실이 남아야 한다. 나중에 "모델이 만든 정답만" 으로 걸러 재는 것도 이 값으로 한다.

게이트 통과가 검수 완료는 아니다. unreviewed 383행은 구조가 맞다는 것만 확인된 상태다. 내용 검수는 사람 몫이고 5.2.2절의 요약이 그 자리를 돕는다.

8. 기각한 대안#

대안 왜 기각했나
전량 실데이터 수치는 가장 정직하지만 표본 0건인 12종을 영원히 못 잰다. P0 에 희귀종이 있으면 그대로 구멍이 된다
역생성 (정답 먼저 → input 을 거꾸로) 검수가 거의 필요 없고 균등이 자동이지만, input 이 정답으로 되돌아가기 쉬운 인공적 문장이 돼 수치가 낙관적으로 나온다. 단 finetuning-plan.md 의 J-3 은 목적이 달라(파인튜닝 데이터 증강) 그 계획은 유효하다
정답을 생성기와 같은 모델로 배관이 하나로 줄어 가장 싸지만 골든셋이 생성기의 거울이 된다 (0절)
전량 합성 (초안 폐기 + 45종 역생성) 깔끔하지만 실유저 입력의 지저분함을 잃는다. 부분 채택 — input 은 제작하되 초안 FAIL 의 judge_reason 을 난이도 설계 근거로 흡수했다
초안 input 재활용 두 계보가 섞여 사람이 더 볼 것이 는다. 2026-08-25 에 뒤집었다 (5.2절) — 실유저 데이터의 값이 그 비용보다 컸다

9. 남은 미결#

넷이고 넷 다 규약이나 팀의 결정을 기다린다. 450행이 다 통과한 것은 손으로 정답을 적었기 때문이지 이 넷이 풀렸기 때문이 아니다. 정하기 전에 만든 정답은 결정에 따라 다시 만들어야 한다.

9.1 표현이 정체성인 종 셋: 채점기가 따로 집계해야 한다#

단어장 v7 이 형태로 정의한 종들이다. 내용이 같고 그리는 방식만 다르므로 입력이 표현을 지목해야 정답이 하나로 읽힌다.

종 무엇으로 갈리나
ProportionComposition 5종 원·도넛·면적·아이콘
TextBasedCommunication.LongFormTextLayout 줄글이냐 항목이냐
ScopeCoverage.NestedScope 동심원이냐 트리냐

그래서 골든셋 입력에 표현 지시를 허용했다 (2026-08-25 황현진: "골든셋에 도넛형태로 를 허용해야할거같아"). 규약이 이미 그렇게 정해 두었다. prompts/system.md 가 "요청이 지목한 표현이 ProportionComposition 계열이면 그것을 고른다" 고 적는다. 규약이 받는 신호를 골든셋이 누설로 막으면 골든셋이 규약과 싸운다. 누설 판정에서 '도넛' 과 '트리맵' 을 뺐고, 내용으로 정의된 종의 이름('타임라인', '간트', '퍼널', '벤다이어그램')은 그대로 막는다.

미결은 이것이다. 표현 지시가 있어야 통과하는 종은 "종이 성립한다" 를 재는 것이고 "텍스트에서 판별한다" 를 재는 것이 아니다. [4-21] 채점기가 이 셋을 따로 집계할 수 있어야 하고, 한 수치에 섞으면 그 수치가 무엇인지 말할 수 없게 된다.

NestedScope 가 왜 트리와 같은지는 reference/semantic-taxonomy-v7.md 의 NestedScope 절이 적는다.

9.2 병치: v1 단일 컴포넌트 고정이 맞나 (팀 확인)#

root components 가 둘 이상인 것을 게이트가 거부한다 (2절 Non-goals). ReasoningStructure.CombinedResult 에서 이 제약이 종의 본질과 부딪힌다. A + B → C 구조가 "A와 B" 덩어리와 "C" 덩어리로 읽힌다.

병치를 허용해도 안 풀리는 행이 절반이다. 실측에서 28건이 정확히 14 대 14 로 갈렸다. 지정 종이 산출 안에 있으면(LineAreaChart + HighlightedMessage) 허용하는 순간 풀리지만, 밖에 있으면(HighlightedMessage + CombinedResult 인데 지정은 LongFormTextLayout) 허용해도 여전히 틀린 행이다. 그래서 게이트가 판정문에 "지정 종이 그중에 있다/없다" 를 적는다. 결정 하나에 걸린 행은 28이 아니라 14다.

9.3 PageNavigation 4종: 능력표 반영 대기#

성립 조건이 정본 시트에 이미 있었다. capability-table.md 2.6절이 "미정" 으로 비워 둔 것은 정하지 않은 결정이 아니라 옮겨 적지 않은 것이었다. 시트 문구를 프롬프트 판단 지침과 reference/semantic-taxonomy-v7.md 에 반영했고, 능력표에 넣을 제안은 page-navigation-capability-draft.md 에 있다.

능력표 자체는 오너가 정원석이라 손대지 않았다. 그 문서가 C-4(뽑혔을 때 어느 템플릿을 주는가) 조건을 적고 있고, 2.6절은 C-4 가 먼저라고 적었는데 순서가 뒤집혀 있었다. 조건은 시트에서 나온다.

9.4 심판 모델 미정 + 비결정성#

심판이 앵커(gpt-5.6-luna)와 같으면 자기 산출을 자기가 채점하고, gpt-5.4 와 같으면 자기가 쓴 정답을 자기가 옹호한다. 거울을 피해야 할 자리가 하나 늘었다. [4-21] 이 정할 일이고 레포의 lib/judge/models.ts 목록에서 고르는 것이 자연스럽다.

한 판의 총합으로 개선을 주장할 수 없다. 생성기에 temperature 나 seed 설정이 없어 API 기본값으로 돈다. 종 단위의 큰 변화는 신호였지만 총합 ±10 은 판마다 그냥 흔들린다. 같은 조건으로 두세 판 돌려 분산을 재거나 temperature: 0 을 걸어야 하는데, 후자가 싸지만 제품이 기본값으로 도는 이상 그 수치는 제품의 성능이 아니다.

alternatives 와 items 의 정본이 없다. 둘 다 규약상 선택이라 어느 쪽도 위반이 아닌데 문자열로 맞대면 점수를 깎는다. 규약을 고칠 일이 아니라 심판 프롬프트에 "선택 필드의 유무는 보지 말라" 를 넣는다.

9.5 닫힌 것. title#

title 을 채점에서 뺀다 (2026-08-25 황현진). 생성기 프롬프트가 title 에 "비어 있으면 안 됨" 만 요구해서 모델이 첫 항목을 베끼거나 마지막 항목만 적는다. 검수한 23행 중 10행이 그랬다.

가를 길이 둘이었고 2번으로 정했다.

  1. title 규약을 정하고 450건의 정답을 그 규약으로 맞춘다
  2. 채점을 문서 전체가 아니라 구조 부분에서 잰다

1번은 450건을 손으로 고치는 비용을 낳는데 그 비용을 써도 지표가 낫지 않는다. 평가 대상 생성기도 임의의 제목을 내므로 정답 쪽만 좋은 제목으로 맞추면 제목 줄은 여전히 거의 언제나 불일치다. 잡음을 지표에서 빼는 것이 잡음을 정답지에 새기는 것보다 싸다.

이미지도 붙이지 않는다 (2026-08-25 황현진). 초안 CSV 의 image_url 이 1,000건 전부 비어 있었고 "보정이 느려지면 그때 붙인다" 로 열어 두었던 자리다. 닫는다.

10. 티켓 분해#

MOR-1776 아래 Subtask [4-N] 체계를 따른다.

# 할 일 티켓 상태
1 평가셋 45 중분류 확장 MOR-2165 [4-8] 끝남
2 CSV 열·ID 규칙 확정 [4-16] 끝남
3 45종 × 10 input_text 450건 제작 [4-17] 끝남
4 정답 YAML 450건 — 생성기와 다른 모델 [4-18] 끝남 (383 생성 + 67 수기)
5 자동 게이트 5종 구현 [4-19] 끝남
6 사람 보정 1회전 + 골든셋 v1 확정 [4-20] 게이트 450 / 450. 내용 검수 383행 남음
7 평가 러너 — 태그는 코드, 구조는 LLM as judge [4-21] 남음. 9.1·9.4절이 요건
8 생성기 프롬프트 결함 여덟 수정 — 끝남. prompt.test.ts 의 REQUIRED_RULES 에 못박음

11. 관련 문서#

Checklist#

  • 정답 형태 확정: 규약 v0.5 YAML 한 덩어리 (2026-08-24)
  • 조달 방식: input 부터 제작, 교차 방식 (2026-08-24)
  • 초안 1,000건 실측과 회수 후 원본 폐기 (2026-08-24)
  • CSV 열과 ID 규칙 확정 (2026-08-24)
  • 자동 게이트 5종 구현 (2026-08-24)
  • 모델 B 확정: gpt-5.4 (2026-08-25, 4절)
  • title 을 채점에서 뺀다 (2026-08-25, 9.5절)
  • 이미지는 붙이지 않는다 (2026-08-25, 9.5절)
  • alternatives 과 items: 규약을 고치지 않고 심판 프롬프트가 흡수 (2026-08-25, 9.4절)
  • 생성기 프롬프트 결함 여덟 수정 (2026-08-25)
  • 450행 전량 게이트 통과 (2026-08-25). 383 생성 + 67 수기 (7.3절)
  • 내용 검수 383행: 게이트는 구조만 본다 (7.3절). 요약을 나란히 놓고 읽는다 (5.2.2절)
  • 표현이 정체성인 종 셋을 채점기가 따로 집계 (9.1절)
  • 병치: v1 단일 컴포넌트 고정이 맞는지 팀 확인. 걸린 행은 14 (9.2절)
  • PageNavigation 능력표 반영: 정원석 확인 대기 (9.3절)
  • 심판 모델 선정: 앵커와도 gpt-5.4 와도 달라야 한다 (9.4절)
  • meta.json 의 visual-summary 항목이 폴더 재구성 이전 경로라 낡았다. 색인 전체를 고칠지는 별건

Changelog#

날짜 버전 변경 근거
2026-08-26 1.2 942 → 약 490줄. 9.3~9.13(제작 과정 318줄)을 golden-set-production-notes.md 로 갈라내고, 통과 수치를 CSV 참값으로 통일했다 — 문서가 316·346·370·372·259 다섯 값을 말했는데 실제는 450 / 450 이고 손으로 쓴 67행 국면이 통째로 빠져 있었다(7.3절 신설). 9절을 살아 있는 미결 넷으로 재편, 반복된 서술 넷(거울 회피·VennDiagram·표현 종·title)을 한 자리씩으로, 삭제된 코드 경로 11곳 정정 황현진: "문서를 좀 짧게 해야해 너무 길어" · golden-set-v1.csv 실측
2026-08-25 1.1 시트의 Calendar 정의를 옮기다 배타 조건을 지어내 384 → 370 으로 떨어뜨린 것과 "긍정 정의를 배타 규칙으로 옮기지 않는다" 규칙 450행 실행
2026-08-25 1.0 정본 시트 확인. 시트가 스냅샷보다 8일 새롭고 역산하던 판별 기준이 거기 있었다. 프롬프트 판단 지침과 repo 사본 v7 갱신, PageNavigation 16행 재작성, 능력표 제안 별 문서 원본 시트 레이아웃 텍사노미 신규 탭 + 2026-08-25 황현진 결정 둘
2026-08-25 0.6~0.9 프롬프트 결함 여덟 발견과 유형화, crafted 실패 17건 중 13건이 input 문제였다는 정독, 병치 28행이 14 대 14, 부분 재생성으로 판을 읽을 수 있게 됨 450행 실행 다섯 판
2026-08-25 0.5 채점을 LLM as judge 로, title 채점 제외, 모델 B 를 gpt-5.4 로 확정, 이미지 안 붙임 2026-08-25 황현진
2026-08-24 0.3~0.4 파일럿 1종 실측으로 7절을 추정에서 교체, 45종 450행 전량 실측 golden-set-v1.run.json
2026-08-24 0.1~0.2 최초 작성. 초안 1,000건 실측, 교차 방식 확정, 기각한 대안 5건, 티켓 7건 분해. 0절 목표와 완료 기준 신설 2026-08-24 싱크 결정 3번 + 황현진: "45종인데 각 분류별로 10개씩 제작할거야. 총 450개로"

↑ 목차로 · 5부 머리로

29장생성 모델 파인튜닝 계획 계획착수 전

원문 plan/finetuning-plan.md GitHub 에서 보기 9 KB 원문 제목 「Summary 생성 모델 파인튜닝 계획」

이 문서의 역할 Visual Summary 생성기를 파인튜닝한다면 무엇을 데이터로 쓰고 무엇을 사람이 판정하는지의 설계다. 지금 착수해야 하는 부분과 나중 것을 갈라 둔다.

상태 DRAFT — 보류 (2026-09-05 정원석). 9월 말까지의 시급한 범위 밖이다. 아래 어느 것도 착수되지 않았다. 규모와 일정 수치는 제안이고 합의된 값이 아니다.

오너 정원석 (MORDOR) · 최종 갱신 2026-09-05 (보류 결정 — 9월 말 범위 밖)

관련 schema-v0.5.md 규약 · markdown-summary-samples.md 실사용 입력과 골든셋 · content-binding-contract.md 대치 구간 · pipeline-flow.md 전체 흐름 · TODO.md


0. 전제 둘#

3단계가 "실행하지 않음"으로 서 있다. Visual Summary 생성기는 아직 없다(README.md 1절, MOR-1775). 이 계획은 생성기가 생기는 시점을 위한 준비다. 다만 데이터 확보만은 지금 시작할 값이 있다. 아래 3절의 역생성 재료가 이미 다 있고, 시간이 지나 색인이 바뀌면 다시 만들어야 한다.

스키마 확정이 선행 조건이었고, 2026-08-02 에 v0.5 로 확정됐다(schema-v0.5.md). 학습 데이터는 스키마에 묶인다. 필드 이름 하나가 바뀌면 데이터를 다시 만들어야 한다. '표현 가능성을 먼저 열고 스키마는 고정한다' 원칙(v0.5 1절 "중첩은 평탄화된다", 원 논의는 schema-v0.4.md 8-1절)이 대량 생성 전에 지켜야 할 조건이다.


1. 무엇을 고치는 튜닝인가#

먼저 목표를 좁힌다. 규약 3절이 LLM 이 YAML 을 직접 쓰고, 검증은 JSON 변환 후 스키마 검증으로 수행하기로 정했다(2026-08-02). 형식 위반은 이 검증이 걸러 재생성으로 처리하므로, 튜닝이 고칠 것은 '스키마는 맞는데 내용이 틀린 것'이다.

실패 유형 자동 검증이 거르나 튜닝이 고치나
필드 이름 오타, 타입 불일치, enum 밖의 값 거른다 (재생성) 대상 아님
component.type 을 잘못 고른다 (표인데 list 로) 못 거른다 고친다
entity 를 잘못 쪼갠다 (한 항목을 둘로, 둘을 하나로) 못 거른다 고친다
값과 단위를 label 문자열에 남긴다 (value 와 unit 으로 안 뺀다) 못 거른다 고친다
relations 나 dataset 을 놓친다 못 거른다 고친다
슬롯 수가 컴포넌트가 감당할 범위를 넘는다 못 거른다 부분적으로 고친다

이 영역이 제품 품질을 가른다는 근거가 이미 있다. 2026-07-26 BAD 원인 분석에서 BAD 의 85% 가 렌더링이 아니라 컴포넌트와 내용이 안 맞는 문제로 나왔다. judge 채점 16,819행 집계와 BAD 200건 직접 분류의 결과이고, 세부 원인은 텍스트 용량 초과, 매핑과 위계 오류, 슬롯 수 불일치, 대치 실패였다(Slack). 위 표의 아래 다섯 줄이 이 넷을 앞단에서 미리 막는 자리다.


2. 데이터 세 갈래#

갈래 무엇을 담보하나 확보 방법 사람 손
A. 역생성 대치 가능성. 컴포넌트에서 거꾸로 뽑았으므로 그 structure 를 받을 컴포넌트가 최소 하나 존재한다 기계. 재료가 이미 있다 표본 검수만
B. 실사용 로그 입력 분포. 사용자가 실제로 무엇을 쓰는지 이미 있다 (7,208 block) 정답 structure 를 만들려면 필요
C. 사람 라벨 정오 기준. 무엇이 좋은 출력인지 새로 만든다 전부

A 와 B 는 서로를 보완하는 관계이지 대체 관계가 아니다. A 만 쓰면 모델이 기존 템플릿이 표현할 수 있는 구조만 만들게 된다. 두 분포가 실제로 크게 다르다.

어느 쪽 List ListingTable ComparisonTable
색인 쪽 (design object 83,771건) 81.1% 0.5% 0.2% 이하
질의 쪽 (7,208 block) 31.96% 15.08% 7.24%

색인 쪽 근거는 README.md 3절, 질의 쪽은 markdown-summary-samples.md 1절이다. A 로만 학습하면 모델이 리스트로 수렴한다. B 가 그 반대편을 잡는다.


3. A. 역생성: 컴포넌트에서 거꾸로 만든다#

3.1 이 방향에만 있는 이점#

정방향으로 생성한 structure 는 그것을 받을 컴포넌트가 있는지 알 수 없다. 규약 5절 fallback 규칙 1번이 "성립 조건 미충족이면 후보에서 빠진다"이고, venn, tree, nested_scope, pyramid 는 각 164건 이하, 데이터 차트는 각 51건 이하다(README.md 3절). 정방향 데이터에는 후보가 0이 되는 구조가 섞여 든다.

역생성은 그 문제가 없다. 컴포넌트 X 에서 뽑은 structure 는 최소한 X 가 받는다.

3.2 두 단계로 나뉜다#

mermaid
flowchart LR
    C["컴포넌트<br/>design_object + RLSC"]
    S["structure<br/>(Visual Summary)"]
    I["자연어 입력문"]
    C -- "① 구조 역추출" --> S
    S -- "② 입력문 역생성" --> I
    I -. "학습 pair" .-> S

학습에 필요한 것은 (입력문, Visual Summary) 쌍이므로 두 단계가 다 있어야 한다. ①은 규칙과 LLM 을 섞을 수 있고, ②는 순수 LLM 이다. ②가 ①보다 쉽다. 구조를 자연어로 푸는 방향은 정보를 버리는 방향이라 틀릴 여지가 적다.

3.3 ①의 재료는 이미 다 있다#

재료 어디서 오나 structure 의 무엇이 되나
RLSC 역할 트리 (Role.Element.*) Structuriser entity.label(Title), entity.items(Description)
CompSignature.textMap comp-sign buildCompSignatures 실제 텍스트 내용
CompSignature.structureHash 같음 entity 개수와 중첩
semantic tag (v7) design_object_semantic_tags component.type 후보
CORE / DESC / DECO / OUT 신영빈님 노드 분류 (Steps 대상 구현, 2026-07-31) 어느 노드가 label 이고 어느 것이 버릴 장식인지
sequence · rank · attach · span MOR-1856 order-tagger 순서형 카테고리의 entity 순서
Chart data, seriesAxis RLSC dataset.series, dimensions

CORE/DESC/DECO/OUT 이 여기서 두 번째 쓸모를 갖는다. 원래는 대치할 때 어느 슬롯을 채우는가의 문제였는데, 역생성에서는 어느 노드를 structure 에 담고 어느 노드를 버리는가의 기준이 된다. 다만 이 태그의 정의가 확정된 것이 아니다. 특히 OUT 이 경계 밖인지 오염자인지가 미확인이다(content-binding-contract.md 1.1절).

3.4 어디서부터 하나#

Steps 부터다. 이유는 재료가 가장 두껍다. order-tagger 가 응집 PASS 911건 전량에 sequence, rank, attach, span 을 부여했고, 마커 보유 540건 중 534건이 기하 순서와 일치한다(99%, 기준 95%). 노드 분류도 Steps 대상으로 먼저 구현됐다.

그다음은 질의 쪽 수요가 두꺼운 순서, 곧 ListingTable(15.08%)과 ComparisonTable(7.24%)이다. 이 둘의 structure 표현 수단은 v0.5 matrix 로 생겼다(2026-08-02, capability-table.md 2.1절). 남은 전제는 RLSC 대응 확인이다(같은 문서 7절).


4. round-trip 검증: 사람 없이 대량으로 거른다#

역생성 결과의 품질을 사람이 다 볼 수는 없다. 자동 신호가 필요하다.

역생성한 structure 를 다시 검색에 넣어 원본 컴포넌트가 상위 후보로 돌아오는지 본다. 돌아오면 그 structure 는 그 컴포넌트를 제대로 기술한 것이다.

컴포넌트 X  →  structure S  →  buildCompSearchData(S)  →  rankCompCandidates
                                                              ↓
                                              X 가 상위 k 에 있나?

comp-sign v4.3.8 이 이미 네 함수를 다 갖고 있으므로 별도 구현 없이 돌릴 수 있다(content-binding-contract.md 2절).

이건 필요조건이지 충분조건이 아니다. 검색 쪽이 잘못돼 있으면 나쁜 structure 도 통과한다. 반대로 통과 못 한 것은 둘 중 하나다. 역생성이 틀렸거나, 검색이 그 구조를 못 찾은 것이다. 후자라면 그것대로 검색 쪽 결함 목록이 된다. 즉 이 검증은 실패해도 산출물이 남는다.


5. B. 실사용 로그: 분포를 맞춘다#

이미 있는 것은 다음이다(sources.md 3절).

자산 규모 라벨
part1~part3 원본 로그 7,208 block (5,371 입력) 없음
시멘틱 쿼리 v2 검증 시트 1,051 block taxonomy_major·taxonomy_minor·판단근거·점수
골든셋 90건 (P0 50 · P1 40, 17 중분류) 위와 같고 case_id 부여

빠진 것은 정답 structure 다. 이 자산들은 카테고리 라벨까지만 있고 YAML 이 없다. 그래서 B 는 두 가지 용도로만 쓴다.

  1. 입력 분포를 A 쪽 데이터에 섞을 비율의 근거로 쓴다
  2. 카테고리 라벨을 component.type 정답으로 쓴다 (structure 내부는 별도)

정답 structure 가 필요한 부분은 C 로 넘어간다.


6. C. 사람 라벨링 설계#

6.1 무엇을 판정하나#

Visual Summary 는 자유 텍스트가 아니라 구조다. '좋다/나쁘다' 한 값으로 받으면 무엇을 고쳐야 할지 알 수 없다. 항목을 나눠 받는다.

항목 판정 왜 나눠 받나
1. 카테고리 component.type 이 맞나 이미 라벨 체계가 있다 (v7 중분류)
2. 분할 entity 를 옳게 쪼갰나 슬롯 수 불일치의 원인
3. 슬롯 배정 value·unit·label·items 가 제자리인가 값이 문자열에 남는 실패가 여기
4. 부가 구조 relations·dataset 을 놓치거나 잘못 붙이지 않았나 가산 정보라 놓쳐도 형식은 통과한다
5. 중요도 importance 배정이 내용과 맞나 필드가 확정되면 (지금은 열린 결정)

5번은 아직 스키마에 없다. 4-1절 등급 논의와 묶여 있다.

6.2 절대 점수보다 쌍 비교#

같은 입력에 대한 두 출력을 놓고 어느 쪽이 나은지 고르게 한다. 절대 점수는 라벨러마다 기준이 흔들리는데 쌍 비교는 덜하다. 그리고 쌍 비교 결과는 DPO 계열 학습에 그대로 쓸 수 있다.

항목별 판정(6.1)과 쌍 비교를 함께 받는다. 항목별 판정은 무엇을 고칠지 알려 주고, 쌍 비교는 학습 신호가 된다.

6.3 어디에 사람을 쓰나#

라벨링은 비싸므로 어디에 쓸지가 설계의 핵심이다. 83,771건 전량은 대상이 아니다.

용도 규모 (제안) 왜
평가셋 골든셋 90건 + 확장 튜닝 전후를 재는 자다. 학습에 절대 넣지 않는다
역생성 표본 검수 카테고리마다 소량 round-trip 이 못 잡는 오류 유형을 찾는다
선호쌍 실패가 잦은 카테고리에 집중 균등 배분하지 않는다

골든셋 90건이 17 중분류만 덮는다. 실사용에서 관측된 것은 38 중분류다(markdown-summary-samples.md 2절). 평가셋 확장이 라벨링의 첫 항목이다.


7. 학습 방식#

SFT 를 먼저 하고, 선호쌍이 모이면 DPO 계열을 얹는다. 순서가 그런 이유는 SFT 가 없으면 비교할 두 출력의 품질이 둘 다 낮아 선호쌍이 정보를 거의 담지 못하기 때문이다.

단계 데이터 무엇이 나아지나
SFT A(역생성, round-trip 통과분) + B 분포 보정 슬롯 배정과 분할의 기본기
선호 학습 C 쌍 비교 애매한 경계의 판단

튜닝 없이 되는지를 먼저 본다. structured output 에 few-shot 예시를 붙인 것이 기준선이다. 튜닝의 이득은 그 기준선을 골든셋에서 재야 나온다.


8. 평가#

종류 지표 기준
오프라인 골든셋 카테고리 정오 튜닝 전 기준선 대비
오프라인 6.1 항목별 정확도 항목마다 따로 본다. 합산하지 않는다
오프라인 round-trip 재현율 생성한 structure 가 후보를 0으로 만들지 않는가
온라인 judge 채점 BAD 비율 16,819행 집계와 같은 방식
온라인 BAD 원인 분류 85% 를 차지하던 유형이 줄었나

마지막 줄이 이 계획의 진짜 목표다. 카테고리 정오가 올라도 대치 결과가 안 좋아지면 의미가 없다.


9. 착수 순서와 선행 조건#

# 할 일 선행 조건 지금 가능한가
1 평가셋을 38 중분류로 확장 없음 가능
2 few-shot 기준선 측정 1 가능 (v0.5 확정, 2026-08-02)
3 Steps 역생성 파일럿 노드 분류 정의 확인 (스키마는 v0.5 확정) 부분 가능
4 round-trip 파이프라인 구축 3 comp-sign 이 이미 있어 구현은 작다
5 카테고리 확대 RLSC 대응 확인 (capability-table.md 7절) 표현은 v0.5 matrix 로 확보됨
6 라벨링 도구와 지침 6.1 항목 확정 5번 importance 결정 대기
7 SFT 3·4 -
8 선호 학습 6·7 -

1번만 지금 독립적으로 진행할 수 있다. 나머지는 규약 확정에 걸려 있다.


10. 위험#

분포 편향 2절에 수치로 적었다. A 와 B 의 혼합 비율을 정하지 않고 A 만 늘리면 모델이 list 로 수렴한다.

스키마 이동 0절에 적었다. 데이터가 스키마에 묶이므로 규약이 흔들리는 동안 대량 생성에 들어가면 버린다.

round-trip 의 공모 4절에 적었다. 검색과 역생성이 같은 편향을 공유하면 서로를 통과시킨다. 표본 검수(6.3)가 이걸 잡는 장치다.

라벨 일치도 6.1 의 항목들이 실제로 라벨러 사이에서 재현되는지 확인되지 않았다. 소량으로 일치도를 먼저 재고 항목을 조정한다.

3단계 스킵과의 관계 로드맵상 Visual Summary 생성 자체가 4단계로 밀려 있다. 이 계획을 지금 실행하는 근거는 재료(RLSC, 노드 분류, order-tagger 산출물)가 지금 있고 색인이 바뀌면 다시 만들어야 한다는 것뿐이다. 그 근거가 약하다고 판단되면 1번만 하고 나머지는 4단계까지 미룬다.

↑ 목차로 · 5부 머리로

30장L1/L2 혼합 랭킹 Blueprint 정본L2 조합 펼치기

원문 plan/l1-l2-result-display-blueprint.md GitHub 에서 보기 14 KB


title: 검색 결과 L1/L2 혼합 랭킹 — L2 조합 펼치기 Blueprint description: "L1, L2 결과 상관없이 코스트 낮은 순으로 나열" 결정에 맞춰, L2 후보 조합을 대표 묶음 하나로 합치지 않고 L1과 같은 개수·같은 정렬 규칙으로 펼쳐 한 랭킹에 섞는 작업 계획. Rust(comp-pass-server)와 TS(miricanvas-iui)를 함께 고친다.#

검색 결과 L1/L2 혼합 랭킹 — L2 조합 펼치기 Blueprint#

버전: 0.5 · 작성일: 2026-09-25 · 상태: 구현 중(로컬 구현·테스트 완료, PR 전. §6 정책 확인은 2026-09-28 예정) · 구현 위치: MOR-2546 — miricanvas-iui 저장소 rust/comp-pass/crates/comp-frame-core/src/document_merge.rs, rust/comp-pass/crates/comp-pass-server/src/document_search.rs, src/comp-frame/documentSearch.ts, src/comp-frame/FrameGroupCard.tsx, src/comp-frame/useUnifiedSearch.ts, packages/comp-frame-client/

1. 배경#

2026-09-22 15:09 Slack 결정(C09N92LC7S9/p1790057360079629, 최예지):

L1,L2 전시 방식 1번으로 결정해서 전달드립니다.

  • L1, L2 결과 상관없이 코스트가 낮은 순서대로 상위에 나열해서 보여준다.
  • L2 점수 계산 기준은 **'평균'**으로 적용한다.

MOR-2519(Done, miricanvas-iui PR #1183)가 반영한 것은 정렬 키(L1/L2를 한 목록에서 정렬)와 L2 점수식(합계 → 평균) 두 가지다.

2. 문제#

L2 문서 하나는 랭킹에 항목 하나로만 들어간다. 반면 L1 문서 하나는 항목을 최대 top개(결과 수, 기본 50) 낸다. 두 레벨이 같은 조건에서 비교되지 않는다.

2026-09-25 /comp-frame 실측(backend=remote, rankMode=tier_pool, cleanFirst=above_tier):

  • 메타 줄: L1: {컴포넌트 1, 후보 124} · L2: {컴포넌트 2, 후보 3253} · 항목 22개
  • 목록: 1등만 L2(cost 4.00, 슬롯 2개)이고 2등부터는 전부 L1이다.

L2의 다른 조합(예: 슬롯1 2위 + 슬롯2 1위)은 랭킹에 따로 오르지 못하고, 1등 카드 안의 슬롯별 대안 <select>에서만 보인다.

원인이 되는 규칙은 두 구현에 똑같이 있다.

  • TS src/comp-frame/documentSearch.ts:519-520: "a ≥2-component one yields one GroupEntry … (one group per level-set, never one per result combination), with every slot defaulted to its own top-1"
  • Rust rust/comp-pass/crates/comp-frame-core/src/document_merge.rs:452 search_document_levels: 위 규칙을 1:1로 옮긴 것

이 규칙은 09-22 결정 전 정책("L2의 검색 후보들을 조합한 대표 묶음을 하나만 노출한다", 홍지혜 2026-09-18)에서 나왔다. 09-22 결정은 이 정책을 명시적으로 폐기하지 않았지만, "L1, L2 결과 상관없이 코스트 순"과 충돌한다.

카드 레이아웃(groups=slide/expand)은 이 문제와 관계가 없다. v0.1~v0.2는 원인을 카드 레이아웃으로 잘못 짚었다(맨 아래 Changelog).

3. 목표#

  • L2(및 평탄화된 L3) 문서 하나가 **L1과 같은 상한(top)**까지 조합을 GroupEntry로 낸다. 각 조합은 현재 정렬 규칙(§4.2) 순서로 만들고, L1 SingleEntry와 함께 rankEntries/rank_entries로 정렬한다.
  • Rust와 TS가 같은 결과를 낸다. 향후 miricanvas-web-2가 호출하는 쪽은 Rust(comp-pass-server)이므로 Rust가 기준 구현이다. TS(iui PoC의 local 백엔드)는 parity를 맞춘다.
  • 조합이 각자 카드가 되므로, L2 카드 안의 슬롯별 대안 드롭다운을 없앤다(§4.4).

Non-goals: 카드 디자인은 바꾸지 않는다. 조합 하나를 기존 FrameGroupCard 한 장으로 보여준다. 프로덕션 UI는 miricanvas-web-2에서 따로 만든다.

4. 설계#

4.1 조합 개수 — L1과 같은 top#

채택: L2 문서당 조합 상한 = top. L1 문서당 항목 상한과 같은 값이며, 옵션을 새로 만들지 않는다.

L1 문서가 SingleEntry를 최대 top개 내므로, L2 문서도 GroupEntry를 최대 top개 내면 두 레벨이 같은 수량으로 경쟁한다. 상한이 아예 없으면 조합 수가 top^슬롯수까지 늘어난다(top=50일 때 슬롯 2개면 2,500개, 3개면 125,000개).

기각한 후보 이유
상한 없이 전체 조합 슬롯 3개에서 조합이 125,000개다. 응답 크기와 정렬 비용이 감당되지 않는다
별도 옵션 K(가칭 groupCombos) L1과 다른 상한을 두는 셈이라 "L1, L2 상관없이"에 어긋난다
슬롯별 같은 순위끼리만 묶기(1위+1위, 2위+2위 …) 정렬 순서를 보장하지 않는다. 슬롯1 1위 + 슬롯2 5위가 2위+2위보다 앞서야 하는 경우를 놓친다

4.2 조합의 순서 — 현재 정렬 규칙 그대로(tier는 max, tier가 cost보다 먼저)#

현재 기본값은 rank_mode: 'tier_pool', clean_first: 'above_tier'다(packages/comp-frame-shared/src/types.ts:496-498). 이때 rankEntries의 정렬 키 순서는 다음과 같다.

  1. 온전한가(entryIsClean): 선택한 슬롯 전부가 unauthored_count === 0이면 온전하다
  2. tier(entryTier): 선택한 슬롯 tier의 max
  3. cost: 선택한 슬롯 cost의 평균
  4. level(L1 < L2 < L3)

L2의 tier는 max로 유지한다(결정됨). #mordor스쿼드 2026-09-22~23 스레드에서 이강윤이 "점수처럼 tier도 평균으로 바꿔야 하는지" 물었다. 정원석·이강윤·홍지혜가 max 유지로 의견을 모았고, 이강윤이 "tier 작업은 수정하지 않고 그대로 유지"로 닫았다. 근거는 홍지혜(09-23 08:25)의 말이다.

대부분은 잘 맞지만 하나의 컴포넌트가 크게 fallback되어 원래 type과 많이 달라진 결과는, 평균 Tier로 보면 차이가 희석될 수 있지만 실제 사용자가 느끼는 결과는 하나의 크게 어긋난 컴포넌트 때문에 전체 Visual Summary의 의미나 디자인 의도가 훼손될 가능성이 더 크다 … Tier는 가장 크게 fallback된 컴포넌트를 기준으로 Max를 유지하고, 동일한 Tier 안에서 세부적인 품질 차이는 Penalty Score로 비교

정원석은 같은 스레드(09-23 00:59)에서 이 규칙을 다음과 같이 정리했다. "같은 fallback 상태에서 score를 겨루는 경우라면 mean, 다른 fallback 상태라면 max로 penalty를 강하게." 이 설계는 이 규칙을 바꾸지 않는다. 조합을 만드는 순서만 이 규칙에 맞춘다.

조합을 cost만 보고 뽑으면 이 순서와 어긋난다. cost가 싸지만 tier 2인 조합이 상한 안의 자리를 먼저 차지해, tier 1 조합이 목록에 못 들어갈 수 있다. 그래서 조합을 다음 순서로 만든다.

  1. 조합의 (온전 여부, tier)는 슬롯별 값의 "가장 나쁜 쪽"(max)이다. 그러므로 "(온전, tier 1) 조합"은 모든 슬롯에서 (온전, tier 1) 후보만 골라 만든 조합과 정확히 같다.
  2. (온전, tier) 칸을 정렬 순서대로 돈다: (온전, 1) → (온전, 2) → (온전, 3) → (온전 아님, 1) → …
  3. 칸마다 슬롯별 후보를 "그 칸 이하" 값으로 거른다. 거른 후보로 만든 조합 중 칸 값이 정확히 그 칸인 것만, 평균 cost가 작은 순으로 뽑는다(k-smallest-sums, 우선순위 큐 O(top·S·log top)).
  4. 모두 합쳐 top개가 되면 멈춘다.

rank_mode: 'tier_penalty', clean_first: 'off'/'within_tier'에서는 키 순서가 달라진다. 조합 생성 순서는 항상 그 요청의 rankEntries 정렬 키를 그대로 따른다. 규칙이 두 벌로 갈라지지 않도록, 정렬 키를 계산하는 함수 하나를 조합 생성과 rankEntries가 함께 쓴다.

4.3 한쪽 슬롯만 다른 조합이 연달아 나오는 문제 — 정책 없음#

슬롯이 2개인 L2에서 슬롯1의 1위 후보가 강하면, 순수 정렬만으로는 (슬롯1 1위 + 슬롯2 1위), (슬롯1 1위 + 슬롯2 2위), … (슬롯1 1위 + 슬롯2 10위)가 연달아 목록을 채울 수 있다. 사용자가 보기에는 절반이 같은 카드 10장이다.

Slack·Confluence에서 이 문제의 정책은 찾지 못했다. 확인한 관련 발언은 셋이다.

  • 홍지혜(2026-09-18 16:29, #px스쿼드_내용시각화셀 스레드): L2를 대표 묶음 1개로 정한 이유가 정확히 이 문제였다. "한 슬롯에 들어가는 후보들이 유사한 경우가 많았습니다. 그래서 L2의 가능한 조합들을 나열하면 일부 컴포넌트만 다른 유사한 결과가 대량으로 만들어져, 이를 개별 카드로 나열하는 대신 슬롯별 최상위 후보를 선택한 대표 묶음 하나를 먼저 보여주고 사용자가 원하는 슬롯만 교체할 수 있게 한 것"
  • 김수현(2026-09-16 18:53, #mordor스쿼드 메시지) → 최예지: "L1, L2 섞어서 보여주는 것, 전체보기에서 첫장 중복없게 하는 것에 대한 로테이션 정책"을 대응 과제로 넘겼다. 이후 결론은 찾지 못했다.
  • 09-22 최예지 결정: 정렬 순서와 점수식만 다룬다. 이 문제는 언급하지 않았다.

Confluence 2026.W38-39 Visual Summary 검색/대치 PoC 개선점 리스트업의 MOR-2483 항목도 점수·tier·clean 정규화만 다루고 이 문제는 다루지 않는다.

그러므로 09-22 결정을 따라 조합을 펼치면, 09-18에 "대표 1개"로 막아 두었던 문제가 다시 열린다. 결정이 필요하다(§6).

후보 동작 목록에서 보이는 것 비용
(가) 제어 없음 순수 정렬 슬롯1이 같은 조합이 연달아 나올 수 있다 없음
(나) 슬롯 후보 1회 사용 한 슬롯의 한 후보(`do_id node_id`)는 목록 전체에서 조합 1개에만 쓴다 조합끼리 모든 슬롯이 다르다. 조합 수가 슬롯별 후보 수의 최솟값으로 줄어든다
(다) 연속 제한 같은 슬롯 후보를 쓴 조합이 목록에서 N칸 안에 다시 나오지 않게 뒤로 민다 비슷한 조합이 흩어져 나온다. "코스트 순"이 부분적으로 깨진다 중간

채택(2026-09-25, 정원석): 일단 (가)로 진행한다. 이후 순서는 다음과 같다.

구현은 (가)로 먼저 한다. 실제 프리셋에서 "슬롯 하나만 다른 조합이 연속 몇 장 나오는지"를 실측해, 그 숫자를 김수현·최예지께 드리고 (나)/(다)를 정한다. 09-16 로테이션 정책이 이미 이 자리를 가리키고 있으므로, 새 규칙을 만들기보다 그 정책의 결론을 받는 것이 순서다. (나)와 (다)는 정렬 뒤에 목록을 걸러내는 단계로 넣을 수 있다. 그래서 (가)로 구현해도 다시 뜯어고칠 일은 없다.

실측(2026-09-25, 로컬 구현, /comp-frame?preset=csv-2&backend=local&rankMode=tier_pool&cleanFirst=above_tier&top=50).

  • 메타 줄: L1: {컴포넌트 1, 후보 2009} · L2: {컴포넌트 2, 후보 3504} · 항목 66개
  • 1~50등은 전부 L2 조합이다(L2 문서 하나의 상한 top=50을 다 채웠다). L1은 51등부터 나온다.
  • 1~41등은 슬롯2가 모두 같은 컴포넌트(do_id 11259566)이고 점수도 모두 4.00이다. 슬롯1만 41개가 서로 다르다. 42~50등은 점수 8.00이다.
  • 즉 (가)로는 상위 41장이 "절반이 같은 카드"다. 09-18에 홍지혜가 "대표 1개"로 막았던 이유가 이 프리셋에서 그대로 재현된다.

L2 문서가 컴포넌트 1개로 생성된 프리셋(csv-5)은 L2가 L1과 같은 단일 항목으로 섞여 나온다(1등 L1, 6등 L2 …). 혼합 정렬 자체는 의도대로 동작한다.

4.4 슬롯별 대안 드롭다운 — 없앤다#

지금 L2 카드 안의 슬롯별 <select>는 랭킹에 오르지 못한 나머지 조합을 사람이 찾아보기 위한 도구였다. 조합이 각자 카드로 랭킹에 오르면 이 역할이 목록 자체로 옮겨간다. 그래서 드롭다운을 없앤다.

  • 이지선(2026-09-18 10:40, #px스쿼드_ppqd): "L2에서는 다른 디자인 보기가 나오고 L1에서는 안 나오는 게 사용자 입장에서 좀 헷갈릴 것 같긴 한데.. (그래서 그냥 L2에서도 다른 디자인 보기를 안 하는 게 낫지 않을까..)". 드롭다운이 없어지면 L1과 L2 카드의 조작 방식이 같아진다.
  • 제거 대상: FrameGroupCard.tsx의 AlternateSelect와 altSwitcher 전달, useUnifiedSearch.ts:476의 selectAlternate, documentSearch.ts의 selectSlotAlternate, 그리고 이 함수들이 부르던 재정렬 경로.
  • 포커스(상세) 화면은 남는다. 슬롯을 나란히 펼쳐 보여주는 기능은 그대로이고, 드롭다운만 빠진다.

4.5 응답 형식 — 조합마다 선택된 후보 하나씩만#

드롭다운이 없어지면 카드가 슬롯별 후보 목록 전체를 들고 있을 이유도 없어진다. 조합 카드는 슬롯마다 자기가 쓴 후보 1건만 싣는다. 이러면 L1 카드와 같은 크기가 된다.

지금 응답의 WireResultSlot(packages/comp-frame-client/src/types.ts)은 results(후보 최대 top개)와 selected_index(항상 0)를 싣는다. 필드 모양은 그대로 두고, results에 선택된 후보 1건만 담고 selected_index는 0으로 보낸다. 타입이 바뀌지 않으므로 기존 소비 코드가 깨지지 않는다. 다만 "results에 대안이 다 들어 있다"는 README 계약 문구는 바뀌므로, README를 고치고 comp-frame-client 버전을 올린다(changeset).

크기(결과 1건 약 800B, Rust document_merge.rs 주석의 추정치 기준, 슬롯 2개·top=50):

방식 L2 한 문서의 응답
지금 방식을 조합 50장에 그대로 적용(카드마다 후보 전체) 50장 × 슬롯 2 × 후보 50 × 800B ≈ 4MB
채택: 카드마다 선택된 후보만 50장 × 슬롯 2 × 800B ≈ 80KB

4.5.1 성능 실측 (2026-09-25)#

검색 호출은 늘지 않는다. 컴포넌트별 검색 호출 수는 그대로다(csv-2는 변경 전후 모두 "검색 3회"). 조합은 이미 받은 후보로 만들기 때문에 DB·서버를 더 부르지 않는다. csv-2 메타 줄의 L1 5,870ms·L2 12,272ms는 Supabase 라이브 검색 시간이고 이번 변경과 무관하다.

조합 계산은 L2 문서 하나에 1ms 미만이다. 같은 머신(개발 노트북)에서 슬롯당 후보 50개, top=50, tier_pool/above_tier, tier·온전 여부를 섞은 합성 입력으로 문서 하나당 평균을 쟀다. TS는 vitest 안에서 50회, Rust는 cargo test --release로 200회 반복했다.

슬롯 수 TS (backend=local) Rust release (comp-pass-server)
2 0.137 ms 0.075 ms
3 0.120 ms 0.069 ms
4 0.170 ms 0.091 ms

슬롯 수가 늘어도 시간이 거의 같다. 전체 조합(50^슬롯수)을 만들지 않고 필요한 약 top개만 뽑고 멈추기 때문이다(§4.2).

응답 크기는 거의 같다. 변경 전 L2 카드 1장은 슬롯 2개 × 후보 50개를, 변경 후에는 카드 50장이 슬롯 2개 × 후보 1개를 싣는다. 어느 쪽이든 결과 100건이다.

늘어난 것은 화면의 카드와 미리보기 iframe이다. csv-2 기준으로 항목은 약 17개(L1 16 + L2 1)에서 66개(L2 50 + L1 16)가 됐다. L2 카드는 슬롯마다 미리보기 iframe을 하나씩 띄우므로, iframe은 약 18개에서 약 116개가 된다(카드 수로 계산한 추정치, 실측 아님). 썸네일은 화면에 보이는 카드부터 큐로 불러오므로(onIframeInView) 첫 화면이 뜨는 시간에는 큰 차이가 없을 것으로 추정한다. 스크롤을 내리면 불러올 썸네일은 많아진다. 첫 화면·전체 썸네일 로딩 시간은 아직 재지 않았다. §4.3의 유사 조합 규칙이 (나)나 (다)로 정해지면 L2 카드 수가 줄어 이 부담도 함께 줄어든다.

4.6 화면#

  • 결과 카드의 React key는 지금 문서 기준이라, 같은 문서에서 나온 조합 카드끼리 key가 겹친다. key에 슬롯별 후보 식별자(do_id|node_id 벡터)를 넣는다.
  • FrameGroupCard의 모양(slide/expand)은 바꾸지 않는다.

5. 결정·근거 요약#

항목 결론 근거
조합 상한 L1과 같은 top 정원석 2026-09-25: "L1 수량이랑 같이 비교하면 되겠다"
L2 tier max 유지 #mordor스쿼드 09-22~23 스레드(홍지혜·정원석·이강윤)
조합 생성 순서 현재 정렬 키(온전 → tier max → cost 평균) 위 두 항목에서 따라 나온다
대안 드롭다운 제거 정원석 2026-09-25, 이지선 2026-09-18
응답 조합마다 선택된 후보만(필드 모양 유지) 드롭다운 제거에서 따라 나온다
유사 조합 연속 노출 일단 (가) 제어 없음. 정책은 2026-09-28 확인 정원석 2026-09-25, §4.3, §6

6. 결정 필요#

  1. 한쪽 슬롯만 다른 조합이 연달아 나오는 것을 제어할지(§4.3의 가/나/다). 구현은 (가)로 진행한다. 정책은 2026-09-28(월)에 김수현(09-16 로테이션 정책 제기)과 최예지(09-22 결정)에게 확인한다. 확인 요청에는 실측 수치를 붙인다.

7. 실행 항목#

  • Jira 서브태스크 생성: MOR-2546(MOR-2477 산하, MOR-2519와 Relates)
  • Rust comp_frame_core::document_merge
    • 정렬 키 계산 함수를 rank_entries에서 분리해, 조합 생성과 정렬이 함께 쓴다
    • (온전, tier) 칸별 k-smallest-sums 조합 생성, 문서당 top_n 상한
    • 유닛 테스트: 상한이 1이면 현재 동작과 같다(회귀) / tier 1 조합이 cost가 더 싼 tier 2 조합보다 먼저 나온다 / 슬롯 3개에서도 생성 조합 수가 top_n을 넘지 않는다
  • Rust comp-pass-server document_search.rs: 조합 카드의 results를 선택 후보 1건으로 + tests/http.rs
  • TS documentSearch.ts: 같은 로직 + documentSearch.test.ts
  • TS/Rust parity: 기존 정합성 테스트(PR #1198)는 컴포넌트 단위 검색만 비교해 조합을 못 잡는다. 공용 골든 rust/comp-pass/tests/golden/group_combinations.json(90건)을 새로 두고 TS(groupCombinations.golden.test.ts)와 Rust(document_merge.rs)가 같은 결과를 내는지 본다
  • 드롭다운 제거(§4.4): AlternateSelect, selectAlternate, selectSlotAlternate, 관련 테스트
  • 화면: 결과 카드 key에 슬롯 후보 식별자 반영(§4.6)
  • comp-frame-client: README 계약 문구 수정, 버전업(changeset)
  • §4.3 실측: csv-2에서 1~41등이 슬롯2 동일(2026-09-25). §6 결정 요청에 첨부
  • 배포: component-search-api-develop는 workflow_dispatch 수동 실행이다. main 머지만으로는 원격 서버가 바뀌지 않는다
  • 검증: /comp-frame에서 backend=local과 backend=remote를 같은 gen으로 열어 목록 순서가 같은지 본다. 메타 줄 숫자가 아니라 목록에 L2 카드가 몇 장, 몇 등에 섞여 있는지를 본다. MOR-2519 QA는 메타 줄만 봐서 이 누락을 잡지 못했다(조사 기록 §3)

8. 관련 자료#

Checklist#

  • 결정문·구현 범위 대조 (2026-09-25)
  • 원인 코드 위치 확정: TS documentSearch.ts:519-520, Rust document_merge.rs:452 (2026-09-25)
  • 조합 상한 = top, 순서 = 현재 정렬 키(tier max), 드롭다운 제거, 응답은 선택 후보만 (2026-09-25, 정원석)
  • tier max 유지 결정 출처 확인 (2026-09-25)
  • 유사 조합 연속 노출 정책 탐색: Slack·Confluence에 없음 확인 (2026-09-25)
  • 유사 조합은 일단 (가)로 진행 (2026-09-25, 정원석)
  • §6 정책 확인 (2026-09-28 예정)
  • 로컬 구현·테스트 (2026-09-25)
  • 성능 실측: 조합 계산 1ms 미만, 검색 호출 수 불변 (2026-09-25, §4.5.1)
  • 썸네일 로딩 시간 전후 비교(필요 시)
  • PR·배포·원격 검증

Changelog#

날짜 버전 변경 근거
2026-09-25 0.1 초안. 카드 레이아웃(A/B/C/D안) 문제로 정의 정원석: "L2가 뭉쳐져서 단독으로 나온다"
2026-09-25 0.2 groups 기본값이 slide라는 이유로 "추가 구현 불필요"로 결론 에이전트 판단(잘못됨)
2026-09-25 0.3 원인을 "L2가 조합 1개로만 랭킹에 들어가는 규칙"으로 다시 정의. Rust·TS 조합 펼치기 설계로 교체 정원석: "그 자동 슬라이드/펼침 옵션이 문제가 아니라니까" + 실측(L2 후보 3,253개 중 목록 노출 1개)
2026-09-25 0.4 조합 상한을 L1과 같은 top으로. 생성 순서를 현재 정렬 키로. 결정문 날짜를 09-14에서 09-22로 정정 정원석: "K값을 안 두는 게 맞지 않나", "현재 기준이면 tier가 먼저 아닌가"
2026-09-25 0.5 tier max 유지 결정의 출처(09-22~23 스레드)를 적음. 드롭다운을 없애고, 그에 따라 응답을 "조합마다 선택된 후보만"으로 단순화(v0.4의 문서 단위 후보표 B안 폐기). 유사 조합 연속 노출 정책이 Slack·Confluence에 없음을 확인하고 §4.3·§6 미결로 둠. v0.4의 "L1-L2 중복 기준" 질문은 삭제(문제 아님) 정원석: "tier 조합 계산은 max로 하자고 한 부분이 있었어", "이번에 바꾸면 드롭다운이 왜 있어", "l1 vs l2 중복은 문제가 아니야"
2026-09-25 0.5 유사 조합 연속 노출은 일단 (가) 제어 없음으로 진행. 정책 확인은 2026-09-28로 잡음 정원석: "일단 (가)로 진행한다"
2026-09-25 0.5 로컬 구현·테스트 완료(MOR-2546). csv-2 실측(1~41등 슬롯2 동일)을 §4.3에 추가 브라우저 확인(backend=local)
2026-09-25 0.5 성능 실측(§4.5.1) 추가: 조합 계산 문서당 0.07~0.17ms, 검색 호출 수 불변, 응답 크기 유사, 카드·iframe 수 증가 정원석: "검색 속도가 혹시 얼마나 느려진거지", "이 수치도 blueprint에 기록"

↑ 목차로 · 5부 머리로

31장속성 골든셋 편집 UI Blueprint 정본I.I.S. 속성 주석 화면

원문 plan/iis-property-annotation-ui-blueprint.md GitHub 에서 보기 8 KB


title: 속성 골든셋 편집 UI Blueprint (I.I.S. Semantic Component Property 사람 확정) description: 경계(4단계)에는 있고 속성(5단계)에는 없는 사람 확정 UI와 골든셋 테이블을 어떻게 만들지 정리한 초안. 판단은 아직 없다.#

속성 골든셋 편집 UI Blueprint#

이 문서의 역할 node-tag-and-property-poc-blueprint.md가 이미 만든 I.I.S. (Semantic Component Boundary/Property) 위에서, 경계에는 있고 속성에는 없는 것 하나를 채우는 방법을 정리한다. 그 하나는 "사람이 속성 값을 고치고 확정해서 재현 가능한 정답으로 남기는 화면과 테이블"이다. 판단은 아직 내리지 않았고, 아래 결정 후보에 확정이 필요하다.

오너 허재영, 황현진 (2026-09-05 정원석 배정), 상태 DRAFT(리뷰 대기), 작성 2026-08-23 (에이전트 초안, 사람 확인 전) 관련 node-tag-and-property-poc-blueprint.md 1절, 2.1절, 4절, 6절 T-7, 9.5절 / property-definition-projection-review-draft.md / capability-table.md 2절


1. 배경#

node-tag-and-property-poc-blueprint.md는 컴포넌트 하나를 노드 트리로 표현한 구조 데이터(RLSC) 위에, 어느 노드 묶음이 어느 의미 카테고리인지를 가리키는 경계 지도(4단계, iis_semantic_component)와, 그 경계 안 노드가 카테고리 고유 속성으로 어떤 값을 갖는지를 가리키는 속성 지도(5단계, 같은 테이블의 properties 칸)를 이미 만들었다. 두 지도 모두 RLSC 스키마 자체는 건드리지 않고 노드 식별자로 가리키는 별도 테이블이다(같은 문서 D-2).

경계 쪽은 LLM이 낸 초안과는 별도로 사람이 확정한 정답을 담는 골든셋 테이블(iis_boundary_annotation, iis_boundary_annotation_node, 2026-08-19)과 그것을 편집하는 화면(BoundaryAnnotationEditor.tsx, /visual-summary/iis-boundary)까지 갖췄다. 속성 쪽은 LLM 추출기(extractProperty.ts, 판 property-v3)가 137개 컴포넌트 전량에 값을 채웠고 채움률 85.4%, 어휘를 벗어나 버려진 값 0건까지 확인됐지만(같은 문서 6절 T-7, 2026-08-22), 사람이 그 값을 고치고 확정해서 재현 가능한 정답으로 남기는 자리가 없다. 지금까지 정확도 확인은 표본 14건을 에이전트가 한 번 눈으로 본 것이 전부이고, 그 문서 576행이 스스로 "정원석 확인 전"이라고 적어 두고 있다.

이 문서는 그 빈 자리 하나를 어떻게 채울지만 다룬다. 이 문서가 다루지 않는 것은 속성 값 자체의 정의(문서 대 코드 불일치, 이종 컴포넌트 구조 대치)로, 이는 property-definition-projection-review-draft.md가 별도로 다루고 있다.

2. 지금 있는 것과 없는 것#

항목 경계 (4단계) 속성 (5단계)
LLM 초안 저장 iis_semantic_component.category iis_semantic_component.properties (jsonb)
사람 확정 화면 BoundaryAnnotationEditor.tsx (카테고리 칩 다중 선택, 상위 경계와 위상 관계 지정) 없음. 검수 화면이 값을 보여주기만 한다
사람 확정 저장소 iis_boundary_annotation + iis_boundary_annotation_node 없음
저장 전 계약 검사 전수 커버리지, 순환 없음, Overlap 0건을 화면에서 미리 검사 없음
정확도 확인 이력 층화 표본 30건과 60건을 사람이 반복 검수, 일치율을 changelog에 실측으로 기록 표본 14건을 에이전트가 1회 눈검수(맞다 12, 애매 1, 틀렸다 1). 사람 확인 전

행 하나가 비어 있는 것이 아니라 줄 전체가 비어 있다. 이 문서는 이 줄을 경계와 같은 모양으로 채우는 안을 낸다.

3. 왜 경계와 같은 모양이어야 하는가#

두 가지 이유가 있다.

같은 원칙을 쓰는 것이 규칙을 두 번 만드는 것보다 싸다. node-tag-and-property-poc-blueprint.md 2.-1절은 "같은 규칙을 두 벌 만들면 반드시 어긋난다"는 것을 PR 리뷰에서 다섯 번 겪은 값비싼 교훈으로 적어 두었다. 경계 골든셋이 이미 낙관적 잠금(저장 시점의 reviewed_at을 확인해 다른 사람이 먼저 저장한 것을 덮어쓰지 않는 장치), LLM 사전 태깅 스냅샷 보존, 판정 상태 넷(accepted/corrected/unsure/needs_correction)을 검증된 형태로 갖추고 있으므로, 속성도 같은 뼈대를 쓰면 이 셋을 새로 설계하지 않아도 된다.

속성은 경계를 포함한다. 같은 문서 1절이 "어느 묶음인지 모르면 그 묶음의 방향을 말할 수 없다"고 이미 정의해 두었다. 실측 사례도 있다. 6절 T-7의 표본 검수에서 틀렸다고 판정된 두 건 중 하나(Hierarchy_HierarchicalTree)는 속성 자체가 아니라 그 경계 안에 실제로는 트리가 없는, 경계가 잘못 잡힌 자리였다(같은 문서 581행). 그래서 속성 골든셋은 경계 골든셋 위에 얹혀야 하고, 독립된 테이블로 따로 설계하면 "이 속성이 틀린 것"과 "이 경계가 틀린 것"을 구분할 수 없게 된다.

4. 결정 후보#

D-1(안, 2026-08-23 정정). 새 테이블을 만들지 않고 기존 골든셋 테이블에 컬럼을 더한다#

처음 이 문서는 iis_boundary_annotation_property라는 새 테이블을 내며, iis_boundary_annotation_node에 컬럼을 더하는 안은 "속성 하나하나의 판정 이력을 칸 단위로 남기기 어렵다"는 이유로 후순위로 미뤘다. 사용자 검토에서 "왜 같이 못 쓰냐"는 지적을 받고 다시 보니 그 이유가 성립하지 않는다.

LLM 초안 테이블 iis_semantic_component가 이미 category와 properties(jsonb)를 한 테이블 한 행에 담고 있다(2026-08-15). 판정 상태(accepted/corrected/unsure/needs_correction)와 preannotation_active 같은 provenance도 전부 design_object 단위지 속성 키 단위가 아니다(iis_boundary_annotation 헤더 행 하나). 즉 지금 있는 검수 단위보다 더 잘게 쪼갤 근거가 없는데, 새 테이블은 그 잘게 쪼갠 단위를 위해서만 존재하게 된다. 새 테이블을 쓰면 저장이 두 테이블에 걸친 트랜잭션이 되고, 경계가 지워지거나 카테고리가 바뀔 때 속성 행을 어떻게 할지도 새로 정해야 한다. 컬럼 하나로 끝날 일에 테이블과 외래키와 삭제 정책을 새로 만드는 것이다.

제안: LLM 초안 테이블이 이미 쓰는 모양을 사람 골든셋 쪽에 그대로 맞춘다.

테이블 추가 컬럼 무엇을 담나
iis_boundary_annotation_node properties jsonb 사람이 확정한 속성 값. prop_key마다 boolean/enum/number/text/object 값을 그대로 담는다. iis_semantic_component.properties와 같은 모양
iis_boundary_annotation preannotation_properties jsonb 사람이 보기 전 LLM이 낸 속성 전체 스냅샷. 기존 preannotation_boundaries와 나란히 둔다. 되돌리기와 "얼마나 고쳤나" 비교에 쓴다

prop_key가 그 노드의 카테고리가 허용하는 키인지는 저장 API가 checkPropertyValues로 검사한다(D-3). 컬럼을 나누지 않고 jsonb 하나로 두는 것은 LLM 쪽과 정확히 같은 이유다. 카테고리마다 키 집합이 다르므로 고정 컬럼으로는 표현할 수 없다.

감수하는 것: 어느 속성 키를 누가 언제 고쳤는지는 행 단위(reviewed_at)로만 남고 키 단위로는 안 남는다. 지금 경계 쪽도 categories 배열을 통째로 갈아 끼우는 방식이라 같은 수준의 이력만 가지므로, 속성만 더 촘촘한 이력을 요구할 이유가 없다. 나중에 키 단위 이력이 실제로 필요해지면(예: 특정 속성만 자주 틀린다는 것을 추적해야 할 때) 그때 별도 이력 테이블을 얹는다. 지금 쓰지 않을 정밀도를 미리 설계하지 않는다.

D-2(안). 편집 화면은 새로 만들지 않고 BoundaryAnnotationEditor에 패널을 얹는다#

제안: 카테고리를 칩으로 고르는 자리 바로 아래에 속성 편집 패널을 추가한다. 그 경계 행의 카테고리가 확정되면(row.categories), 각 카테고리가 capability-table.md에서 허용하는 속성 키를 propertyVocabulary.ts의 PROPERTY_PARAM_SPEC에서 그대로 읽어 그 키마다 입력 위젯을 하나씩 놓는다.

kind 위젯
boolean 토글
enum 드롭다운 (values 배열 그대로)
number 숫자 입력
text 텍스트 입력
object objectKeys마다 텍스트 입력을 나란히

왜 새 화면이 아니라 기존 편집기에 얹는가. 속성은 경계 없이 존재할 수 없으므로(3절), 경계를 확정하는 화면과 속성을 확정하는 화면이 분리되면 사람이 두 화면을 오가며 같은 노드를 다시 찾아야 한다. 지금 편집기는 이미 노드 선택, 카테고리 선택, 트리와 렌더링의 동기화된 하이라이트를 갖추고 있으므로 그 위에 패널 하나를 더하는 것이 화면을 새로 설계하는 것보다 싸다.

한 카테고리에 여러 개가 겹치는 자리(다중 라벨). BoundaryAnnotationEditor는 한 경계 행에 카테고리를 여러 개 다중 선택할 수 있게 해 두었다(toggleCategory, Unclassified만 단독). 카테고리가 여러 개면 각 카테고리가 요구하는 속성 키를 합집합으로 보여주고, 어느 카테고리의 속성인지 위젯 옆에 라벨을 붙인다.

D-3(안). 값을 저장 전에 어휘로 검사한다#

propertyVocabulary.ts의 checkPropertyValues가 이미 LLM 추출값을 걸러내는 데 쓰이고 있다(property-definition-projection-review-draft.md 41행). 사람이 입력한 값도 같은 함수로 검사해서, 어휘에 없는 값이 골든셋에 들어가는 것을 막는다. 검사 함수를 두 번 만들지 않는다 (2.-1절의 교훈을 그대로 적용).

5. 완료 판정#

경계(T-4)와 같은 기준을 쓴다.

  1. 기계 판정: 경계가 확정된 노드 중 하나 이상의 속성을 가진 카테고리로 태깅된 노드 전량에 대해, iis_boundary_annotation_node.properties에 든 키가 전부 그 노드 카테고리의 허용 어휘 안에 있다(checkPropertyValues 통과 100%).
  2. 사람 판정: 층화 표본을 사람이 직접 편집기로 확정하고, LLM 사전 태깅(iis_boundary_annotation.preannotation_properties)과 사람 확정값(iis_boundary_annotation_node.properties)이 얼마나 다른지를 표로 낸다. 이 수치가 나오기 전까지는 "속성이 몇 퍼센트 맞다"를 결과물로 주장하지 않는다. 지금 있는 것은 표본 14건의 에이전트 눈검수뿐이고, 이는 완료 판정을 채우지 못한다.
  3. 회귀 테스트: checkPropertyValues 위반 시 저장이 막히는 것, 경계가 없는 노드에는 속성 패널이 나타나지 않는 것을 회귀로 고정한다.

6. 범위 제외#

  • 속성 값 자체가 맞는지에 대한 판단은 이 문서가 답하지 않는다. property-definition-projection-review-draft.md가 다루는 문서 대 코드 불일치(14개 키의 "코드 확장"), StackedBarChart.normalize 모순은 그 문서에서 정원석의 확인을 기다린다.
  • 이종 컴포넌트 구조 대치(같은 문서 6절)도 범위 밖이다. 이 문서는 카테고리 하나짜리 경계 안의 속성 확정만 다룬다.
  • 속성을 쓰는 검색과 대치의 정책 선택(T-8의 exclude/reorder/ignore)도 범위 밖이다. 이 문서가 만드는 골든셋은 그 정책을 고를 때 쓸 사람 정답을 공급하는 것이지, 정책 자체를 정하지 않는다.

7. 다음 단계#

이 문서는 초안이다. D-1~D-3이 확정되기 전에는 마이그레이션도 화면 작업도 시작하지 않는다. 확정되면 스키마 변경(컬럼 추가), API(annotationStore.ts, /api/visual-summary/iis-boundary-annotation), 화면(BoundaryAnnotationEditor.tsx)을 한 변경 단위로 함께 만든다. 8절이 그 순서를 다룬다.

8. D-4(안). 스키마를 먼저 올리지 않는다. 스키마와 API와 화면을 한 변경으로 묶는다#

지금 이 컬럼을 쓰는 코드는 없다. annotationStore.ts의 replaceBoundaryAnnotation/loadBoundaryAnnotation, /api/visual-summary/iis-boundary-annotation 라우트, BoundaryAnnotationEditor.tsx 어디에도 속성을 읽거나 쓰는 경로가 아직 없다(D-2가 아직 구현되지 않았으므로 당연하다). 그래서 "지금 이 필드를 쓸 곳이 있는가"에 대한 답은 없다이고, 이 절은 그 상태에서 마이그레이션만 먼저 올릴지를 정한다.

제안: 먼저 올리지 않는다. 스키마 변경(컬럼 추가), API, 화면을 같은 PR 하나로 묶어서 만든다.

근거 첫째: 이 저장소는 한 번 커밋된 마이그레이션을 삭제하거나 이름을 바꿀 수 없고, 잘못된 것은 새 마이그레이션으로 덮어써야 한다(AGENTS.md "Migration 관리"). D-1은 이 대화 안에서 이미 한 번 뒤집혔다(새 테이블 안에서 컬럼 추가 안으로). 소비하는 코드 없이 스키마만 먼저 확정하면, 실제로 화면과 API를 만들면서 모양이 또 바뀔 경우(예: jsonb 하나로는 부족하고 칸을 더 쪼개야 하는 사례가 나오는 경우) 되돌릴 수 없는 이력 위에 고쳐야 하는 마이그레이션이 하나 더 쌓인다. 화면과 API까지 만들고 나서 마이그레이션을 쓰면 그 왕복이 통째로 로컬 스키마 설계 단계 안에서 끝난다.

근거 둘째: 저장소 관례가 "Migration 파일 생성 후에는 반드시 pnpm supabase migration up을 실행해 로컬 DB에 적용해야 한다"고 못박아 두는 것은, 마이그레이션이 만들어지면 곧바로 쓰인다는 것을 전제한다. 쓰는 코드가 없는 컬럼을 올려 두면 그 실행이 형식적인 절차가 되고, 나중에 이 컬럼이 왜 비어 있는지 다른 사람이 코드에서 추적해야 한다.

감수하는 것: 스키마를 먼저 올려 두면 API와 화면 작업을 시작하기 전에 리뷰어가 테이블 설계만 먼저 볼 수 있다는 이점을 잃는다. 다만 지금은 이 작업을 한 사람(또는 한 세션)이 이어서 하므로 그 이점이 실제로 발생하지 않는다. 여러 사람이 스키마와 화면을 나눠 맡는 상황이 되면 이 결정은 다시 봐야 한다.

↑ 목차로 · 5부 머리로

32장zod + OpenAPI + 시그니처 수집 Blueprint 정본API 검증

원문 plan/zod-openapi-signature-blueprint.md GitHub 에서 보기 8 KB


title: Visual Summary API: zod 검증 + OpenAPI + 함수 시그니처 수집 Blueprint description: visual-summary route들의 수동 as {...} 캐스팅을 zod로 교체하고, 거기서 OpenAPI 스펙을 생성하고, 함수 코멘트를 자동 수집하는 계획. 참고한 외부 레퍼런스는 tRPC + trpc-to-openapi 방식을 쓰는데, aippt-jailbreak는 tRPC가 없어 그대로 옮길 수 없다는 것이 이 문서의 핵심 결정 포인트다.#

Visual Summary API: zod 검증 + OpenAPI + 함수 시그니처 수집 Blueprint#

버전: 0.4 · 작성일: 2026-08-16 · 상태: Draft(단기 B / 장기 A 확정, 구현 미착수) · 구현 위치: (아직 없음) · Jira: MOR-2086(독립 Task — Visual Summary PoC 로드맵 MOR-1776과 무관) · 작성 형식은 blueprint-writing-guide.md를 따른다.

이 문서는 src/app/api/visual-summary/* route들에 zod 검증과 OpenAPI 스펙 생성, 함수 시그니처(코멘트) 자동 수집을 도입하는 설계 정본이다. 정원석님이 참고할 만한 외부 레퍼런스가 있다고 해서 그 구조를 조사했고, aippt-jailbreak와의 근본적인 차이(tRPC 유무) 때문에 그대로 옮길 수 없다는 것이 확인되어, 그 지점을 결정 사항으로 남긴다.

  • 이 문서가 답하는 것: 참고 레퍼런스가 실제로 무엇을 쓰는가, aippt-jailbreak에 그대로 옮길 수 있는가, 옮길 수 없다면 대안이 무엇인가, PoC 범위(visual-summary)에 먼저 무엇을 적용하는가
  • 이 문서가 답하지 않는 것: aippt-jailbreak 전체 API에 확대 적용할지, tRPC를 aippt-jailbreak에 도입할지(둘 다 이 PoC 범위 밖의 별도 결정)
  • 상위 맥락: ../README.md · ../TODO.md

1. 현재 상태#

1-1. aippt-jailbreak (visual-summary)#

src/app/api/visual-summary/ 아래 5개 route(run, sheet, index, samples, comp-sign-status)는:

  • 파일 상단에 POST /api/... body: {...} 형태의 수기 코멘트로 시그니처를 적어두는 관례가 있음 (예: run/route.ts:1-8).
  • body 검증은 zod 없이 (body ?? {}) as {mode?: string; ...} 캐스팅 + 수동 if 체크로 되어 있음.
  • 반면 같은 repo의 다른 파이프라인 route(s3-upload-batch, thumbnail-propagate 등)는 이미 zod로 검증하고 있음. visual-summary가 기존 컨벤션에서 뒤처진 상태였을 뿐, zod 자체는 새 도입이 아님.
  • OpenAPI 관련 패키지/코드는 repo 어디에도 없음.
  • tRPC 관련 패키지(@trpc/*)도 repo에 없음. Route Handler(route.ts) 기반 구조.

1-2. 참고 레퍼런스 (외부 개인 프로젝트)#

참고한 개인 프로젝트의 실제 구조:

  • API는 tRPC 프로시저로 정의된다 (src/server/domain/*/procedures/*.ts). 각 프로시저가 .input(zodSchema) / .output(zodSchema)로 계약을 갖고, .meta({ openapi: { method, path, tags, summary, protect } })로 REST 매핑을 선언한다.
  • pnpm openapi:gen (scripts/openapi/generate.ts)이 trpc-to-openapi 패키지로 tRPC 라우터 전체를 순회해 openapi.json을 생성, 레포에 커밋한다. .meta({ openapi })가 없는 프로시저는 스펙에서 빠진다.
  • 생성물은 openapi-typescript로 다시 타입(schema.d.ts)으로 변환되고, /api/v1/[...trpc]/route.ts가 REST 요청을 tRPC로 브릿지, /api/v1/openapi.json/route.ts가 스펙을 서빙, admin api-docs-viewer.tsx가 Swagger UI로 보여준다.
  • 함수 코멘트는 JSDoc(@description, @returns 등)을 procedure 함수 위에 관례적으로 적는 것. 별도 수집 스크립트가 아니라 zod .describe() + JSDoc이 코멘트 겸 계약 문서 역할을 한다. 즉 "함수 코멘트 자동 수집 시그니처"는 참고 레퍼런스에서 독립 도구가 아니라, zod 스키마의 .describe()가 그 역할을 흡수하고 있다.
  • CI가 재생성-비교로 스펙 드리프트를 막는다 (generate.ts 주석 "CI가 재생성-비교로 드리프트를 차단한다").

핵심: 참고 레퍼런스에서 OpenAPI는 tRPC 라우터라는 이미 존재하는 단일 진입점을 순회해서 나온다. tRPC가 "모든 프로시저의 입출력 계약을 한곳에 모아놓은 레지스트리" 역할을 하기 때문에 trpc-to-openapi가 동작한다.

2. 결정: 단기 경량 브릿지, 장기 tRPC (2026-08-16 확정)#

aippt-jailbreak는 순수 Next.js Route Handler 구조라 참고 레퍼런스의 trpc-to-openapi 경로를 그대로 쓸 수 없다. A(tRPC 도입)와 B(경량 브릿지) 중 하나를 고르는 문제가 아니라, B를 먼저 하고 A를 나중에 하는 순서 문제로 정원석님이 정리함: "우선은 경량 브릿지로 하지만 장기적으로는 tRPC."

옵션 방식 장점 비용 시점
A. tRPC 도입 참고 레퍼런스와 동일하게 tRPC 레이어를 새로 얹고 그 위에서 trpc-to-openapi 사용 참고 레퍼런스와 완전히 같은 패턴, visual-summary 밖 다른 API까지 확대할 때 스펙이 자동으로 한곳에 모임 aippt-jailbreak 전역 아키텍처를 바꾸는 결정. Route Handler와 tRPC 프로시저 두 스타일이 공존하게 되거나 대규모 마이그레이션이 필요 장기 목표. 착수 시점·트리거는 미정(Checklist 참고)
B. 경량 브릿지 Route Handler는 그대로 두고, 각 route에 z.object({...}).describe(...) 스키마만 두고 zod-openapi(또는 @asteasolutions/zod-to-openapi) 같은 "zod → OpenAPI 조각" 라이브러리로 route별 path item을 만들어 하나의 openapi.json으로 합침 기존 Route Handler 구조를 그대로 유지, visual-summary 5개 route에만 국소적으로 적용 가능, PoC 실패해도 되돌리기 쉬움 참고 레퍼런스와 스펙 생성 코드 자체는 다름(단, 계약의 SSOT가 zod 스키마라는 원칙은 동일) 지금 착수. 이번 PoC 범위

PoC 범위(visual-summary)에서 지금 하는 건 B다. tRPC 전환까지 끌고 가는 것은 이 작업의 목적(시그니처/스펙 정비)을 넘어서는 별도 크기의 결정이라 시점을 나눴다. 참고 레퍼런스에서 지금 가져올 것은 "tRPC를 쓴다"가 아니라 **"zod 스키마가 계약의 SSOT이고, 스키마의 .describe()가 곧 문서이며, 거기서 OpenAPI를 생성해 커밋하고 CI로 드리프트를 막는다"**는 원칙이다. tRPC는 그 원칙을 구현하는 수단이고, B에서도 A로 갈 때도 그대로 유지된다.

B를 만들 때 A로의 이관을 염두에 둘 것. zod 스키마를 route 파일 안에 인라인으로 박지 말고, route별로 재사용 가능한 스키마 모듈로 분리해서 둔다(예: src/app/api/visual-summary/<route>/schema.ts에 input/output 스키마 + .describe()). 나중에 tRPC로 옮길 때 이 스키마가 그대로 .input()/.output()에 들어가야 한다. Route Handler 안에서 스키마를 새로 짜는 일이 없게.

3. 이번 PoC(visual-summary)에 적용할 것#

  1. zod 검증: run, sheet, index, samples, comp-sign-status route의 수동 as {...} 캐스팅을 각각 zod 스키마로 교체. 필드마다 .describe()로 의미를 적어 기존 파일 상단 코멘트를 대체/보강한다.
  2. OpenAPI 생성: zod-openapi(경량, 옵션 B)로 5개 route의 스키마를 모아 docs/projects/mordor/visual-summary(또는 repo 표준 위치, 결정 필요) 아래 openapi.json 생성 스크립트 추가. 참고 레퍼런스처럼 생성물을 커밋한다.
  3. 시그니처 자동 수집: 별도 코멘트 파서 대신, zod .describe() + route 파일 상단 JSDoc을 그대로 소스로 삼아 OpenAPI summary/description에 흘려보낸다. 참고 레퍼런스와 같은 원칙.
  4. 5개 route 전환이 끝나면 실제로 유용한지(문서가 최신 상태를 반영하는지, 프론트/다른 소비자가 실제로 참조하는지) 확인한 뒤 다른 API로 확대할지 별도로 논의한다.

3-1. 역할 분담 (2026-08-16 확정)#

  • AI(Claude)가 쓰는 것: 각 zod 필드의 .describe('...') 문구, route 상단 JSDoc. route를 구현과 수정할 때마다 값의 의미를 설명하는 description을 직접 작성한다. 이건 이번 blueprint 이전부터 있던 "함수 description 잘 뽑는" 관례를 zod 스키마 위로 옮기는 것뿐, 새 규칙이 아니다.
  • 생성기가 자동으로 하는 것: 그 describe들을 모아 OpenAPI 스펙의 paths.*.responses.*.content['application/json'].schema 같은 구조로 조립하는 것(zod-openapi가 담당). 이 구조 자체는 사람도 AI도 손으로 쓰지 않는다. zod 스키마가 바뀌면 재생성 시 같이 바뀐다.
  • 즉 유지보수 대상은 딱 하나, **zod 스키마의 .describe()**다. 스키마를 고치는데 describe를 안 고치면 스펙이 오래된 설명을 반환하므로, route를 고칠 때마다 describe도 같이 갱신하는 것을 리뷰 체크 항목으로 둔다.

Non-goals#

  • aippt-jailbreak 전체 API를 이번에 zod/OpenAPI로 옮기지 않는다.
  • tRPC를 이번 PoC 범위에서 도입하지 않는다. 기각이 아니라 장기 목표로 확정했고, 착수 시점은 별도 논의(2절). 다만 B의 zod 스키마는 A로 이관 가능한 형태(재사용 가능한 모듈)로 만든다.
  • Swagger UI 같은 뷰어를 이번 PoC에 붙이지 않는다. 스펙 생성과 커밋까지만 하고, 누가/무엇이 이 스펙을 소비할지는 스펙이 나온 뒤에 정한다.

Checklist#

  • Jira 티켓 생성: MOR-2086 (독립 Task, PoC 로드맵 MOR-1776과 무관하게 분리) (2026-08-16)
  • 참고 레퍼런스(tRPC + trpc-to-openapi) 구조 조사 (2026-08-16)
  • aippt-jailbreak에 tRPC 없음 확인, 옵션 A/B 정리 (2026-08-16)
  • AI-작성 vs 생성기-자동조립 역할 분담 정리 (2026-08-16, 3-1절)
  • A/B 순서 확정: 단기 B, 장기 A (2026-08-16, 2절)
  • tRPC(A) 마이그레이션 착수 시점과 트리거 결정 (B 적용 후 실제 유용성 확인이 선행 조건일 가능성 높음, 아직 미정)
  • zod-openapi vs @asteasolutions/zod-to-openapi 중 어느 라이브러리를 쓸지. A 이관을 염두에 두면 tRPC .input()/.output()과 궁합이 좋은 쪽을 우선 검토 (둘 다 route handler 친화적, 세부 비교 필요)
  • 생성된 openapi.json을 어디 둘지: repo 루트(참고 레퍼런스 방식) vs docs/projects/mordor/visual-summary/spec/ 아래
  • CI 드리프트 체크를 이번 PoC 범위에 넣을지, 나중으로 미룰지
  • run/sheet/index/samples/comp-sign-status 5개 route를 zod로 전환
  • OpenAPI 생성 스크립트 추가 및 최초 openapi.json 커밋
  • 5개 route 전환 후 실제 유용성 확인 → 확대 여부 별도 논의

Changelog#

날짜 버전 변경 근거
2026-08-16 0.1 최초 작성 — 참고 레퍼런스 구조 조사, 옵션 A/B 정리, PoC 적용 범위 확정 정원석: "blueprint 먼저 잡아서 계획부터 하자"
2026-08-16 0.1 참고 레퍼런스 이름 익명화(개인 프로젝트) 정원석: "그건 내 개인 프로젝트이니 이름 밝히면 안돼" — [[dont-name-personal-projects-in-work-repo]]
2026-08-16 0.1 3-1절 역할 분담(AI가 쓰는 것 vs 생성기가 자동 조립하는 것) 추가 정원석: "describe 는 ai 가 써야하는거 아니야?" 확인 질문에 대한 정리
2026-08-16 0.2 헤더(버전/상태/구현위치)·Checklist·Changelog 절 추가, "열린 질문"을 Checklist로 흡수 blueprint-writing-guide.md 표준 적용
2026-08-16 0.2 문서 전체의 repo 지칭을 세션 worktree 이름("crayfish")에서 실제 repo 이름("aippt-jailbreak")으로 정정 정원석: "현재 worktree 이름을 이렇게 남기면 다른 사람은 아무도 못알아먹는다"
2026-08-16 0.3 2절를 "A/B 중 택일"에서 "B 먼저, A 나중" 순서 결정으로 재구성. B의 zod 스키마를 A로 이관 가능한 형태로 만들라는 설계 제약 추가 정원석: "우선은 경량브릿지로 하지만 장기적으로는 trpc"
2026-08-16 0.4 Jira 티켓 MOR-2086 생성 — 독립 Task, Visual Summary PoC 로드맵(MOR-1776)과 무관하게 분리 정원석: "visual summary poc 작업과 관련된 ticket 이면 subtask로. api 인프라 정비 작업은 독립된 task 로."

↑ 목차로 · 5부 머리로

33장Jira Artifact 재편 기록 정본티켓 구조와 대응표

원문 plan/jira-artifact-blueprint.md GitHub 에서 보기 7 KB 원문 제목 「Visual Summary Jira Artifact 재편 기록과 현황」

이 문서의 역할 2026-08-06 에 실행한 Jira 재편(Artifact 신설, milestone 정리, Subtask 15건)의 결과 대응표와, 아직 열려 있는 결정을 담는다. 재편 전 설계 초안은 4절 결정 기록으로 압축했다. 오너 정원석 (MORDOR) · 상태 재편 완료, 후속 결정 진행 중 (5절) · 최종 갱신 2026-08-07 관련 ../TODO.md 할 일 SSoT · ../README.md 허브 · 8/4 체크인 회의록 · Confluence 발행본

이 문서 훑기

절 무엇에 답하나
1 지금 티켓 구조가 어떻게 생겼나
2 티켓마다 무엇을 담고 누가 맡나
3 티켓 간 선후 관계는 무엇인가
4 왜 이렇게 재편했나
5 아직 정하지 않은 것은 무엇인가

1. 현재 티켓 구조#

2026-08-06 재편 결과다. 배경과 근거는 4절에 있다.

[Artifact] MOR-1923 Visual Summary (구조화 Summary 생성과 검색·대치)   정원석, due 10/15
└─ [Milestone] MOR-1776 단계 4 (YAML 전환 + node 태그)                정원석, due 9/30
    ├─ [4-1] ~ [4-3]   생성기 (MOR-1775 에서 이관)                    허재영, 황현진
    ├─ [4-4] ~ [4-9]   변환기, 상수, PoC, 계측, 평가셋, 판정 지표
    └─ [4-10] ~ [4-15] 내용 대치 contract 와 열린 결정 해소

상태 변경: MOR-1773 규약 정의 Done, MOR-1775 단계 3 Cancelled (둘 다 근거 comment 있음). MOR-1774 시멘틱 검색 v2 는 전달 트랙이라 Artifact 밖에 유지한다.

2. 티켓 대응표#

티켓 내용 담당 덮는 TODO 항목
MOR-1923 Artifact. 산출물 5종(규약, 생성기, 변환기, capability 상수, PoC·계측)과 완료 기준(9월 말 dev/stg end to end, 교집합 상위 10개 품질, BAD 체감 50→20%) 정원석 -
MOR-1776 Milestone. 생성기 YAML 전환 포함으로 재정의. PoC 결과가 나쁘면 node 태그 확장을 skip 하고 5단계 직행 정원석 C-3
MOR-1909 [4-1] fallback map 49종 확정. B-24 는 이 티켓에서 49종으로 닫혔다 (2026-08-06 정원석) 허재영 (In Progress) B-24, B-26, E-2
MOR-1910 [4-2] YAML 생성 프롬프트 초안. silent corruption 계측(F-2)이 할 일에 포함 황현진 F-2 일부
MOR-1911 [4-3] entity.components[] 생성 시점 허재영 B-20
MOR-1924 [4-4] structure 를 검색 질의로 바꾸는 변환기 스펙 + PoC (예시 18종으로 질의 산출까지) 정원석, due 8/28. 9월 이석님 인수 D-4
MOR-1925 [4-5] capability TS 상수 + BE 소비용 JSON 정원석 B-14, I-5 연계
MOR-1926 [4-6] props 없는 type 간 대치 PoC. 4단계 skip 판정 정원석 D-3 승계
MOR-1927 [4-7] 품질 영향 분리 계측 지점 설계 정원석 C-3 감수 사항
MOR-1928 [4-8] 평가셋 분할 찌꺼기 제거와 38 중분류 확장 허재영 J-1
MOR-1929 [4-9] 교집합 상위 10개 품질 판정 절차와 지표 정원석 (김수현 합의) 신규 (8/4 목표)
MOR-1932 [4-10] semantic category 별 필요 속성 목록 정원석 (류민·신영빈 리뷰) I-1
MOR-1933 [4-11] 세 축 정렬 + comp-sign 변형 3종 정원석 조율 (류민·신영빈·이석) I-2, B-19
MOR-1934 [4-12] fallback 동작 열린 결정 정원석 (이석 협의) B-27, B-28
MOR-1935 [4-13] 꾸밈 축 확정과 색인 경로 정원석 (류민·신영빈·이강윤·이석) B-21, B-22, I-4
MOR-1936 [4-14] rankCompCandidates 위계 가중치 A/B 정원석 (이석) I-6
MOR-1937 [4-15] 스마트 다이어그램 정합 실측 정원석 (강창석·이석·류민) B-13, B-10, B-12

의도적으로 티켓을 만들지 않은 것. C절(2단계 전달 트랙, MOR-1774 와 MOR-1814 계열이 이미 존재), A-2(KDL 근거 이석 전달, 시급하지 않음), A-6(UNICORN-77437 깨진 링크 정리), B-6, B-7, B-9, B-23, B-25(규약 문서 결정, TODO.md 로 충분), D-1, D-2, D-6(스쿼드 구두 결정), E-1 과 E-3(별도 트랙), J-2~J-8(J-1 과 규약 안정화에 종속), F-1(사후 검증).

Blocks 는 선행 관계다 (앞이 blocker).

Blocker Blocked 이유
MOR-1909 MOR-1924, MOR-1925 fallback map 이 변환기 스펙과 상수의 선행
MOR-1927 MOR-1910 계측 설계가 프롬프트 착수 전
MOR-1934 MOR-1909, MOR-1924 B-27·B-28 이 fallback 동작을 가름
MOR-1933 MOR-1935 세 축 정렬이 꾸밈 축의 선행
MOR-1911 MOR-1910 components[] 결정이 프롬프트 경계의 한 줄

Relates: MOR-1928 ↔ MOR-1910, MOR-1929 과 MOR-1927 ↔ MOR-1896 (judge 기여도 분해 문제 카드), MOR-1923 ↔ MOR-1774 과 MOR-1709, MOR-1932 ↔ MOR-1856.

4. 재편 경위와 결정 기록#

왜 재편했나 (2026-08-06 시점의 겹친 셋). 첫째, 8/4 회의가 일정과 목표를 새로 확정했다. 8/25 제품 배포 취소, 9월 말 dev/stg 와 10월 중순 production 판정, 판정 지표는 시멘틱 검색과 구조 검색의 교집합 상위 10개 품질이다. 이 일정을 담는 티켓이 없었다. 둘째, 허재영님이 8/5 에 MOR-1775 아래 subtask 정의 가능 여부를 물었고 실제로 [3-1][3-3] 셋을 만들어 둔 상태였다. 셋째, MOR 의 Artifact 6종(MOR-1419)이 8/2 에 전부 Cancelled 되어 Visual Summary 를 담는 상위 이슈가 없었다.

결정 다섯 (2026-08-06 정원석).

결정 내용 근거
Artifact 신설 이름은 Visual Summary (구조화 Summary 생성과 검색·대치). 시멘틱 검색 v2(2단계)와 스마트블록 투입과 조합 기능은 범위 밖 전달 트랙(MOR-1741, MOR-1814 계열)과 성격이 다르다
마일스톤 하나로 초안의 새 Milestone(VS-M1) 안을 접고 MOR-1776 하나에 모은다 생성기 YAML 전환은 C-3 결정대로 이미 4단계 범위다
MOR-1775 Cancelled 재정의 부활 대신 닫는다 7/24 스킵 결정과 문서의 "실행하지 않음" 기록과 일치시킨다
Subtask 이관 허재영님이 만든 [3-1]~[3-3] 을 삭제 없이 MOR-1776 으로 옮기고 [4-x] 로 개명 허재영님이 3단계와 4단계를 헷갈린 것이고 작업 내용은 유효하다
전 범위 이슈화 TODO 의 I 절과 사람 간 정렬이 필요한 B 절 열린 결정까지 [4-10]~[4-15] 로 만든다 "가능한 모든 작업 범위를 issue 로 준비, 빈 영역 모두 대비" (정원석)

함께 닫힌 것. B-24 는 49종으로 확정됐고(MOR-1909 본문에 기록), D-3(3단계 스킵 최종 확인)은 MOR-1775 Cancelled 로 확정되며 대치 로직 관점 확인이 MOR-1926 으로 승계됐다. A-5(규약 그림 공유)는 8/4 세션에서 완료됐다.

후속 완료 (2026-08-06). TODO.md 와 README.md 티켓 키 반영, 허재영님 이관 공지 (Slack), 김수현님용 Confluence 발행 (발행본, C-8 발행분).

5. 남은 것과 모호한 것#

하나씩 정리한다. 정리되는 대로 이 표를 갱신한다.

# 항목 상태 메모
1 김수현님께 발행본 알림 + MOR-1774 마감 8/18 유지 여부 확인 [x] 답변 완료 (2026-08-07, Confluence 코멘트). 8/18 은 이미 결정된 배포 연기에 포함. 검색풀 17만 증량분 judge 는 배포 보류, List 편중 방치 여부는 2단계 자체를 안 하므로 결정할 것 없음. 이 셋을 실제로 닫는 티켓은 MOR-1939 — AC 갱신 필요
2 MOR-1924 ([4-4] 변환기 스펙) 담당 확정 [x] 스펙 선행 + PoC 로 확정 (2026-08-06 정원석). 담당 정원석, due 8/28, 예시 18종으로 질의 산출 PoC 까지. 9월 이석님은 구현만 받는다
3 단계 번호 정의 정리 [x] 같은 것의 다른 표현으로 확인 (2026-08-06 정원석). node 태그 = core component 경계 특정(4단계), tagged node 에 category 속성과 고유 props 부착 = 속성 호환(5단계). ../README.md 1절에 대응 관계를 병기했다
4 validator semantic tag 필수 가드 rollback 티켓 [x] 이미 존재했다. MOR-1918 [X-7] (이강윤, In Progress, 8/7 머지 목표, 8/11 BE 전달 전 반영)
5 Subtask 15건의 due date [x] 2026-08-06 일괄 설정. 8/14: [4-1]·[4-7]·[4-12] (blocker 우선) · 8/21: [4-3]·[4-6]·[4-8]·[4-9]·[4-10] · 8/28: [4-4]·[4-5]·[4-15] (이석님 9월 투입 전 라인) · 9/12: [4-2]·[4-11] · 9/19: [4-13]·[4-14] (dev/stg 전 마지막 라인)
6 MOR-1774 본문 정리 [ ] 깨진 키 UNICORN-77437 잔존(A-6). due 는 8/18 폐기가 확인됐으니(1번) MOR-1939 AC 반영 후 새 날짜로 갱신

↑ 목차로 · 5부 머리로

34장PageNavigation 4종 성립 조건 초안제안 초안

원문 plan/page-navigation-capability-draft.md GitHub 에서 보기 4 KB 원문 제목 「`PageNavigation` 4종 성립 조건: 제안 초안」

이 문서의 역할 capability-table.md 2.6절이 "성립 조건 미정" 으로 비워 둔 4행을 채우는 제안이다. 능력표 자체는 손대지 않았다 — 그 문서 오너는 정원석이고 2.6절이 C-4 확정을 조건으로 적고 있다.

왜 지금 쓰나 골든셋 v1(MOR-2165)에서 이 4종 40행 중 21행이 막혀 있었고, 원인을 찾다가 성립 조건이 정본 시트에 이미 있다는 것을 확인했다 (2026-08-25). 정하지 않은 결정이 아니라 규약이 아직 옮겨 적지 않은 것이었다.

오너 제안 작성 클로드 · 확인 필요 정원석(능력표)·김수현(단어장) · 작성 2026-08-25

관련 golden-set-blueprint.md 9.3절 미결과 실측 · ../reference/golden-set-production-notes.md 1절 정본 시트 · ../reference/semantic-taxonomy-v7.md PageNavigation 절 · ../spec/capability-table.md 2.6절 채울 자리


1. 출처#

원본 시트 2026q3_내용시각화_시멘틱 분류_v7.xlsx 의 레이아웃 텍사노미 신규 탭. 🆕 표시가 붙은 신규 보강 항목이고, 대분류 정의와 중분류 4종의 조건이 모두 있다.

원본 최종 수정 2026-08-25 08:41 UTC. repo 사본의 이전 스냅샷(2026-08-17)보다 8일 새롭다.

대분류 정의. 콘텐츠 자체가 아니라 프레젠테이션의 구조와 흐름을 안내하는 슬라이드다. 표지(시작/끝 선언), 목차(전체 구조 안내), 간지(섹션 전환 신호)가 해당하며, 공통적으로 정보를 전달하기보다 읽는 사람의 위치를 알려주는 기능을 한다.

2. 채울 4행#

능력표 2.6절의 열 이름을 그대로 쓴다.

# 카테고리 개수 내용 조건 dataset 선택 내용 속성 (params)
42 PageNavigation_Opening 1 제목이 있고, 발표자·날짜·소속 중 하나 이상이 함께 온다 없음 부제목 없음
43 PageNavigation_TableOfContents 2 이상 뒤에 올 섹션의 번호와 제목을 나열한다. 흐름(화살표)·단계(계단)가 없다 없음 섹션마다 한 줄 설명 없음
44 PageNavigation_SectionDivider 1 다음 한 섹션의 번호와 제목. 본문 콘텐츠가 없다 없음 한 줄 설명 없음
45 PageNavigation_Ending 1 종료를 알리는 단일 메시지. 별도 콘텐츠가 없다 없음 없음 없음

개수를 이렇게 잡은 근거#

Opening, SectionDivider, Ending 이 1인 것은 시트가 셋 다 "중앙에 배치하는 단일 슬라이드 구조" / "단일 메시지만" 으로 적기 때문이다. TableOfContents 만 2 이상인 것은 "전체 섹션 구조를 나열" 이므로 항목이 하나면 목차가 아니라 간지다.

dataset 이 넷 다 없음인 근거#

이 4종은 수치 데이터를 담지 않는다. v7 사본 4절 표가 이미 이 4종에 "없음 / 페이지에서의 자리다. 애초에 structure 가 표현할 대상이 아님" 이라고 적어 두었다.

다만 structure 는 필요하다. 규약 v0.5 의 structureSchema 가 entities 와 dataset 중 하나를 요구하므로 빈 structure 는 스키마를 통과하지 못한다. 이 4종은 entities 로 적는다. Opening 이면 제목, 발표자, 날짜가 각각 entity 이고, TableOfContents 면 섹션마다 entity 하나다. "구조가 없다" 는 것은 의미 구조가 없다는 뜻이고 structure 필드를 비운다는 뜻이 아니다.

3. 이웃과 가르는 신호#

능력표 2.6절이 채워지면 생성기 프롬프트가 이 신호를 쓸 수 있다. 골든셋 실측에서 막힌 21행 중 14행이 HighlightedMessage 로 갔고, 원인은 제목 한 줄이 그 종과 텍스트로 구별되지 않기 때문이었다.

이 종이 아니라 언제 시트의 근거
TextBasedCommunication.HighlightedMessage 제목만 있고 메타정보가 없다 Opening 은 "발표자·날짜 등 기본 메타정보" 를 요구한다
ProcessFlow.Steps 항목에 화살표나 단계가 있다 TableOfContents 는 "흐름이나 단계는 없다"
PageNavigation.TableOfContents 섹션이 둘 이상 나열된다 SectionDivider 는 "전체 구조가 아닌 단일 섹션만"
ReasoningStructure.CombinedResult 나 HighlightedMessage 본문 내용을 요약한 결론이다 Ending 은 "별도 콘텐츠 없이 발표가 끝났음을 알리는 역할만"

4. 확인이 필요한 것 둘#

4.1 Opening 이 마지막 슬라이드도 되는가 (김수현). 닫힘 (2026-08-27)#

해소. 허재영 확인(Slack): "표지 또는 마지막 슬라이드" 는 단어장 업데이트 전 문구였고, 최신 정본 시트의 minor category 정의는 Opening/Ending 을 명확히 분리한다. Opening 은 맨 앞장 전용으로 확정.

시트의 Opening 정의가 "프레젠테이션의 표지 또는 마지막 슬라이드처럼" 이라고 적어 Ending 과 겹친다. 그런데 같은 시트의 Ending 정의가 "Opening 이 시작을 선언하는 것과 대칭적 관계" 라고 적어 스스로 어긋난다.

골든셋은 "맨 앞장 전용" 으로 만들었다 (2026-08-25 황현진 결정). repo 사본 v7 의 중분류 표가 원래 "맨 앞장을 구성할 때만 사용" 이었고, 두 종이 배타적이어야 채점이 가른다. 원본 문구를 고칠지는 단어장 오너 확인 사항이다.

4.2 C-4 와의 관계 (정원석)#

능력표 2.6절은 "이 4종을 검색 풀에서 어떻게 다룰지(C-4)가 먼저 정해져야 개수 조건과 내용 조건을 채울 수 있다" 고 적었다.

이 제안은 그 순서를 뒤집는다. 위 조건들은 검색 풀 정책과 무관하게 시트에서 나온 것이고, "이 입력이 이 종인가" 를 판정하는 데 C-4 가 필요하지 않다. C-4 가 정할 것은 그 종이 뽑혔을 때 어느 템플릿을 주는가이지 뽑히는 조건이 아니다. 이 해석이 맞는지 확인이 필요하다.

5. 표기 오류 하나#

시트가 Section Devider 로 적었다. 규약 어휘와 repo 문서는 SectionDivider 다. 원본 수정 대상이다.

↑ 목차로 · 5부 머리로

35장Blueprint 작성 가이드 참고헤더·본문·Checklist·Changelog

원문 plan/blueprint-writing-guide.md GitHub 에서 보기 2 KB


title: Blueprint 문서 작성 가이드 description: docs/projects/mordor 아래 *-blueprint.md 문서가 갖춰야 할 최소 형식. 본문 구조는 자유이되, 헤더, Checklist, Changelog 세 가지는 고정한다.#

Blueprint 문서 작성 가이드#

이 repo의 *-blueprint.md 문서는 지금까지 본문 구조가 문서마다 제각각이었다(배경/목표/설계 절 이름도, Changelog 유무도 문서마다 다름). 그 자유는 유지하되, 세 가지만 표준으로 고정해서 여러 턴에 걸친 작업에서도 "왜 이렇게 했는지"와 "지금 뭐가 남았는지"가 문서만 보고 복원되게 한다. [[blueprint-doc-not-context]] 원칙의 이 프로젝트 안 적용판이다.

1. 헤더 (필수)#

frontmatter 아래, 본문 시작 전에 다음 넷을 한 줄씩 적는다.

markdown
> **버전**: 0.1 · **작성일**: 2026-08-16 · **상태**: Draft(옵션 A/B 결정 대기) · **구현 위치**: (아직 없음)
  • 버전: 문서가 바뀔 때마다 올린다. 결정이 뒤집히거나 범위가 바뀌면 minor를, 오탈자 수준이면 안 올려도 된다.
  • 상태: Draft(논의 중) / Active(결정됨, 구현 중이거나 구현 완료) / Superseded(다른 문서로 대체됨, 대체 문서 링크 필수) 셋 중 하나.
  • 구현 위치: 실제 코드 경로. 구현 전이면 "(아직 없음)"으로 명시해 문서-따로-코드-따로가 안 되게 한다.

2. 본문 (자유)#

배경, 목표, Non-goals, 설계, 옵션 비교 등은 기존 관례대로 문서 성격에 맞게 쓴다. 강제하지 않는다.

3. Checklist (필수)#

문서 끝 쪽에 지금 남은 할 일, 아직 못 정한 것을 체크박스로 남긴다. "열린 질문" 절이 있었다면 그걸 체크리스트로 흡수해도 된다.

markdown
## Checklist

- [ ] 옵션 A/B 중 확정
- [ ] 라이브러리 선택 (`zod-openapi` vs `@asteasolutions/zod-to-openapi`)
- [x] neume 언급 익명화 (2026-08-16)

완료된 항목은 지우지 말고 [x]로 남긴다. 뭘 이미 검토했는지가 미검토와 똑같이 중요하다.

4. Changelog (필수)#

문서 맨 끝에 표로 남긴다. 코드 커밋이 아니라 문서 자체의 결정 변경을 기록하는 것. 무엇을 바꿨고 왜 바꿨는지, 근거가 된 사용자 발화가 있으면 같이 남긴다.

markdown
## Changelog

| 날짜 | 버전 | 변경 | 근거 |
|---|---|---|---|
| 2026-08-16 | 0.1 | 초안 작성 (옵션 A/B 정리) | 정원석 지시: "blueprint 먼저 잡아서 계획부터" |
| 2026-08-16 | 0.1 | 참고 레퍼런스 이름 익명화 | 정원석 지정: "neume 이름 밝히면 안 돼" |

Checklist#

  • 헤더, Checklist, Changelog 세 항목 표준 확정 (2026-08-16)
  • 이 가이드를 docs/projects/mordor/AGENTS.md나 상위 PROJECT_DOCUMENTATION_GUIDE.md에서 링크할지: 이번 범위 밖이고 필요해지면 별도 결정

Changelog#

날짜 버전 변경 근거
2026-08-16 0.1 최초 작성 정원석: "이 repo에 blueprint 어떻게 써야하는지 내용이 부실한 상황인가" → B(경량 표준화) 선택

↑ 목차로 · 5부 머리로

6부

참고 — reference/

밖에서 가져온 사실의 사본. 단어장, 스마트 다이어그램, 실사용 샘플, 출처 색인.

36장Semantic Taxonomy v7 단어장 전량 참고일반 29종 + 표·차트 23종

원문 reference/semantic-taxonomy-v7.md GitHub 에서 보기 18 KB 원문 제목 「Semantic Taxonomy v7: 단어장 전량」

이 문서의 역할 프로젝트에서 쓰는 semantic tag 값 집합(v7 중분류 단어장)의 repo 사본이다. 규약과 검색, 태깅이 모두 이 집합 안에서만 값을 고른다. 단어장 밖 값은 생성하지 않는다.

SSoT는 여기가 아니다. 값 자체의 SSoT는 아래 두 곳이고, 이 문서는 읽기 편하려고 옮겨 둔 것이다. 불일치하면 SSoT를 우선한다.

오너 김수현(단어장) · 황현진(추출 모듈) · repo 사본 관리 정원석 · 스냅샷 일자 2026-08-25 (3절 구현 우선순위 열을 원본 시멘틱 단어장v7 탭에서 전량 갱신 — 이전 스냅샷 2026-08-01은 P0 9개만 반영했었다)

관련 README.md 2절 용어 · 3절 컴포넌트 분포 / capability-table.md 성립 조건과 fallback 순서 / markdown-summary-samples.md 실사용 샘플

이 문서 훑기

절 무엇을 보나
0 2층 구조와 표기 세 가지
1 일반 카탈로그 29종 전량
2 표차트 카탈로그 23종 전량
3 어느 라벨이 실제로 얼마나 나오나
4 규약 어휘와의 격차 (해소 예정)
5 이 사본을 언제 무엇으로 맞췄나

0. 구조: 카탈로그 둘로 라우팅한다#

v7 은 평평한 목록이 아니다. 대분류(taxonomy_major) → 중분류(taxonomy_minor) 2층이고, 중분류가 곧 우리가 말하는 semantic tag 다(README.md 2절).

그리고 분류기는 라벨을 고르기 전에 먼저 카탈로그를 고른다. 일반과 표차트 둘 중 하나로 라우팅한다.

(input_text, summary)
        │
        ├─ [1] Layout Description 추출  (gpt-5.4-mini)
        │      "시각 레이아웃 모양"을 한국어 1~2문장으로 묘사
        │
        └─ [2] 규칙 라우팅 → 카탈로그 선택 → 라벨 1개  (claude-haiku-4-5)
               ├─ 일반 카탈로그      29종  (텍스트·관계·구조)
               └─ 표/차트 카탈로그   23종  (데이터 시각화)

두 카탈로그에 이름이 겹치는 항목이 있다. 대분류에서는 Enumeration, AttributeComparison, RelationalComparison, TimelineSchedule, Hierarchy, KPIKeyMetric, Geo 가 겹치고, 중분류에서는 Timeline, HierarchicalTree, Flow 가 양쪽에 있다. 라우팅을 먼저 결정하므로 실제 충돌은 없다. 다만 태그 값만 보고는 어느 카탈로그에서 나왔는지 알 수 없다는 점을 contract 에서 유의해야 한다.

표준 표기는 {major}_{minor} 이다. 같은 태그가 문서마다 세 형태로 나타나므로 주의한다.

형태 예 어디서 쓰나 상태
{major}_{minor} Enumeration_List design_object_semantic_tags.tags v2.2 · iui.comp_signs_v3_4.semantic_tags · comp-sign 코드 SSoT. 저장·질의·비교는 전부 이 문자열로 한다
영문+괄호 한국어 List(리스트) 시멘틱 쿼리 v2 검증 시트 · 태깅 CSV 표시용
minor 만 List 카탈로그 프롬프트 · 화면 표시(minorOf()) v2.0 어휘는 폐기됐다

근거는 comp-sign packages/comp-sign/src/comp-match/semanticTags.ts 주석이다: "식별자(id)가 유일한 정본 값이다 (…) minor 이름만 담던 옛 어휘(v2.0)는 버렸다 — 두 형식을 섞으면 조회가 조용히 0건이 된다."

분류 규모는 코드 기준이 정확하다. major 23 에 minor 49 이다. 아래 1절(29종)과 2절(23종)을 더한 52 에서 양쪽 카탈로그에 중복 등장하는 3종(Timeline, HierarchicalTree, Flow)을 빼면 49 로 일치한다. 미분류(빈 배열)는 v3.4 풀 기준 5% 안팎이다.


1. 일반 카탈로그 (29종)#

Enumeration#

동등한 수준의 정보 단위를 관계·흐름·비교보다 목록화와 묶음 중심으로 보여줄 때

중분류 (semantic tag) 언제 고르나
List 순서나 우열 없이 섹션 형태로 병렬 나열할 때. 시각 배치 형태가 달라도 동등 나열이면 모두 이 라벨 하나로 선택한다.

AttributeComparison#

동일한 기준이나 속성 축을 기준으로 대상 간 차이, 강약점, 우열을 비교할 때

중분류 (semantic tag) 언제 고르나
Versus 2개의 대상을 서로 대응시켜 차이점, 장단점, 우열을 직접 대조할 때

RelationalComparison#

항목별 차이를 나열하기보다 대상 간 상대적 위치, 영역, 교집합, 구조적 연관성을 중심으로 보여줄 때

중분류 (semantic tag) 언제 고르나
SWOT 내부/외부, 긍정/부정의 네 영역으로 전략적 상황 관계를 정리할 때
VennDiagram 집합 간 공통점, 차이점 관계를 원형 도형으로 교차하여 동시에 보여줄 때

Transformation#

동일 대상의 전후 상태, 개선 방향, 변화 결과를 보여줄 때

중분류 (semantic tag) 언제 고르나
Transformation 동일 대상의 Before/After, AS-IS/TO-BE, 개선 전후 상태를 보여줄 때 (전후의 변화를 강조)

ProcessFlow#

시간표보다 절차, 순서, 실행 단계가 더 중요할 때

중분류 (semantic tag) 언제 고르나
Steps 업무나 절차를 순차 단계로 구분해 진행 순서를 보여줄 때 (단계가 핵심)

LogicFlow#

조건, 판단, 분기, 의사결정 규칙에 따라 흐름이 달라지는 구조를 보여줄 때

중분류 (semantic tag) 언제 고르나
FlowChart 조건, 판단, 분기, 예외 처리에 따라 흐름이 달라지는 구조를 보여줄 때 (중간에 분기가 생기는 것이 핵심)

Progression#

상태, 수준, 성숙도, 성과가 단계적으로 높아지거나 발전하는 과정을 보여줄 때

중분류 (semantic tag) 언제 고르나
Staircase 상태, 수준, 성숙도, 성과가 단계적으로 상승·성장하는 것을 상향의 형태로 보여줄 때

TimelineSchedule#

날짜, 기간, 순서, 마일스톤 등 시간 축을 기준으로 사건이나 작업을 보여줄 때

중분류 (semantic tag) 언제 고르나
Timeline 날짜, 기간, 순서, 마일스톤 등 시간 축을 기준으로 사건이나 작업을 보여줄 때

Hierarchy#

상하 관계, 소속 관계, 계층 구조, 레이어 구조가 핵심일 때

중분류 (semantic tag) 언제 고르나
HierarchicalTree 상위-하위, 부모-자식, 조직 구조처럼 계층적 연결 관계를 보여줄 때
Pyramid 넓은 기반에서 좁은 정점으로 쌓이는 층 구조. 각 층의 너비·면적이 중요도·우선순위·위계를 시각적으로 암시

ScopeCoverage#

중심에서 외부로 확장되는 범위, 포함 영역, 적용 범위를 보여줄 때

중분류 (semantic tag) 언제 고르나
NestedScope 중심 개념을 기준으로 포함 범위, 영향권, 적용 영역, 레이어가 안쪽→바깥쪽으로 확장되는 구조를 보여줄 때. 안쪽 = 더 좁은·핵심·최소 범위, 바깥 = 더 넓은·포괄 범위

표현 (2026-08-25 원본 시트에서 옮김). 시트는 이것을 "동심원 계층 구조" 라고 부른다. 중심에서 바깥으로 퍼져나가고, 안쪽 원이 핵심이자 가장 작은 범위, 바깥 원이 이를 포함하는 더 큰 범위다. TAM/SAM/SOM 이 전형이다.

Hierarchy.HierarchicalTree 와의 경계. 시트가 이 종을 "계층 구조" 라고 부르는 것이 중요하다. 단일 사슬 포함 관계는 가지가 하나뿐인 트리와 구조적으로 같은 것이어서 HierarchicalTree 도 유효한 읽기가 된다. 둘을 가르는 것은 의미가 아니라 동심원으로 그리는지다. 골든셋 실측에서 이 종이 판마다 5~7 사이를 진동한 이유이며, 그래서 표현 지시를 허용하는 종으로 다룬다. 근거는 ../plan/golden-set-blueprint.md 9.1절.

Cycle#

과정이나 상태가 한 방향으로 끝나지 않고 반복, 순환, 재귀적으로 이어지는 구조를 보여줄 때

중분류 (semantic tag) 언제 고르나
Cycle 프로세스나 상태가 끝나지 않고 반복, 순환, 피드백되는 구조를 보여줄 때

ReasoningStructure#

원인, 문제, 해결책, 조합 등 결과나 주장을 도출하는 논리 구조를 보여줄 때

중분류 (semantic tag) 언제 고르나
Fishbone 문제나 결과의 원인을 여러 갈래로 분해해 분석할 때 (문제를 중심에 두고 복수 원인이 퍼져나가는 경우)
CombinedResult 여러 요소의 결합이 하나의 결과나 결론으로 이어지는 구조를 보여줄 때 (결합되어 결과가 도출되는 게 핵심)
ProblemSolution 문제와 대응 방안, 기대 효과를 짝지어 보여줄 때 (문제-원인/해결-기대효과가 연결지어 나오는 것이 핵심)

KPIKeyMetric#

핵심 숫자, 대표 성과 지표, 목표 대비 달성 상태를 강조할 때

중분류 (semantic tag) 언제 고르나
BigNumber 가장 중요한 수치나 KPI를 크게 강조해 한눈에 전달할 때

Geo#

지리적 위치, 지역별 분포, 특정 지역 강조, 지역 간 이동 흐름을 지도 위에 보여줄 때

중분류 (semantic tag) 언제 고르나
Flow (지도 전용) 지역 간 이동, 물류, 유입·유출 흐름을 지도 위에 연결해 보여줄 때

TextBasedCommunication#

비교, 흐름, 위계, 수치, 지도처럼 별도의 관계 구조보다 문장이나 본문의 의미를 전달하는 것이 핵심일 때. 텍스트를 압축해 강조하거나, 본문 흐름으로 전개해 사용자가 내용을 읽고 이해하도록 보여준다.

중분류 (semantic tag) 언제 고르나
HighlightedMessage 사용자가 반드시 기억해야 할 하나의 핵심 문장 또는 하나의 완결된 주장(정의·주의사항·권고·결론·안내 포함)을 화면 중심에 크게 배치해 강조하는 구성. 아래 조건을 모두 충족할 때만 선택한다.
LongFormTextLayout 하나의 주제·가치·배경·사례·맥락을 여러 문장 이상의 본문 흐름으로 풀어 설명하고, 이해를 돕기 위해 선택적으로 이미지·제목·그래픽 요소를 함께 배치하는 구성. 정보 단위를 독립 항목으로 분리해 병렬 나열하기보다, 장문의 글로 시각화한 것이 핵심이다.

PageNavigation#

프레젠테이션의 시작, 목차, 구간 전환, 마무리처럼 페이지 역할 자체가 핵심일 때

중분류 (semantic tag) 언제 고르나
Opening 발표 슬라이드의 맨 앞장을 구성할 때만 사용
TableOfContents 발표의 전체 구성, 섹션, 순서를 안내할 때
SectionDivider 발표 중간의 장 전환이나 섹션 시작을 알릴 때
Ending 발표 맨 마지막 장에 감사한다·발표 종료를 표현할 때 (본문 내용의 결론 요약은 해당하지 않고 종결 용도로만 쓰임)

대분류 정의 (2026-08-25 원본 시트 레이아웃 텍사노미 신규 탭에서 옮김). 콘텐츠 자체가 아니라 프레젠테이션의 구조와 흐름을 안내하는 슬라이드다. 표지(시작/끝 선언), 목차(전체 구조 안내), 간지(섹션 전환 신호)가 해당하며, 공통적으로 정보를 전달하기보다 읽는 사람의 위치를 알려주는 기능을 한다.

중분류의 성립 조건. 같은 탭이 4종 각각의 조건을 적어 두었다. capability-table.md 2.6절이 "성립 조건 미정" 으로 비워 둔 자리가 이것이다. 정하지 않은 결정이 아니라 규약이 아직 옮겨 적지 않은 것이었다.

중분류 시트의 성립 조건
Opening 제목·부제목·발표자·날짜 등 기본 메타정보를 중앙에 배치하는 단일 슬라이드 구조
TableOfContents 전체 섹션 구조를 번호와 제목으로 나열. 항목 간 순서와 계층은 있지만 흐름(화살표)이나 단계(계단)는 없다
SectionDivider 다음 섹션의 번호·제목·간단한 설명만을 중앙에 배치. 콘텐츠 없이 "지금 주제가 바뀐다" 는 신호를 전달하는 것이 유일한 목적이다. TableOfContents 와 달리 전체 구조가 아닌 단일 섹션만 다룬다
Ending "감사합니다", "Q&A", "Thank You" 등 단일 메시지만 크게 배치. 별도 콘텐츠 없이 발표가 끝났음을 알리는 역할만 한다. Opening 이 시작을 선언하는 것과 대칭적 관계다

시트 안에서 어긋나는 자리 하나. 시트의 Opening 정의는 "프레젠테이션의 표지 또는 마지막 슬라이드처럼" 이라고 적어 Ending 과 겹친다. 그런데 같은 시트의 Ending 정의가 "Opening 이 시작을 선언하는 것과 대칭적 관계" 라고 적어 스스로 어긋난다. 위 표의 "맨 앞장을 구성할 때만" 을 유지한다 (2026-08-25 황현진 결정). 두 종이 배타적이어야 채점이 가른다. 원본을 고칠지는 단어장 오너(김수현) 확인 사항이다.

시트의 표기 오류. 시트가 Section Devider 로 적었다. 규약 어휘와 이 문서는 SectionDivider 다.

QnACard#

질문과 답변을 대응하여 보여줄 때

중분류 (semantic tag) 언제 고르나
QnACard 질문과 답변을 대응하여 보여줄 때

Utterance#

질문, 대화, 증언, 인용문처럼 발화 형식의 콘텐츠를 보여줄 때

중분류 (semantic tag) 언제 고르나
DialogueChat 두 명 이상 또는 사용자와 서비스 간 대화를 말풍선·채팅 형태로 보여줄 때
QuoteTestimonial 고객 후기, 인터뷰 인용, 핵심 발언을 강조할 때

셋을 가르는 축 (2026-08-25 원본 시트에서 옮김). 발화 계열은 교환이 있는지로 갈린다. QuoteTestimonial 은 Q&A 나 Dialogue 와 달리 응답이나 교환 없이 단방향으로 발화를 제시한다(발화자의 이름·직책·사진이 함께 오는 일이 많다). QnACard(별도 대분류)는 질문과 답변이 쌍을 이루며 반복된다. FAQ, OX 퀴즈, 골든벨이 그것이다. DialogueChat 은 두 화자가 교번으로 메시지를 주고받는다.

PortraitProfile#

인물, 역할, 경력, 프로필 정보를 소개할 때

중분류 (semantic tag) 언제 고르나
ProfileCard 인물의 사진, 이름, 역할, 핵심 정보를 카드로 소개할 때
ResumeCV 경력, 학력, 역량, 프로젝트 이력을 이력서 형식으로 이력 중심으로 보여줄 때

둘을 가르는 축 (2026-08-25 원본 시트에서 옮김). 대분류는 데이터 비교나 프로세스 없이 단일 인물(또는 소수 인물)의 정보를 소개하는 것이 목적이다. ProfileCard 는 사진+이름+연락처를 한 카드 안에 압축 배치한다(한 사람이든 여러 명이든 카드로 나열). ResumeCV 는 학력·경력·자격·수상 같은 이력 항목을 섹션+구분선으로 문서형 배치한다.


2. 표 / 차트 카탈로그 (23종)#

Enumeration#

동등한 수준의 정보 단위를 관계·흐름·비교보다 목록화와 묶음 중심으로 보여줄 때

중분류 (semantic tag) 언제 고르나
ListingTable 비교가 아니라 목록 정보를 행과 열로 구조화해 표로 정리할 때

축 (2026-08-25 원본 시트에서 옮김). 행이 항목이고 열이 그 항목의 속성(이름·설명·수치)이다. 비교가 아닌 목록화·인덱싱이 목적일 때 쓴다. AttributeComparison.ComparisonTable 과 축이 뒤집힌 관계이므로 무엇이 첫 열에 오는지로 가른다.

AttributeComparison#

동일한 기준이나 속성 축을 기준으로 대상 간 차이, 강약점, 우열을 비교할 때

중분류 (semantic tag) 언제 고르나
ComparisonTable 여러 기준이나 속성을 행·열로 정리해 여러 대상을 같은 기준으로 나란히 놓고 차이를 보게 할 때 사용
RadarChart 동일한 평가 기준 여러 개에서 대상의 강약점이나 프로파일 차이를 수치로 비교할 때

축 (2026-08-25 원본 시트에서 옮김). ComparisonTable 은 행이 지표이고 열이 대상이다. 동일한 평가 지표를 기준으로 여러 대상을 한 테이블에서 나란히 견주며 체크마크·수치·텍스트로 비교값을 적는다. Enumeration.ListingTable(행=항목, 열=속성)과 전치 관계다. Versus 는 두 대상을 중앙 기점(VS 마크·구분선) 좌우로 대칭 배치해 세부 속성 비교보다 대결 구도 자체를 전달할 때다.

RelationalComparison#

항목별 차이를 나열하기보다 대상 간 상대적 위치, 영역, 교집합, 구조적 연관성을 중심으로 보여줄 때

중분류 (semantic tag) 언제 고르나
Quadrant 두 축을 기준으로 대상의 상대적 위치나 포지션을 네 영역으로 나누어 보여줄 때
BubbleChart 두 축의 위치와 버블 크기를 함께 사용해 대상 간 상대적 위치와 규모를 동시에 보여줄 때

Conversion#

단계가 진행될수록 대상이 선별, 축소, 전환되는 흐름과 변화 비율을 보여줄 때

중분류 (semantic tag) 언제 고르나
Funnel 많은 대상이 단계별로 줄어들거나 선별·전환되는 퍼널 전환을 보여줄 때

TimelineSchedule#

날짜, 기간, 순서, 마일스톤 등 시간 축을 기준으로 사건이나 작업을 보여줄 때

중분류 (semantic tag) 언제 고르나
Timeline 날짜, 기간, 순서, 마일스톤 등 시간 축을 기준으로 사건이나 작업을 보여줄 때
GanttChart 작업별 시작·종료·기간·병렬 진행을 일정표 형태로 보여줄 때 (열에 분기·월 등 시간, 행에 작업 목록·할 일을 두고 각 작업의 시작·종료를 표현한 경우)
Calendar 월 단위의 일정이나 이벤트를 달력 형태로 보여줄 때

Hierarchy#

상하 관계, 소속 관계, 계층 구조, 레이어 구조가 핵심일 때

중분류 (semantic tag) 언제 고르나
HierarchicalTree 상위-하위, 부모-자식, 조직 구조처럼 계층적 연결 관계를 보여줄 때

ProportionComposition#

전체 대비 구성 비율이나 상대적 규모 차이를 보여줄 때

중분류 (semantic tag) 언제 고르나
TreeMap 면적 크기로 전체 대비 구성 비율과 상대적 규모를 보여줄 때
PieChart 전체를 여러 조각으로 나누어 구성 비율을 보여줄 때
DonutChart 중앙 공간을 활용하면서 전체 대비 구성 비율을 보여줄 때
StackedBarChart 항목별 전체값 안의 구성 비중을 막대 세그먼트로 보여줄 때
PictogramChart 아이콘 반복이나 채움 정도로 비율, 수량을 직관적으로 보여줄 때

Trend#

시간이나 순서에 따른 수치 변화, 증가·감소 패턴, 흐름을 보여줄 때

중분류 (semantic tag) 언제 고르나
LineAreaChart 시간이나 순서에 따른 수치 변화와 추세를 선 또는 면적으로 보여줄 때
ComboChart 동일한 시간(또는 순서) 축 위에서 서로 다른 성격의 지표를 한 차트에 겹쳐 보여주는 구조

MagnitudeRanking#

항목 간 수치 크기, 순위, 상대적 규모 차이를 비교할 때

중분류 (semantic tag) 언제 고르나
BarChart 항목 간 수치 크기, 순위, 상대적 규모를 막대 길이로 비교할 때

KPIKeyMetric#

핵심 숫자, 대표 성과 지표, 목표 대비 달성 상태를 강조할 때

중분류 (semantic tag) 언제 고르나
Gauge 목표 대비 달성률, 진행률, 임계치 대비 상태를 게이지 형태로 보여줄 때

Geo#

지리적 위치, 지역별 분포, 특정 지역 강조, 지역 간 이동 흐름을 지도 위에 보여줄 때

중분류 (semantic tag) 언제 고르나
Distribution (지도 전용) 지역별 수치나 밀도, 분포 차이를 지도 위에 보여줄 때
Location (지도 전용) 특정 장소, 지점, 매장, 거점을 지도 위에 표시할 때
Highlight (지도 전용) 특정 국가, 지역, 권역을 지도로 표시해 강조하거나 선택 영역을 부각하는 것이 적합할 때
Flow (지도 전용) 지역 간 이동, 물류, 유입·유출 흐름을 지도 위에 연결해 보여줄 때

3. 실사용 빈도 (질의 쪽, 2026-07-06~07-12)#

전체 목록 중 실제로 어느 라벨이 얼마나 나오는지다. 출처와 모수는 markdown-summary-samples.md 1절과 같다(사용자 입력 7,168 block).

| 순위 | 중분류 | 절대수 | 비중 | 누적 | 데이터용 | Page역할 | 구현 우선순위 | | 1 | List | 2,291 | 31.96% | 31.96% | N | N | P0 | | 2 | ListingTable | 1,081 | 15.08% | 47.04% | Y | N | P0 | | 3 | Steps | 783 | 10.92% | 57.96% | N | N | P0 | | 4 | ComparisonTable | 519 | 7.24% | 65.20% | Y | N | P0 | | 5 | HighlightedMessage | 412 | 5.75% | 70.95% | N | N | P0 | | 6 | LongFormTextLayout | 342 | 4.77% | 75.72% | N | N | P0 | | 7 | Timeline | 306 | 4.27% | 79.99% | N | N | P0 | | 8 | Versus | 223 | 3.11% | 83.10% | N | N | P0 | | 9 | BarChart | 120 | 1.67% | 84.77% | Y | N | P0 | | 10 | HierarchicalTree | 91 | 1.27% | 86.04% | Y | N | P1 | | 11 | BigNumber | 86 | 1.20% | 87.24% | N | N | P1 | | 12 | ProfileCard | 78 | 1.09% | 88.33% | N | N | P2 | | 13 | Opening | 71 | 0.99% | 89.32% | N | Y | P1 | | 14 | TableOfContents | 63 | 0.88% | 90.20% | N | Y | P1 | | 15 | Transformation | 62 | 0.86% | 91.06% | N | N | P0 | | 16 | ProblemSolution | 61 | 0.85% | 91.91% | N | N | P1 | | 17 | 제거됨 | 55 | 0.77% | 92.68% | Y | N | — | | 18 | LineAreaChart | 50 | 0.70% | 93.38% | Y | N | P0 | | 19 | Calendar | 46 | 0.64% | 94.02% | Y | N | P4 | | 20 | Cycle | 36 | 0.50% | 94.52% | N | N | P0 | | 21 | Ending | 34 | 0.47% | 94.99% | N | Y | P1 | | 22 | Staircase | 33 | 0.46% | 95.45% | N | N | P0 | | 23 | QnACard | 33 | 0.46% | 95.91% | N | N | P2 | | 24 | Gauge | 30 | 0.42% | 96.33% | Y | N | P0 | | 25 | FlowChart | 28 | 0.39% | 96.72% | N | N | P3 | | 26 | PieChart | 22 | 0.31% | 97.03% | Y | N | P0 | | 27 | GanttChart | 20 | 0.28% | 97.31% | Y | N | P4 | | 28 | QuoteTestimonial | 18 | 0.25% | 97.56% | N | N | P1 | | 29 | ComboChart | 18 | 0.25% | 97.81% | Y | N | P0 | | 30 | SectionDivider | 17 | 0.24% | 98.05% | N | Y | P1 | | 31 | Flow | 17 | 0.24% | 98.29% | Y | N | P4 | | 32 | ResumeCV | 16 | 0.22% | 98.51% | N | N | P2 | | 33 | DonutChart | 14 | 0.20% | 98.71% | Y | N | P0 | | 34 | DialogueChat | 13 | 0.18% | 98.89% | N | N | P1 | | 35 | CombinedResult | 12 | 0.17% | 99.06% | N | N | P1 | | 36 | StackedBarChart | 10 | 0.14% | 99.20% | Y | N | P0 | | 37 | Fishbone | 10 | 0.14% | 99.34% | N | N | P3 | | 38 | Quadrant | 10 | 0.14% | 99.48% | Y | N | P3 | | 39 | SWOT | 8 | 0.11% | 99.59% | N | N | P0 | | 40 | VennDiagram | 6 | 0.08% | 99.67% | N | N | P1 | | 41 | NestedScope | 6 | 0.08% | 99.75% | N | N | P1 | | 42 | Funnel | 5 | 0.07% | 99.82% | Y | N | P2 | | 43 | PictogramChart | 4 | 0.06% | 99.88% | Y | N | P2 | | 44 | Pyramid | 4 | 0.06% | 99.94% | N | N | P1 | | 45 | RadarChart | 2 | 0.03% | 99.97% | Y | N | P2 | | 46 | Distribution | 2 | 0.03% | 100.00% | Y | N | P4 |

표 밖 4종 (질의 샘플 0건, 원본 시트엔 있음). TreeMap(P4), BubbleChart(P4), Location(P4), Highlight(P4) 다. 이 넷은 사용자 입력 7,168 block 표본에 한 번도 안 나와 순위표에서 빠졌다.

읽는 법 세 가지.

첫째, 상위 5종(List, ListingTable, ComparisonTable, Steps, HighlightedMessage)이 전체의 약 65% 이다. 롱테일 20여 종은 각 0.5% 미만이다.

둘째, Page역할 = Y 인 4종(Opening, TableOfContents, SectionDivider, Ending)은 의미 구조가 아니라 페이지 역할이다. README 2절 "헷갈리기 쉬운 셋" 둘째 항목의 Content Archetype 이 단어장 안에 섞여 들어와 있다. 이것을 검색 풀에서 어떻게 다룰지는 TODO.md C-4 에 있다.

셋째, 구현 우선순위 열은 P0~P4 다섯 단계 전량을 2026-08-17에 원본 시트(시멘틱 단어장v7 탭, 로컬 사본 확인)에서 옮겼다. 이전엔 P0 9개만 채워져 있었다. P0=19종, P1=13종, P2=7종, P3=3종, P4=7종(위 4종 포함)이다. 이 값은 규약의 등급(필수, 선호, 무시) 논의(schema-v0.4.md 4-1절)와 아직 연결돼 있지 않는다. 연결하는 것이 열린 결정 중 하나다.


4. 규약 component.type enum 과의 격차#

규약 v0.4 의 component.type enum 은 11종(list, big_number, venn, nested_scope, tree, pyramid, quote, dialogue, gauge, stacked_bar, combo)인데 단어장은 50여 종이다. 1:1 이 아니다.

v7 중분류 component.type 대응 비고
List list 1:1
BigNumber big_number 1:1
VennDiagram venn 1:1
NestedScope nested_scope 1:1
HierarchicalTree tree 1:1
Pyramid pyramid 1:1
QuoteTestimonial quote 1:1
DialogueChat dialogue 1:1
Gauge gauge 1:1
StackedBarChart stacked_bar 1:1
ComboChart combo 1:1
Steps / Timeline / Staircase / Cycle / FlowChart 없음 순서형. enum 에 대응 없어 list 로 fallback되거나 임시 매핑 중
Versus / SWOT / Quadrant / ComparisonTable 없음 비교형. Versus 는 venn 에 임시 매핑, Quadrant 는 v0.4 4절 역방향 갭(v0.5 에서 matrix 로 해소)
ListingTable / Calendar / GanttChart 없음 표·일정. v0.4 4절 역방향 갭 Table Gantt (v0.5 에서 matrix 로 해소)
PieChart / DonutChart / RadarChart / BubbleChart / TreeMap / PictogramChart / LineAreaChart / BarChart / Funnel 없음 dataset.type 가 gauge·stacked_bar·combo 3종뿐이라 담을 수 없음
Opening / TableOfContents / SectionDivider / Ending 없음 페이지에서의 자리다. 애초에 structure 가 표현할 대상이 아님
HighlightedMessage / LongFormTextLayout / ProblemSolution / CombinedResult / Fishbone / QnACard / ProfileCard / ResumeCV / Transformation / Distribution / Location / Highlight / Flow 없음 미매핑

이 격차가 걸었던 열린 결정은 2026-08-02 에 닫혔다. v0.5 가 대분류.중분류 결합 표기로 41종 확장을 택했다(schema-v0.5.md 0절, TODO.md B-11). 위 실사용 분포에서 미매핑 라벨이 질의의 절반 이상을 차지한다는 사실이 확장의 근거였고, 위 표는 그 결정 근거로 보존한다.


5. 갱신 이력#

일자 무엇이 바뀌었나
2026-08-01 repo 사본 최초 작성. 원본 시트 2026-08-01 02:56 KST 판 기준
2026-08-01 태그 표준 표기를 {major}_{minor}(Enumeration_List)로 정정. comp-sign v4.3.8 semanticTags.ts 확인 결과 v2.0 minor-only 어휘는 폐기됐다
2026-08-02 4절이 가리키는 규약 필드명이 hint.component 에서 component.type 로 바뀌었다. 개명 근거는 TODO.md G절
2026-08-25 원본 시트가 2026-08-25 08:41 UTC 에 바뀌어 스냅샷을 다시 맞췄다 (이전 스냅샷 2026-08-17). 레이아웃 텍사노미 신규 탭에 🆕 표시로 신규 보강된 정의를 옮겼다 — PageNavigation 4종의 성립 조건(위 절), NestedScope·VennDiagram·Gauge·발화 셋·인물 둘·Fishbone·표 둘. 골든셋 제작(MOR-2165)이 프롬프트로 역산하던 판별 기준이 시트에 이미 있었다. 근거와 반영 내역은 ../reference/golden-set-production-notes.md 1절

갱신 규칙 단어장은 2단계 배포 이후에도 바뀔 수 있다(README 2절 "지금은 capability 규약을 확정하지 않는다"). 원본 시트가 바뀌면 이 문서와 capability-table.md 를 함께 다시 맞춘다.

↑ 목차로 · 6부 머리로

37장스마트 다이어그램 데이터 모델 참고32종과 스키마, 세 층의 격차

원문 reference/smart-diagram-data-model.md GitHub 에서 보기 32 KB 원문 제목 「스마트 다이어그램 데이터 모델: 종류와 스키마」

이 문서의 역할 스마트 다이어그램이 실물 코드에서 어떻게 생겼는지 정리한 참조 문서다. 어떤 종류가 있고, 한 종류가 어떤 부품으로 이루어지고, 그중 무엇이 실제로 화면에 그려지고 저장되는지를 담는다. SSoT는 이 문서가 아니다. SSoT는 저장소와 패키지의 코드이고, 이 문서는 2026-08-02 기준 스냅샷이다 (0절). 오너 정원석 (MORDOR) · 상태 DRAFT · 최종 갱신 2026-09-05 (스마트 블록 편입 여부는 MORDOR 소관 아님으로 명시) 관련 schema-v0.4.md 10절 · TODO.md A-4, B-10, B-13 · semantic-taxonomy-v7.md · content-binding-contract.md

읽는 사람 스마트 다이어그램을 처음 보는 사람과, Visual Summary 스키마를 스마트 다이어그램에 연결하려는 사람이다. 사전 지식은 필요 없다.

이 문서 훑기

절 무엇에 답하나
1~4 전체 그림. 무엇이고, 이름이 왜 둘이고, 종류 목록이 왜 셋이고, 누가 무엇을 맡나
5~9 값의 목록. 종류 32종, 그려지는 16종, 저장되는 6종, 슬롯 어휘
10~12 Visual Summary 와의 대응, 고칠 것, 남은 확인
13 이 값들을 다시 확인하는 방법

0. 읽기 전에#

왜 만들었나 TODO.md A-4 "스마트 다이어그램과 스마트 오브젝트 데이터 모델 정합 확인"을 위해 문서 포맷과 종류 목록, 슬롯 어휘, 저장 contract 를 실물 코드에서 확인했다. schema-v0.4.md 10절이 2026-07-21 이전 상태를 근거로 쓰여 지금 실물과 어긋나 있어, 그 절을 대체할 실측 근거로 만들었다. 어긋난 내용은 11절에 있다.

이 문서는 스냅샷이다. 확인한 버전을 3절에 적었고, 다시 확인하는 방법은 13절에 있다.

출처 표기 규칙 코드 근거는 두 가지로 단다.

  • miricanvas-web-2 파일은 a558c0a 에 고정한 permalink 로 건다. main 이 움직여도 이 문서가 읽은 그 줄을 가리킨다.
  • 패키지 내부 파일(@miricanvas/smart-object-parser, @miricanvas/jari)은 사내 Nexus 의 배포 tarball 안에 있어 딥링크를 걸 수 없다. 패키지 링크와 버전, tarball 안의 경로를 함께 적는다. 받는 방법은 13절에 있다.

이름은 줄이지 않는다. 저장소와 패키지는 smart-object-ax, @miricanvas/jari, miricanvas-web-2 로 적는다. A-4, B-10 처럼 문자와 숫자를 묶은 것은 TODO.md 의 할 일과 열린 결정 항목 번호다. contract 는 지키지 않으면 거부되는 입출력 규약이고, 이 문서에서는 저장 contract(8절)와 항목 수 contract(6-1절) 둘을 가리킨다.


1. 스마트 다이어그램이 무엇인가#

글 한 벌을 넣으면 도형 배치가 나오는 요소다. 입력은 사람이 읽을 수 있는 문서 한 벌이다. 문서 앞머리에 어떤 그림을 그릴지 적고, 그 아래 본문에 마크다운으로 내용을 적는다. 그러면 그 내용이 벤 다이어그램이나 타임라인 같은 도형 배치로 화면에 놓인다.

여기서 개수를 정하는 것은 본문이다. 항목을 몇 개 그릴지는 설정값이 아니라 마크다운 heading 이 몇 개인지가 정한다(7절). 이 문서를 누가 언제 쓰는지, 즉 사내에서 미리 저작하는지 사용자 입력에서 생성하는지는 이 조사의 범위가 아니다.

1.1 부품 이름 셋: 종류, 컴포넌트, 슬롯#

이 문서 전체가 이 세 낱말로 쓰여 있다. 크기 순서로 겹쳐 있는 개념이다.

낱말 무엇인가 예
종류 (type) 어떤 그림인가. 32종 중 하나를 고른다 (5절) venn
컴포넌트 (component) 그 그림을 이루는 부품의 종류 venn 은 set 과 overlap 을 갖는다
슬롯 (slot) 부품 하나에 들어가는 값 한 칸 set 은 title, value, unit 같은 칸을 갖는다

한 장으로 보면 이런 모양이다.

type: venn              ← 종류. 어떤 그림인가
  set                   ← 컴포넌트. 이 그림을 이루는 부품 종류
    title               ← 슬롯. 부품에 들어가는 값 한 칸
    value
    unit
  overlap               ← 같은 그림의 다른 부품 종류

컴포넌트는 종류이지 개수가 아니다. set 이 화면에 두 개 놓일지 세 개 놓일지는 본문 heading 이 정하고, 그렇게 놓인 하나하나를 인스턴스라고 부른다.

여기에 이름이 하나 더 붙는다. vizType 은 에디터가 저장할 때 쓰는 종류 이름이다(8절). 값은 type 과 같은 낱말을 쓰지만 받는 범위가 6종으로 좁다.


2. 이름 두 개: 스마트 오브젝트와 스마트 다이어그램#

코드를 열면 같은 것을 가리키는 이름이 두 개 나온다. 먼저 이것을 정리해야 나머지가 읽힌다.

같은 것이다. 제품 명칭만 바뀌었다. 2026-07-03 에 한유진님이 실서비스 기능 명칭을 '스마트 다이어그램'으로 확정해 전사 공지했다 (Slack). 미리디 고객 설문과 내용시각화 PP 검토를 거쳐 결정했고, 슬랙과 위키, 지라 티켓의 용어를 '스마트 다이어그램'으로 통일하기로 했다. 개발 용어는 순차적으로 정리하기로 했다.

개발 쪽 정리는 절반만 끝났다. 어느 이름이 살아 있는지 층마다 다르다.

층 지금 쓰는 이름 근거
제품, 고객 노출 스마트 다이어그램 2026-07-03 한유진님 전사 공지 (Slack)
슬랙 채널 #프레젠테이션트라이브_스마트다이어그램 2026-07-03 개명. 프레젠테이션트라이브_스마트오브젝트 에서 바뀌었다 (Slack 2026-07-09 스쿼드 리포트, 같은 사실이 2026-07-07 리포트 에도 있다)
miricanvas-web-2 에디터 코드 SmartDiagram 2026-07-06 커밋 30685b8 "chore: smart-object 이름을 smart-diagram으로 변경" (ktkwon, 20개 파일)
저작 문서 포맷 smart-object/recipe-3.0 파서 0.17.0 dist/v3-frontmatter.js 의 SMART_OBJECT_RECIPE_SCHEMA_VERSION (패키지)
파서 패키지 @miricanvas/smart-object-parser 사내 Nexus 등록명 (패키지). miricanvas-web-2 가 0.16.0 을 의존한다 (packages/engine/editor/package.json:95)
파서 공개 타입 SmartObjectItem 파서 0.17.0 dist/smart-object-item.d.ts:49 (패키지). miricanvas-web-2 도 이 이름 그대로 import 한다 (smart-diagram/AGENTS.md 저장 contract 절)
저작 저장소 smart-object-ax scripts/node/link-smart-object.mjs 머리 주석 "스오(smart-object-ax) 패키지를 publish 없이 로컬 clone으로 임시 연결/해제한다"
프리뷰 배포 URL smart-object.preview.miricanvas.com 2026-07-09 스쿼드 리포트 (Slack)

읽는 법 문서에 SmartObject 나 smart-object 가 나오면 저작 문서와 파서 쪽을 가리킨다. SmartDiagram 이 나오면 miricanvas-web-2 에디터 쪽이다. 둘은 같은 기능의 다른 자리이지 서로 다른 기능이 아니다.

명칭 후보는 다섯이었다. 2026-06-25 에 이설희님이 프로덕트그룹 PD 팀에 스마트 오브젝트, 스마트 다이어그램, 스마트 레이아웃, 스마트 디자인, 인포그래픽 다섯을 놓고 의견을 물었다 (Slack).

주의: 스마트 블록은 다른 요소다. 스마트 블록은 스마트 다이어그램 이전부터 있던 별개 요소다. 2026-07-02 에 손유정님이 스마트 블록을 스마트 다이어그램의 하위 카테고리로 편입하고 요소 탭을 하나로 합치자고 제안했다 (Slack). 앞서 한유진님도 상위 카테고리를 '다이어그램'으로 바꾸는 안을 냈고, 스마트 블록이 다이어그램에 해당하는지는 애매하다고 적었다 (Slack).

MORDOR/Visual Summary 소관 아님 (2026-09-05 정원석). 이 편입 여부는 미리캔버스 에디터/PD팀의 UI 분류 결정이지 이 프로젝트가 정할 것이 아니고, 9월 말까지의 범위에도 들어가지 않는다. 참고용 배경 기록으로만 남긴다.


3. 종류 목록이 세 곳에서 각각 다르다#

이 문서에서 하나만 가져간다면 이 절이다. 스마트 다이어그램의 데이터 모델은 한곳에 있지 않고 세 곳에 나뉘어 있다. 저작 문서, 레이아웃 엔진, 에디터다. 그리고 곳마다 쓸 수 있는 종류 목록이 다르다. 문서에는 적을 수 있는데 화면에 안 놓이는 종류가 있고, 화면에는 놓이는데 에디터가 저장하지 못하는 종류가 있다. A-4 가 확인해야 할 정합은 이 셋 사이의 격차다.

mermaid
flowchart LR
  A["저작 문서<br/>파서가 32종을 정의"] --> B["레이아웃 엔진<br/>그중 16종을 그린다"] --> C["에디터<br/>그중 6종을 저장한다"]

숫자 셋으로 줄이면 이렇다. 저작 문서 포맷은 32종을 정의하고, 그중 16종이 화면에 그려지고, 그중 6종만 에디터가 저장한다. 뒤로 갈수록 좁아지고, 좁아지면서 잘려 나간 것이 격차다. 어느 종류가 어디서 잘리는지는 5절 표의 뒤 두 열에 O 와 X 로 적어 두었다.

어디 무엇을 정하나 실물 확인한 버전
저작 문서 어떤 종류가 있고 각 종류가 어떤 컴포넌트와 슬롯을 갖는가 @miricanvas/smart-object-parser 최신 0.17.0 (2026-07-31 15:36 UTC 배포, Nexus 메타데이터 time 필드). miricanvas-web-2 는 0.16.0 고정 (package.json:95)
레이아웃 엔진 그중 무엇을 실제로 배치할 수 있는가 @miricanvas/jari 0.12.0. Nexus dist-tags.latest 와 miricanvas-web-2 고정 버전이 같다 (package.json:93)
에디터 그중 무엇을 저장하고 편집할 수 있는가 miricanvas-web-2 packages/engine/node-props-schema main a558c0a

각 층의 상세는 차례로 5절, 6절, 8절이다.

버전 격차는 이 조사에 영향을 주지 않는다. miricanvas-web-2 가 고정한 파서 0.16.0 과 최신 0.17.0 의 차이는 content-surface-guard, content-surface-split, surface-style 세 모듈뿐이다. 종류 목록과 스키마 레지스트리, 카탈로그에는 차이가 없다. 두 배포판 tarball 의 dist 를 diff -rq 로 비교해 확인했다 (2026-08-02, 이 문서의 실측).

보조 패키지 @miricanvas/md-engine 0.2.0 이 마크다운을 위계 트리(OutlineNodeTree)로만 변환하는 공통 파서다. 2026-07-07 에 권경태님이 역할을 이 수준으로 축소하고 타입별 슬롯 해석을 outline-unit 으로 분리하기로 결정했다 (Slack).


4. 저장소와 패키지 넷이 무엇을 맡는가#

3절의 세 곳은 저장소와 패키지 넷에 나뉘어 있다. 이름이 문서 전체에 반복해서 나오므로 여기서 한 번 묶어 둔다.

이름 무엇인가 이 문서에서
smart-object-ax 저작 문서를 담는 저장소 저작 문서의 입력
@miricanvas/smart-object-parser 저작 문서를 읽어 컴포넌트 인스턴스를 계획하는 파서 저작 문서의 SSoT
@miricanvas/jari 계획된 것을 화면 좌표로 배치하는 레이아웃 엔진 레이아웃 엔진
miricanvas-web-2 미리캔버스 에디터 에디터

jari 라는 이름에 대해 이 패키지는 smart-object-ax 저장소의 packages/layout-engine 을 배포한 것이다. 매핑은 miricanvas-web-2 의 scripts/node/link-smart-object.mjs 에 { name: '@miricanvas/jari', dir: 'packages/layout-engine' } 로 적혀 있고, 권경태님도 같은 설명을 남겼다 (Slack 2026-07-24).

이름의 유래는 확인하지 못했다. 설계문서 스오 레이아웃 엔진(초안)(권경태, 2026-06-12)은 전 절에서 layout-engine 으로만 부르고 jari 는 12절에 배포명으로 한 번 나올 뿐이다. 이름이 처음 보이는 것은 2026-06-19 의 브랜치 feat/UNICORN-74561-jari 인데 그 티켓에는 본문이 없고, Slack 과 Confluence, 저장소 어디에도 작명 근거가 없다. 2026-08-02 기준 권경태님께 확인이 필요하다.

한 물건에 이름이 둘이다. 설계문서는 layout-engine, 소비하는 쪽(miricanvas-web-2 코드와 이 문서)은 jari 로 부른다. 어느 쪽이 SSoT인지 선언돼 있지 않다.

smart-object-ax 저장소는 2026-08-02 기준 접근되지 않아, 배포된 파서 패키지의 dist 를 근거로 삼았다(13절).


5. 저작 문서가 정의하는 종류 32종#

파서 0.17.0 의 TYPE_NAMES 가 종류의 SSoT다. tarball 안 dist/catalog/common/type-name.js 에 있다 (패키지). 같은 tarball 의 dist/schema/registry.js 도 ALL_TYPES = TYPE_NAMES 로 그 배열을 그대로 쓴다.

아래 표의 '주요 컴포넌트'는 dist/schema/registry.js 의 SCHEMAS 에서 타입별 정의를 읽고 공통 메타 컴포넌트(5-1절)를 뺀 것이다.

뒤 두 열을 읽는 법 이 두 열이 3절에서 말한 격차를 종류별로 펼친 것이다.

화면에 그려지나 열의 O 는 그 종류를 그릴 머신이 레이아웃 엔진에 구현돼 있다는 뜻이고, X 는 저작 문서에는 쓸 수 있지만 화면에 나오지 않는다는 뜻이다. 에디터가 저장하나 열은 에디터가 그 종류를 저장하고 편집할 수 있는지다. 판정 근거는 각각 6절과 8절이다.

# type 주요 컴포넌트 화면에 그려지나 에디터가 저장하나
1 circular center, cycle_node, cycle_connector, guide, label O O
2 hub_and_spoke hub, spoke, spoke_connector O O
3 venn set, overlap O O
4 staircase step O O
5 timeline axis, event O O
6 hierarchical hierarchy_node, hierarchy_node_root, hierarchy_node_branch, hierarchy_node_leaf O O
7 linear_venn set, companion, overlap X X
8 semicircle_list item, arc_label X X
9 steps step X X
10 arrow_chain chain_step X X
11 roadmap phase, milestone X X
12 pyramid layer, layer_attachment X X
13 quadrant quadrant_cell, axis_label, corner_label, middle_band X X
14 funnel stage X X
15 concentric_rectangles layer X X
16 tile_grid tile, axis_label_x, axis_label_y, center X X
17 weighted_scorecards scorecard X X
18 flow_chart decision_node, action_node, terminator, connector_label X X
19 transformation transformation_before, transformation_after, transformation_arrow X X
20 swot swot_strength, swot_weakness, swot_opportunity, swot_threat X X
21 versus versus_left, versus_right, versus_center X X
22 quote quote_body, quote_attribution, quote_source_image, quote_mark_open, quote_mark_close X X
23 radial_progress track, indicator, metric, caption O X
24 bar_chart title, grid, xAxis, yAxis, series, data, legend, markPoint, markLine, markArea O X
25 line_chart title, grid, xAxis, yAxis, series, data, legend O X
26 scatter_chart 위와 같음 O X
27 heatmap_chart 위와 같음 O X
28 treemap_chart title, series, data, legend O X
29 pictogram_chart title, series, data, track, indicator, legend O X
30 packed_bubble_chart title, series, data, legend O X
31 pie_chart title, series, data, legend O X
32 radar_chart title, grid, axis, axisLabel, series, data, legend O X

이 표에서 읽을 것 둘. 첫째, O 가 붙은 것은 16종이고 그중 여섯이 miricanvas-web-2 까지 간다. 1번부터 6번까지가 세 층을 모두 통과하는 유일한 묶음이다. 둘째, 7번부터 22번까지 열여섯 종은 저작 문서에는 쓸 수 있지만 어느 층에서도 화면에 놓이지 않는다.

카탈로그 등록 수가 주석과 어긋난다. dist/catalog/index.d.ts 머리 주석은 "27 type 전체가 카탈로그로 등록됨"이라고 적혀 있지만 같은 파일의 CatalogIndex 실물은 32종 전부를 담고 있다. 주석이 갱신되지 않은 것으로 보인다. 이 문서는 실물 배열을 기준으로 적었다.

5.1 공통 메타 컴포넌트 7종#

위 표에 적지 않았지만 모든 종류가 함께 갖는 컴포넌트가 일곱 있다. dist/schema/registry.js 의 getSchema(type) 이 아래 일곱(META_COMPONENTS)을 자동으로 붙인다. 타입 고유 컴포넌트가 같은 이름을 정의하면 그쪽이 우선한다.

컴포넌트 슬롯
diagram_title text, subtitle
diagram_intro text
diagram_legend items, description
annotation label, description, position
caption text, position
relation_label text, from, to, position
axis_label text, pole_low, pole_high, axis

5.2 공통 지표 슬롯 4종#

value 를 갖는 컴포넌트는 타입과 무관하게 sign, value, unit, delta 를 함께 받는다. 부호와 값, 단위, 증감을 각각 다른 칸에 담아 시각적으로 따로 다루려는 것이다. dist/schema/registry.js 의 METRIC_SLOTS 주석이 이것을 "metric sign/value/unit 시각 분리" 전역 규칙이라고 적고 있다.

이 묶음을 받는 컴포넌트는 venn.set, linear_venn.set, hierarchical 의 노드 4종, hub_and_spoke 의 hub 와 spoke, staircase.step, quadrant.quadrant_cell, funnel.stage, concentric_rectangles.layer, tile_grid.tile, weighted_scorecards.scorecard, versus_left, versus_right 다.

schema-v0.4.md 의 component.type: big_number 갭(그 문서 11절)과 직접 맞물리는 지점이다. 큰 숫자를 보여 주는 표현이 저쪽에는 컴포넌트로 있는데 이쪽에는 슬롯 묶음으로만 있다.


6. 레이아웃 엔진이 그릴 수 있는 것#

jari 0.12.0 tarball 안 dist/templates/catalog.js 의 TYPE_CATALOG 가 실제로 배치할 수 있는 것의 SSoT다 (패키지). 프롬프트 타입 id 18개가 배치 머신 16개로 매핑된다. 여러 id 가 한 머신을 공유한다. 파이 차트와 도넛 차트는 id 가 둘이지만 배치는 pie 머신 하나가 맡는 식이다. 같은 파일의 listSupportedTypes() 주석이 이 배열을 "Prompt type ids the engine can lay out today" 라고 적고 있다.

세는 단위가 5절과 다르다. 5절에서 O 가 붙은 smart-object-ax 종류는 16개인데 여기 id 는 18개다. jari 가 chart.pie 와 chart.donut 을 따로 세는 것처럼 한 종류를 변형까지 나눠 세기 때문이다. 둘은 같은 것을 다른 단위로 센 수다.

프롬프트 타입 id 배치 머신
radial.hub_spoke hub_spoke
radial.polar, radial.ring_cycle circular
hierarchy.tree hierarchy_tree
staircase staircase
timeline timeline
overlap.venn_sets venn
chart.pie, chart.donut pie
chart.bar bar
chart.line line
chart.scatter scatter
chart.heatmap heatmap
chart.treemap treemap
chart.pictogram pictogram
chart.packed_bubble packed_bubble
chart.radar radar
radial.progress radial_progress

id 수는 18개로 실측 확정했다 (2026-08-02). 초판 조사 기록에 19개로 적혀 있었으나, jari 0.12.0 tarball 의 TYPE_CATALOG 객체를 다시 세어 18개임을 확인했다. 위 표가 전량이고 빠진 id 는 없다. 배치 머신 16개는 그대로다.

파서가 아는 템플릿 이름은 44개다. 파서 0.17.0 dist/layout-template-manifest.js 의 LAYOUT_TEMPLATE_NAMES 에 sequence.axis_three_band, grid.mosaic, flow.swimlane 같은 이름까지 44개가 있다 (패키지). 저작 문서는 이 44개 중 하나를 layoutRecipe.template 에 적을 수 있지만, jari 가 실제로 배치할 수 있는 것은 6절 표의 18개뿐이다. 나머지를 적은 문서가 어떻게 처리되는지는 확인하지 못했다.

6.1 항목 수 제약과 위상#

배치 머신은 항목을 무한정 받지 않는다. jari 0.12.0 dist/templates/content-contract.js 의 LAYOUT_CONTENT_CONTRACTS 가 다이어그램형 머신 6종의 항목 수 상하한을 정한다 (패키지). 차트형 머신 10종에는 이 contract 가 없다.

'위상'은 항목들이 서로 어떤 관계로 놓이는지다. peers 는 대등한 항목들이 나란히 놓이는 것, rooted_peers 는 중심 하나에 나머지가 붙는 것, tree 는 항목이 다시 항목을 품는 것이다.

머신 위상 주 항목 역할 최소 최대 루트
timeline peers event 2 15 없음
staircase peers step 2 8 없음
venn peers set 2 3 없음
circular peers node 2 8 center (선택)
hub_spoke rooted_peers spoke 2 8 hub (필수)
hierarchy_tree tree node 1 없음 node (필수)

miricanvas-web-2 는 이 contract 를 getLayoutContentContract(template) 로 읽어 쓴다 (SmartDiagramLayoutTemplateResolver.ts).

이 표가 TODO.md B-1 의 실측 근거다. B-1 은 검색에서 필수 요구를 필수, 선호, 무시 3단계로 나눌지 정하는 항목이다. 지금은 통과 아니면 탈락 이분법인데, venn 은 집합이 4개 이상이면 배치되지 않는 것처럼 머신마다 상한이 있다. 요구를 엄격하게 걸었을 때 후보가 0이 되는 문제를 판단하려면 이 상하한을 capability 테이블에 반영해야 한다.

위상 3종이 TODO.md B-3 과 대응할 여지가 있다. B-3 은 계층이 그 표현에 꼭 필요한지를 essential, incidental, none 세 값으로 선언할지 정하는 항목이다. peers, rooted_peers, tree 와 값 집합이 대응할 여지가 있지만, 이 대응은 이 문서의 관찰이고 확정이 아니다. 확인이 필요하다.


7. 저작 문서 포맷: smart-object/recipe-3.0#

에디터 쪽으로 가기 전에, 파서가 입력으로 받는 문서가 어떻게 생겼는지 본다.

문서는 YAML frontmatter 와 마크다운 본문 한 벌이다. frontmatter 는 문서 맨 앞에 --- 로 감싸 두는 설정 블록이다. gray-matter 로 frontmatter 를 뽑고(실패하면 yaml-recover 로 복구 시도), remark-parse 로 본문을 AST 로 만든 뒤, 타입별 outline 파서가 컴포넌트 인스턴스를 계획한다. 이 순서는 파서 0.17.0 tarball 의 README.md "런타임 흐름" 절에 적혀 있다 (패키지).

받는 최상위 키 출처는 dist/v3-frontmatter.js 의 compileSmartObjectV3Frontmatter 다.

키 필수 무엇인가
schemaVersion 필수 smart-object/recipe-3.0 고정. 다른 값이면 거부한다
type 필수 5절의 32종 중 하나
blueprint (또는 visualTree) 필수 컴포넌트별 frame, slots, variants, connector, guide
canvasLayout 선택 레이아웃 변형과 파라미터
canvas 선택
theme 선택 tokens 와 styles 만 받는다. 다른 키는 거부한다
meta 선택

거부하는 레거시 키 6종 binding, layout, layoutRecipe, style, topology, adaptivity. 같은 파일의 LEGACY_PUBLIC_KEYS 배열이고, v3 문서에 이 키가 있으면 파싱이 실패한다.

색 SSoT는 hex 이다. 레거시 oklch 저작은 카탈로그 검증 전에 normalizeDocumentColors 가 hex 로 정규화한다 (dist/v3-frontmatter.js).

본문이 콘텐츠 SSoT이다. 1절에서 말한 대로 항목 개수는 마크다운의 heading 구조가 소유한다.

항목 heading 의 depth 는 타입과 문서마다 다르다. hubless circular 와 venn 의 집합은 H2, centered circular 와 hub spoke, staircase, timeline 의 event 는 H3 이다. miricanvas-web-2 는 절대 레벨을 고정하지 않고 computeUnitDepth(형제가 2개 이상인 가장 얕은 heading depth)로 문서마다 감지한다 (smart-diagram/AGENTS.md 커맨드 계층 절).

venn 의 교집합은 HTML 주석으로 표시한다. 집합 heading 과 교집합 heading 이 같은 H2 depth 로 섞여 있어서, 파서(outline-venn.js)가 <!-- overlap: 0, 2 --> 형태의 주석 유무로 둘을 구분한다. 멤버십은 집합 인덱스(0부터)로 적는다 (smart-diagram/AGENTS.md "venn set/overlap 구분" 절).


8. 에디터가 저장하는 것과 vizType 6종#

packages/engine/node-props-schema/src/nodes/SmartDiagramPropsSchema.ts 가 miricanvas-web-2 의 저장 contract SSoT다. 여기 선언되지 않은 필드는 저장 단계에서 거부된다. 스마트 다이어그램 노드는 dataVizProps 한 덩어리로 저장된다. 2026-07-14 에 강인권님이 exporter 모듈을 공유하면서 이 파일을 props 근거로 지목했다 (Slack).

SmartDiagramItemProps = NodePropsSchema + { dataVizProps }

dataVizProps (strict, 미선언 필드 거부)
  vizType       enum 6종: circular | hub_and_spoke | venn | staircase | timeline | hierarchical
  datasource    string   마크다운 본문. 콘텐츠 SSoT이고 인스턴스 개수를 소유한다
  encoding      record
  version       리터럴 2  유일한 저장 contract. v1 과 rootComponent/slots 입력은 거부한다
  schemaVersion string   선택. `smart-object-ax` 문서 스키마 버전이고 version 과 별개다
  blueprint     record   컴포넌트 역할 식별자 → { role, frame, slots, variants, variantRules, guide, connector }
  theme         record   { tokens, styles }
  canvasLayout  record   { type, componentBindings, targets, root, parameters, constraints, connectors, orbit, underlay, overflow, elements }
  overrides     record   선택. componentName → (PresentationOverride | null)[]

출처는 SmartDiagramPropsSchema.ts:86-107 이고, 각 필드의 의도는 smart-diagram/AGENTS.md 의 저장 contract 절이 적고 있다.

저장하지 않는 것 rootComponent 와 slots 는 저장하지 않는다. datasource, blueprint, theme, canvasLayout 에서 매 revision 마다 메모리 스냅샷으로 다시 만든다. canvas 의 너비와 높이도 저장하지 않는다. 노드의 BoundingBox 가 SSoT다 (smart-diagram/AGENTS.md 의 저장 contract 절과 "파생 vs 저장 경계" 절).

vizType 과 canvasLayout.type 은 다른 어휘다. 이름이 둘 다 type 이라 헷갈리는 자리다.

vizType 은 smart-object-ax frontmatter 의 type 을 그대로 받는 파서 라우팅 키이고, canvasLayout.type 은 radial_ring_cycle 같은 레이아웃 변형 키다. 둘 다 저장한다. resolveLayoutTemplate 이 둘을 jari 머신 이름으로 정규화한다 (SmartDiagramLayoutTemplateResolver.ts).

hierarchical 은 저장은 되지만 항목 편집이 안 된다. 항목 추가와 삭제, 재정렬 커맨드가 hub_and_spoke, circular, staircase, timeline, venn 다섯만 지원한다.

hierarchical 은 '끝' 개념이 없어 미지원 상태이고, 타입 변경(TypeChange)도 no-op stub 이다 (smart-diagram/AGENTS.md 커맨드 계층 절의 '미지원' 항목).


9. 슬롯과 컴포넌트 어휘#

schema-v0.4.md 의 structure 와 대응시킬 때 쓰는 어휘 넷이다. 넷 다 파서 0.17.0 tarball 의 dist/catalog/common/ 에 zod enum 으로 박혀 있어, 값이 어긋나면 파싱 단계에서 거부된다 (패키지). 즉 여기 없는 낱말은 저작 문서에 쓸 수 없다.

9.1 slotType 33종#

슬롯 한 칸이 어떤 성격의 값을 담는지 정하는 어휘다. 출처는 dist/catalog/common/slot-type.js 이고, 아래 묶음 이름도 그 파일의 주석을 옮긴 것이다.

묶음 값
일반 시맨틱 title, subtitle, label, description, value, caption
식별과 메타 name, avatar, icon, organization
시간 period, time
지표 sign, unit, delta
구조와 파생 index, marker, position, axis
연결선 끝점 from, to
도메인 전용 pole_low, pole_high, step_label, conversion_rate, bullets, items, goals, graphic
자연어 본문 text
구조 슬롯 block

9.2 componentType 8종#

부품이 어떤 부류인지 정하는 어휘다. CONTAINER, SERIES, DATA, GRAPHIC, TITLE, SUBTITLE, AXIS, LEGEND. 출처는 dist/catalog/common/component-type.js 다.

9.3 ComponentRole 6종#

부품이 그림 안에서 어떤 역할을 맡는지 정하는 어휘다. hero, focal, data_node, relation, annotation, decoration. 출처는 dist/component-role.js 의 COMPONENT_ROLE_VALUES 다. 저작 문서가 명시하지 않으면 같은 파일의 inferComponentRole 이 컴포넌트 이름에서 추론한다. 이름에 mark 나 decor 가 있으면 decoration, center 나 hub 로 끝나면 focal, connector 나 overlap 이 있으면 relation 이다.

이 6종이 TODO.md I-2 의 전제를 바꾼다. I-2 는 같은 문제를 세 곳이 서로 다른 기준으로 풀고 있는 것을 하나로 정렬하는 작업이다. 셋은 RLSC 노드 분류(CORE / DESC / DECO / OUT)와 장식 등급(N / N+α / N+β), comp-sign 변형 3종이다. 그런데 여기 네 번째인 ComponentRole 6종이 있다. decoration 과 annotation 이 DECO 와 DESC 에 대응하는지 확인이 필요하다.

9.4 SlotFill 채움 출처 3종#

슬롯을 무엇으로 채우는지의 어휘다. dist/schema/registry.js 가 슬롯마다 기본 채움 출처를 data 또는 derived 로 지정한다. data 는 내용에서 직접 오는 것이고, derived 는 다른 값에서 계산해 내는 것이다. index, marker, position, axis, conversion_rate 같은 파생 슬롯이 derived 이다.

schema-v0.4.md 10절이 이미 data, derived, structure 세 종류와 Visual Summary 의 직접 채움, 파생 투영, 중첩의 대응을 적어 두었고, 그 대응은 그대로 유효하다.

9.5 topology 어휘#

저작 문서의 topology 블록이 시각 배치를 선언한다. 출처는 dist/catalog/common/topology.js 다. shape 16종(arc, cyclic, free, grid, linear, nested, network, opposed, overlap, path, radial, single, spiral, stacked, stepped, tree), connectorPattern 8종, ordering 10종, symmetry 9종이다.

주의 topology 는 smart-object/recipe-3.0 에서 최상위 키로는 거부된다(7절). 컴포넌트 수준 정보로만 쓴다.


10. Visual Summary component.type 와의 대응#

여기서부터가 이 조사를 시작한 이유다. schema-v0.4.md core schema 의 component.type enum 11종은 Visual Summary 쪽에서 이런 그림으로 보여 달라고 적는 값이고, smart-object-ax 타입 32종은 실제로 그릴 수 있는 것이다. 둘이 맞물리는지를 본다.

이 대응은 이 문서의 해석이고 확정이 아니다. 이름과 컴포넌트 구성으로 추정했고, 대치 실측으로 확인해야 한다.

component.type smart-object-ax 의 type 후보 화면에 그려지나 에디터가 저장하나 비고
list 대응 없음 smart-object-ax 카탈로그에 list 타입이 없다. steps 나 tile_grid 가 가장 가깝다. 질의의 31.96% 를 차지하는 최대 수요인데 대응이 없다 (semantic-taxonomy-v7.md 3절)
big_number 대응 없음 5-2절의 지표 슬롯 4종(sign, value, unit, delta)이 컴포넌트가 아니라 슬롯 묶음으로만 있다
venn venn, linear_venn O / X O / X venn 은 집합 2~3개만 배치된다 (6-1절)
nested_scope concentric_rectangles X X topology.shape: nested 로 분류돼 있다 (9-5절)
tree hierarchical O O TODO.md B-10 의 답이다 (11절)
pyramid pyramid X X jari TYPE_CATALOG 에 머신이 없다 (6절)
quote quote X X jari TYPE_CATALOG 에 머신이 없다 (6절)
dialogue 대응 없음
gauge radial_progress O X jari 는 배치하지만 miricanvas-web-2 vizType enum 이 받지 않는다 (8절)
stacked_bar bar_chart O X smart-object-ax 쪽에도 stacking 표식이 없다. TODO.md B-12 와 같은 문제다
combo 대응 없음

이 표에서 읽을 수 있는 것 셋

  1. 양쪽 다 되는 것은 tree 와 venn 둘뿐이다. 나머지 아홉은 어느 한 층에서 막힌다.
  2. 질의 최대 수요인 list 에 대응 타입이 없다. TODO.md D-2 는 이 List 편중을 그대로 두고 2단계를 낼 것인가를 정하는 항목인데, 이 사실이 그 판단의 근거가 된다.
  3. gauge 와 차트 계열은 jari 와 miricanvas-web-2 사이에서 끊긴다. jari 는 차트 머신 10종을 갖고 있는데 miricanvas-web-2 의 vizType enum 은 차트를 하나도 받지 않는다. 차트는 별도 노드 타입으로 가는 것으로 보이고, 확인이 필요하다 (12절).

11. schema-v0.4.md 10절에서 고칠 것#

schema-v0.4.md 10절은 2026-07-21 이전 상태를 근거로 쓰였다. 진술 셋이 지금 실물과 어긋난다. 아래 표가 정정 내용이다.

10절의 진술 2026-08-02 실물 근거
"Smart Object 는 독립 타입이 아니라 DataViz 위에 얹힌 vizType 이다(SmartObjectItem extends DataVizItem)" 클래스 이름만 바뀌고 관계는 그대로다. SmartDiagramItem extends DataVizItem SmartDiagramItem.ts:151
"트리 시각화는 SmartObjectItem 이 아니라 별도 TreeItem 으로 분리돼 있고 SmartObject 는 graph 등 확장용 placeholder 상태다" hierarchical 이 저장 스키마의 vizType enum 6종에 정식으로 들어가 있고, 레이아웃 엔진에 hierarchy_tree 머신이 구현돼 있다. placeholder 가 아니다 SmartDiagramPropsSchema.ts:5-12 · jari 0.12.0 dist/templates/catalog.js (패키지)
데이터 모델 상세를 yaml-examples.md Appendix A 의 Matrix, ComponentProps, SlotFill, Encoding 정의로 참조 저장 contract 가 version: 2 로 바뀌었다. v1 과 rootComponent/slots 를 포함한 입력은 스키마와 프리셋 로더가 거부한다 SmartDiagramPropsSchema.ts:91 · smart-diagram/AGENTS.md 저장 contract 절

TODO.md B-10 은 절반이 닫혔다. B-10 은 tree 와 nested_scope 를 SmartObject 로 볼지 별도 TreeItem 으로 볼지 정하는 항목이다. tree 쪽은 답이 나와 있다. hierarchical 은 스마트 다이어그램 타입이다. nested_scope 쪽은 smart-object-ax 카탈로그의 concentric_rectangles 가 대응 후보로 보이지만(5절), miricanvas-web-2 가 지원하지 않아 아직 열려 있다.


12. A-4 와 B-13 에 남은 것#

이 문서로 A-4 의 문서 조사 부분은 끝났다. 남은 것은 실측이다.

남은 확인 무엇을 하면 되나 관련 항목
dataset 을 dataVizProps 로 넘기는 어댑터 실측 8절의 저장 contract 가 version: 2 로 바뀌었으므로, yaml-examples.md Appendix A 의 Matrix 기준으로 만든 어댑터 설계를 다시 봐야 한다 TODO.md A-4, B-13
ComponentRole 6종을 I-2 에 넣을지 9-3절. 네 번째가 있다는 사실 자체가 I-2 의 전제를 바꾼다 TODO.md I-2
jari 항목 수 상하한을 capability 테이블에 넣을지 6-1절의 표를 rlsc-capability.yaml 에 반영 TODO.md B-1, B-4, B-14
위상 3종과 계층 3단계의 대응 6-1절의 peers, rooted_peers, tree 가 essential / incidental / none 과 대응하는지 TODO.md B-3
차트 계열이 miricanvas-web-2 의 어느 노드 타입으로 가는지 10절 세 번째 항목. packages/engine/editor/src/node/item/ 의 차트 아이템 확인 TODO.md B-8, B-12
nested_scope 를 concentric_rectangles 로 볼지 10절. jari 머신이 없어 지금은 어느 쪽도 렌더되지 않는다 TODO.md B-10

jari 프롬프트 타입 id 수 불일치(19 또는 18)는 2026-08-02 실측으로 18개 확정되어 이 표에서 뺐다(6절).


13. 실물을 다시 확인하는 방법#

이 문서는 2026-08-02 기준 스냅샷이므로, 값이 맞는지 다시 볼 때 아래 경로를 쓴다.

확인할 것 어디
종류 목록 파서 tarball dist/catalog/common/type-name.js 의 TYPE_NAMES
타입별 컴포넌트와 슬롯 파서 tarball dist/schema/registry.js 의 SCHEMAS
슬롯, 컴포넌트, 역할 어휘 파서 tarball dist/catalog/common/ 의 slot-type.js, component-type.js, topology.js 와 dist/component-role.js
문서 포맷 규칙 파서 tarball dist/v3-frontmatter.js, README.md
배치 가능 목록과 항목 수 제약 jari tarball dist/templates/catalog.js, dist/templates/content-contract.js
miricanvas-web-2 저장 contract SmartDiagramPropsSchema.ts
miricanvas-web-2 편집 contract 와 지원 범위 smart-diagram/AGENTS.md
miricanvas-web-2 가 고정한 패키지 버전 packages/engine/editor/package.json:93-95

패키지 받는 법 사내 Nexus 에 있다. 인증 없이 메타데이터와 tarball 을 받을 수 있는 것을 2026-08-02 에 확인했다.

bash
# 버전 목록과 배포 시각
curl -s "https://repo.miridih.com/nexus/repository/npm-miri-group/@miricanvas%2Fsmart-object-parser" \
  | jq '.["dist-tags"], .time'

# tarball 받아 풀기
curl -sO "https://repo.miridih.com/nexus/repository/npm-miri-group/@miricanvas/smart-object-parser/-/smart-object-parser-0.17.0.tgz"
tar -xzf smart-object-parser-0.17.0.tgz    # -> package/dist/...

smart-object-ax 저장소는 2026-08-02 기준 miridih-wsj 계정으로 접근되지 않아(GET /repos/miridih/smart-object 가 404), 배포된 패키지의 dist 를 근거로 삼았다. 저장소 접근 권한을 받으면 dist 가 아니라 소스와 주석을 직접 볼 수 있다.

↑ 목차로 · 6부 머리로

38장Markdown Summary vs Visual Summary 비교 참고같은 입력, 무엇을 잃고 되찾나

원문 reference/summary-comparison-samples.md GitHub 에서 보기 15 KB 원문 제목 「같은 입력, Markdown Summary 와 Visual Summary 비교」

이 문서의 역할 같은 사용자 입력이 현행 Markdown Summary 로는 어떻게 나오고 Visual Summary 로는 어떻게 나오는지를 나란히 놓는다. 무엇을 잃고 무엇을 되찾는지를 실사용 입력으로 확인하는 문서다. 오너 정원석 (MORDOR) · 상태 DRAFT · 최종 갱신 2026-08-02 관련 markdown-summary-samples.md 실사용 샘플 전량 · schema-v0.5.md 규약 · yaml-examples.md 규약 예시

왼쪽은 실측, 오른쪽은 조립이다. 사용자 입력 과 Markdown Summary 는 markdown-summary-samples.md 의 실사용 로그 원문 그대로다. Visual Summary 는 생성기가 아직 없어서(로드맵 3단계 실행하지 않음) 규약에 맞춰 이 문서에서 손으로 조립한 것이다. 생성기가 실제로 이렇게 쓴다는 근거가 아니라, 규약이 이 입력을 어디까지 담을 수 있는지를 보이는 자료다.

조립한 YAML 8종은 규약 스키마 검증을 통과한다 (2026-08-02 확인).

KDL 도 함께 실었다. KDL 은 규약이 아니라 검토 자료다(format-alternatives.md). 2절부터 5절까지 네 사례는 KDL 을 파싱해 JSON 으로 바꾼 결과가 같은 사례의 YAML 과 바이트 단위로 같은 JSON 이 되는 것을 확인했다 (2026-08-02).

인용한 Markdown Summary 의 줄바꿈은 2026-08-02 에 복원한 것이다. markdown-summary-samples.md 에 실려 있던 표 20건이 한 줄로 눌려 있었고, 그 문서에서 복원했다. 방법과 한계는 그 문서 0.1절에 있다. 글자 내용은 손대지 않았다.

이 문서 훑기

절 무엇을 보나
1 앞의 네 사례에서 되찾는 것을 한 표로
2 표 → matrix
3 수치 → value 와 unit
4 시각 → period
5 번호 → 배열 순서와 type
6~9 복잡한 입력 넷. 위계, 마커 격자, 다중 컴포넌트, 못 담는 경우

1. 무엇이 달라지나#

네 사례 모두 입력에는 있었는데 Markdown Summary 에서 문자열로 눌린 값이 있다. Visual Summary 는 그것을 필드로 되돌린다.

사례 카테고리 Markdown 이 잃은 것 Visual Summary 가 담는 자리
94449_1 ListingTable 어느 열이 범주이고 어느 열이 값인지가 안 남는다 matrix 의 columns 와 role
94669_1 BarChart 31.5% 가 숫자가 아니라 글자다 role: value 열과 unit
100119_1 Timeline 시각이 활동명과 같은 칸의 글자다 entity.period
100046_2 Steps '1. → 2.' 가 ## 두 개가 된다 배열 순서와 type

왜 이게 문제인가 검색과 대치가 문자열을 못 쓴다. 31.5% 가 글자면 막대 높이를 정할 수 없고, 시각이 활동명에 붙어 있으면 타임라인 축에 놓을 수 없다. content-binding-contract.md 가 적은 대로 2026-07-26 BAD 원인 분석에서 BAD 의 85% 가 컴포넌트와 내용이 안 맞는 문제였고, 그 세부 원인 중 하나가 이것이다.


2. 표가 타입 없는 글자로 남는 경우 (94449_1)#

사용자 입력 (원문)

text
2030년 국가 핵심 목표 (단기 로드맵) 총 재생에너지 누적 설비 용량: 100GW 내외 확충 (태양광 + 풍력 등) 태양광 발전 목표 비율: 전체 설비의 60% 이상으로 유도 (약 60GW+ 수준 담당) 재생에너지 발전 비중: 2030년 20%, 2035년 30% 이상 달성

현행 Markdown Summary (원문)

markdown
# 2030년 국가 핵심 목표 (단기 로드맵)

| 구분 | 목표 수치 |
| :--- | :--- |
| **총 재생에너지 누적 설비 용량** | 100GW 내외 확충 |
| **태양광 발전 목표 비율** | 전체 설비의 60% 이상 유도 (약 60GW+ 수준) |
| **재생에너지 발전 비중** | 2030년 20%, 2035년 30% 이상 달성 |

Visual Summary (이 문서에서 조립)

yaml
version: "0.5.4"
title: 2030년 국가 핵심 목표 (단기 로드맵)
components:
  - type: Enumeration.ListingTable
    structure:
      dataset:
        shape: matrix
        columns:
          - { label: 구분, role: category }
          - { label: 목표 수치 }
        rows:
          - [총 재생에너지 누적 설비 용량, 100GW 내외 확충]
          - [태양광 발전 목표 비율, "전체 설비의 60% 이상 유도 (약 60GW+ 수준)"]
          - [재생에너지 발전 비중, "2030년 20%, 2035년 30% 이상 달성"]

같은 것을 KDL 로 적으면

kdl
summary "0.5" "2030년 국가 핵심 목표 (단기 로드맵)" {
  component "Enumeration.ListingTable" {
    matrix {
      col "구분" role="category"
      col "목표 수치"
      row "총 재생에너지 누적 설비 용량" "100GW 내외 확충"
      row "태양광 발전 목표 비율" "전체 설비의 60% 이상 유도 (약 60GW+ 수준)"
      row "재생에너지 발전 비중" "2030년 20%, 2035년 30% 이상 달성"
    }
  }
}

되찾는 것 행과 열이 배열이 되고, 첫 열이 범주 축이라는 것이 role: category 로 선언된다. 마크다운 표도 행과 열은 담지만 어느 열이 범주이고 어느 열이 값인지는 담지 못한다. 대치할 때 어느 값이 어느 칸에 들어가는지가 여기서 갈린다.


3. 값과 단위가 문자열에 남는 경우 (94669_1)#

사용자 입력 (원문)

text
성별 임금 격차 (29위 - 최악):임금 격차가 31.5%로 OECD 평균(13.5%)의 2배가 넘음

현행 Markdown Summary (원문)

markdown
# 성별 임금 격차 현황

| 항목 | 수치 |
|---|---:|
| 성별 임금 격차 | 31.5% |
| OECD 평균 | 13.5% |
* 한국의 성별 임금 격차는 OECD 평균(13.5%)의 2배 이상인 31.5%로, OECD 회원국 중 29위에 해당

Visual Summary (이 문서에서 조립)

yaml
version: "0.5.4"
title: 성별 임금 격차 현황
components:
  - type: MagnitudeRanking.BarChart
    structure:
      entities:
        - { label: 한국, value: 31.5, unit: "%", emphasis: true, items: [OECD 회원국 중 29위] }
        - { label: OECD 평균, value: 13.5, unit: "%" }
      dataset:
        shape: matrix
        columns:
          - { label: 항목, role: category }
          - { label: 임금 격차, role: value, unit: "%" }
        rows:
          - [한국, 31.5]
          - [OECD 평균, 13.5]

같은 것을 KDL 로 적으면

kdl
summary "0.5" "성별 임금 격차 현황" {
  component "MagnitudeRanking.BarChart" {
    entity "한국" value=31.5 unit="%" emphasis=true {
      items "OECD 회원국 중 29위"
    }
    entity "OECD 평균" value=13.5 unit="%"
    matrix {
      col "항목" role="category"
      col "임금 격차" role="value" unit="%"
      row "한국" 31.5
      row "OECD 평균" 13.5
    }
  }
}

되찾는 것 31.5 가 숫자이고 % 가 단위다. 막대 높이를 계산할 수 있고, 두 값의 비(2.33배)도 계산해서 쓸 수 있다.

'OECD 회원국 중 29위'도 그대로 담는다. structure 는 entities 와 dataset 을 함께 가질 수 있다. 표로 그릴 수 있는 두 수치는 dataset 에 넣고, 표에 자리가 없는 순위는 해당 항목의 items 로 붙인다. Markdown 이 담던 것을 Visual Summary 가 흘리면 이 작업의 목적이 무너진다. 담을 자리가 없어 보이면 자리를 만들어야지 버리면 안 된다.


4. 시간이 사라지는 경우 (100119_1)#

사용자 입력 (원문)

text
(7/24. 금)  4:30- 5:00 등록 5:00-5:30 오프닝 5:30-6:30 레크레이션 6:30-7:30 저녁 식사 7:30-9:00 기도회 9:00-10:00 야식 및 교제 10:00~ 취침

현행 Markdown Summary (원문)

markdown
# 7/24 (금) 일정표

| 시간 | 활동 |
| :--- | :--- |
| 16:30 - 17:00 | 등록 |
| 17:00 - 17:30 | 오프닝 |
| 17:30 - 18:30 | 레크레이션 |
| 18:30 - 19:30 | 저녁 식사 |
| 19:30 - 21:00 | 기도회 |
| 21:00 - 22:00 | 야식 및 교제 |
| 22:00 - | 취침 |

Visual Summary (이 문서에서 조립)

yaml
version: "0.5.4"
title: 7/24 (금) 일정표
components:
  - type: TimelineSchedule.Timeline
    structure:
      entities:
        - { label: 등록, period: "16:30-17:00" }
        - { label: 오프닝, period: "17:00-17:30" }
        - { label: 레크레이션, period: "17:30-18:30" }
        - { label: 저녁 식사, period: "18:30-19:30" }
        - { label: 기도회, period: "19:30-21:00" }
        - { label: 야식 및 교제, period: "21:00-22:00" }
        - { label: 취침, period: "22:00-" }

같은 것을 KDL 로 적으면

kdl
summary "0.5" "7/24 (금) 일정표" {
  component "TimelineSchedule.Timeline" {
    entity "등록" period="16:30-17:00"
    entity "오프닝" period="17:00-17:30"
    entity "레크레이션" period="17:30-18:30"
    entity "저녁 식사" period="18:30-19:30"
    entity "기도회" period="19:30-21:00"
    entity "야식 및 교제" period="21:00-22:00"
    entity "취침" period="22:00-"
  }
}

되찾는 것 시각이 period 라는 제 자리에 들어간다. 컴포넌트가 시간 축을 가진 타임라인이면 그 축에 놓을 수 있고, 축이 없는 컴포넌트로 fallback 하면 period 를 그냥 안 읽는다(규약 1절 "선언되지 않은 것은 무시한다").


5. 순서가 사라지는 경우 (100046_2)#

사용자 입력 (원문, 발췌)

text
… 2. 입찰 참여 및 견적 제출
-견적 제출: 참여 업체들은 현장 상황과 본인들의 기술력, 장비 가동 효율을 고려하여
 업체별 단가(낙찰가)를 기입한 견적서를 작성합니다.
-기한 준수: 정해진 마감 기한 내에 견적서와 함께 시공 계획서, 장비 사양서 등을 제출합니다. …

현행 Markdown Summary (원문)

markdown
# 입찰 참여 및 견적 제출

## 1. 견적 제출
* 참여 업체 현장 상황 고려 
* 기술력 및 장비 가동 효율 고려 
* 업체별 단가(낙찰가) 기입 
* 견적서 작성

## 2. 기한 준수
* 정해진 마감 기한 내 제출 
* 시공 계획서 제출 
* 장비 사양서 제출

Visual Summary (이 문서에서 조립)

yaml
version: "0.5.4"
title: 입찰 참여 및 견적 제출
components:
  - type: ProcessFlow.Steps
    structure:
      entities:
        - label: 견적 제출
          items:
            - 참여 업체 현장 상황 고려
            - 기술력과 장비 가동 효율 고려
            - 업체별 단가(낙찰가) 기입
            - 견적서 작성
        - label: 기한 준수
          items:
            - 정해진 마감 기한 내 제출
            - 시공 계획서 제출
            - 장비 사양서 제출

같은 것을 KDL 로 적으면

kdl
summary "0.5" "입찰 참여 및 견적 제출" {
  component "ProcessFlow.Steps" {
    entity "견적 제출" {
      items "참여 업체 현장 상황 고려" "기술력과 장비 가동 효율 고려" "업체별 단가(낙찰가) 기입" "견적서 작성"
    }
    entity "기한 준수" {
      items "정해진 마감 기한 내 제출" "시공 계획서 제출" "장비 사양서 제출"
    }
  }
}

되찾는 것 항목이 둘이라는 사실, 각 항목의 제목과 하위 항목의 구분, 그리고 순서가 있다는 사실이다.

Markdown 쪽은 ## 1. 과 ## 2. 라는 문자열만 남는다. 이게 순서형인지 그냥 두 덩어리인지 문자열에서 구분되지 않는다. Visual Summary 는 순서를 배열 순서가 담고, '이 순서에 의미가 있다'는 것은 type 이 ProcessFlow 라는 사실이 담는다. 그래서 params.sequence 를 따로 적지 않아도 된다.



여기부터는 입력이 길고 구조가 겹쳐 있는 사례다. 앞의 넷이 필드 하나씩을 보였다면, 이 넷은 규약이 실제로 어디까지 버티는지를 본다.

사례 무엇을 시험하나 결과
99115_1 3단으로 겹친 위계 children 중첩으로 담긴다
95249_5 ■ 로 칠한 격자 start 와 end 로 되살아난다
94894_1 전후 대비와 목록이 한 장에 컴포넌트 둘로 나뉜다
95838_2 화살표가 든 대비 못 담는다. 아래 참고

6. 3단 위계 (99115_1)#

사용자 입력 (원문, 발췌)

text
1. 인건비 (100단위) 110 인건비: 공무원의 봉급, 각종 수당(겸임, 정근, 성과상여금 등) 및 기간제 근로자, 공무직, 계약제 교원의 보수를 포함합니다. 2. 물건비 (200단위) 210 운영비: 기관 운영을 위한 일반수용비(사무용품, 인쇄비, 소모품 등), 공공요금, 피복비, 급량비, 시설장비유지비 등 가장 기본적인 행정 경비입니다. 220 여비: 국내 및 국외 출장, 외빈 초청 및 훈련을 위한 여비입니다. … 3. 이전지출 (300단위) 310 보전금: 포상금, 보상금, 법정부담금 등을 포함합니다. …

현행 Markdown Summary (원문)

markdown
# 예산 분류 체계

## 1. 인건비 (100단위)
* 110 인건비
* 공무원의 봉급
* 각종 수당(겸임, 정근, 성과상여금 등)
* 기간제 근로자, 공무직, 계약제 교원의 보수

Visual Summary (이 문서에서 조립)

yaml
version: "0.5.4"
title: 예산 분류 체계
components:
  - type: Hierarchy.HierarchicalTree
    structure:
      entities:
        - label: 인건비
          period: "100단위"
          children:
            - label: 110 인건비
              items:
                - 공무원의 봉급
                - 각종 수당(겸임, 정근, 성과상여금 등)
                - 기간제 근로자, 공무직, 계약제 교원의 보수
        - label: 물건비
          period: "200단위"
          children:
            - { label: 210 운영비, items: [일반수용비, 공공요금, 피복비, 급량비, 시설장비유지비] }
            - { label: 220 여비, items: [국내외 출장, 외빈 초청, 훈련] }
            - { label: 230 업무추진비 }
            - { label: 240 복리후생비, items: [맞춤형 복지비, 교직원 복리후생, 교원연구비] }
            - { label: 250 직무수행경비 }
            - { label: 260 연구개발비 }
            - { label: 280 경상교육지원사업비 }
        - label: 이전지출
          period: "300단위"
          children:
            - { label: 310 보전금, items: [포상금, 보상금, 법정부담금] }
            - { label: 320 민간이전, items: [민간경상보조, 민간위탁금, 보험료부담금] }
            - { label: 330 자치단체등이전 }
            - { label: 340 해외이전 }

세 층이 세 층으로 남는다. 대분류(인건비, 물건비, 이전지출), 코드 항목(110, 210, 310 등), 그 아래 세부 경비가 entities → children → items 로 각각 자기 층에 놓인다. 예산 코드의 단위 표기는 period 에 넣었다.

양이 다르니 이 절은 품질 비교가 아니다. 왼쪽 Markdown 은 대분류 셋 중 인건비 하나만 담고 있는데, 이 데이터는 입력을 --- 로 잘라 여러 block 으로 나눈 것이라 나머지 둘이 다른 block 에 있다(markdown-summary-samples.md 0.2절). 오른쪽은 입력 전체를 담았다. 여기서 볼 것은 담은 양이 아니라 세 층이 세 층으로 남는가다.


7. 마커 격자에서 기간을 되살린다 (95249_5)#

현행 Markdown Summary (원문)

markdown
#

# 추진 일정

| 추진 과제 | 1월 | 2월 | 3월 | 4월 | 5월 | 6월 |
| :--- | :---: | :---: | :---: | :---: | :---: | :---: |
| 시장조사 및 요구사항 분석 | ■ | ■ | | | | |
| 상품 기획 및 제도 설계 | | ■ | ■ | | | |
| 전산 개발 및 시스템 구축 | | | ■ | ■ | | |
| 시범 운영(Pilot) | | | | ■ | ■ | |
| 마케팅 및 직원 교육 | | | | | ■ | ■ |
| 상품 출시 및 성과관리 | | | | | | ■ |

Visual Summary (이 문서에서 조립)

yaml
version: "0.5.4"
title: 추진 일정
components:
  - type: TimelineSchedule.GanttChart
    structure:
      dataset:
        shape: matrix
        columns:
          - { label: 추진 과제, role: category }
          - { label: 시작, role: start, unit: 월 }
          - { label: 종료, role: end, unit: 월 }
        rows:
          - [시장조사 및 요구사항 분석, 1, 2]
          - [상품 기획 및 제도 설계, 2, 3]
          - [전산 개발 및 시스템 구축, 3, 4]
          - [시범 운영(Pilot), 4, 5]
          - [마케팅 및 직원 교육, 5, 6]
          - [상품 출시 및 성과관리, 6, 6]

이 사례가 가장 큰 차이를 만든다. Markdown 의 ■ 는 그림을 글자로 흉내 낸 것이다. 6열 격자를 그대로 옮겨 적었을 뿐, '이 과제가 1월에 시작해 2월에 끝난다'는 사실은 어디에도 적혀 있지 않다. 칠해진 칸의 위치를 사람이 눈으로 읽어야 안다.

role: start 와 role: end 로 적으면 그 사실이 값이 된다. 그래서 셋이 가능해진다. 기간이 6개월이 아니라 12개월인 컴포넌트에 넣어도 맞고, 월 단위를 분기 단위로 바꿔 그릴 수 있고, '가장 긴 과제'를 계산할 수 있다. 격자를 옮겨 적은 표로는 셋 다 못 한다.

대신 판단이 하나 들어간다. ■ 두 칸을 '1월 시작, 2월 종료'로 읽은 것은 해석이다. 격자가 연속이 아니라 띄엄띄엄 칠해져 있으면 이 변환이 성립하지 않는다. capability-table.md 2.2절의 GanttChart 성립 조건이 "작업마다 시작과 끝"을 요구하므로, 연속이 아닌 격자는 GanttChart 로 못 가고 Enumeration.ListingTable 로 남는다.


8. 한 장에 컴포넌트가 둘 (94894_1)#

사용자 입력 (원문, 발췌)

text
1 Discover Instagram  첫 인지 접점 형성  Before (문제) 비전공자가 Entegris를 알 기회 부족  After (변화) 학생 일상 속에서 자연스럽게 발견  남는 가치 (지속 자산) ✓ 콘텐츠 플랫폼 (SNS 채널) ✓ 학생 맞춤 소통 채널 ✓ 성과 데이터 축적 및 분석 …

현행 Markdown Summary (원문)

markdown
# 1 Discover Instagram

## Before (문제)
* 비전공자가 Entegris를 알 기회 부족

## After (변화)
* 학생 일상 속에서 자연스럽게 발견

## 남는 가치 (지속 자산)
* 콘텐츠 플랫폼 (SNS 채널)
* 학생 맞춤 소통 채널
* 성과 데이터 축적 및 분석

Visual Summary (이 문서에서 조립)

yaml
version: "0.5.4"
title: 1 Discover Instagram
components:
  - type: Transformation.Transformation
    params: { direction: forward }
    structure:
      entities:
        - { label: "Before (문제)", items: [비전공자가 Entegris를 알 기회 부족] }
        - { label: "After (변화)", items: [학생 일상 속에서 자연스럽게 발견] }
      relations:
        - type: compares
          of: ["Before (문제)", "After (변화)"]
          desc: 첫 인지 접점 형성
  - type: Enumeration.List
    structure:
      entities:
        - { label: 콘텐츠 플랫폼 (SNS 채널) }
        - { label: 학생 맞춤 소통 채널 }
        - { label: 성과 데이터 축적 및 분석 }

## 셋이 같은 무게가 아니다. Before 와 After 는 짝이고, '남는 가치'는 그 둘의 결과를 나열한 별개 덩어리다. Markdown 은 셋을 같은 ## 로 적어 이 차이를 지운다. Visual Summary 는 컴포넌트를 둘로 나눠, 앞은 전후 대비로 뒤는 목록으로 그리게 한다.

입력 첫 줄의 '첫 인지 접점 형성'은 Before 에도 After 에도 속하지 않는다. 그래서 relations 의 desc 로 간다. Markdown 요약에서는 이 문장이 통째로 사라졌다.

컴포넌트를 몇 개로 쪼갤지는 규약이 정하지 않는다. 상한을 두지 않기로 했고(TODO.md B-15), 어디서 자를지는 생성기 정책이다. 위는 이 문서의 판단이다.


9. 규약이 못 담는 것 (95838_2)#

현행 Markdown Summary (원문)

markdown
#

# CAN2: VCU ↔ Actuator FW

### VCU → FW
* act_cmd_payload_t(좌/우 구동 명령)
* owner_notify_t(소유권 통지)

### FW → VCU
* drive_command_ack_t(엔코더 좌/우 피드백)
* owner_notify_ack_t

Visual Summary (이 문서에서 조립)

yaml
version: "0.5.4"
title: "CAN2: VCU ↔ Actuator FW"
components:
  - type: AttributeComparison.Versus
    structure:
      entities:
        - { label: VCU → FW, items: ["act_cmd_payload_t(좌/우 구동 명령)", "owner_notify_t(소유권 통지)"] }
        - { label: FW → VCU, items: ["drive_command_ack_t(엔코더 좌/우 피드백)", owner_notify_ack_t] }

여기서는 되찾는 게 거의 없다. 두 방향을 entities 둘로 나눈 것이 전부다. 누가 누구에게 보내는가는 VCU → FW 라는 라벨 문자열 안의 화살표로만 남고, 이건 Markdown 과 똑같다.

방향을 값으로 담으려면 relations 에 방향 있는 어휘가 있어야 하는데, 규약의 관계 어휘는 intersects 와 compares 둘뿐이고 둘 다 방향이 없다. 그리고 이건 실수가 아니라 결정이다. projection-model.md 7절이 '구조로 적을 수 없는 제3의 내용을 가진 관계만 어휘로 둔다'로 기준을 세웠고, 방향은 그 기준에서 기각됐다.

그래서 이 입력에는 Visual Summary 로 바꿀 이득이 없다. 이런 사례가 실사용에서 얼마나 되는지는 세어 본 적이 없다. 규약을 넓힐지 판단하려면 그 수부터 필요하다 (TODO.md B-16 이 관계 어휘 확장 항목이다).


10. 참고#

↑ 목차로 · 6부 머리로

39장Markdown Summary 실사용 샘플 전량 참고기준선 · 분량이 크다

원문 reference/markdown-summary-samples.md GitHub 에서 보기 90 KB 원문 제목 「Markdown Summary 실사용 샘플 (2단계 이전 현행 산출물)」

이 문서의 역할 현행 Markdown Summary 가 실제 사용자 입력을 어떻게 평탄화하는지, 그 결과가 어느 semantic category 로 분류되는지를 실데이터로 남긴다. Visual Summary(YAML)가 이 대비 무엇을 더 보존하는지 비교하는 기준선이다. 대응하는 YAML 표현은 yaml-examples.md 에 있다.

인용 원문 주의 아래 사용자 입력, Markdown Summary, Layout Description, 판단 근거 네 칸은 로그와 모듈 출력 원문 그대로다. 문체나 표기를 고치지 않는다 (../../../WRITING.md 2.8 예외).

출처 Google Drive summary 706_712 5000개_split_시멘틱v2검증용 (Drive 폴더 내용시각화 시멘틱, 최종 수정 2026-07-30). 상세는 sources.md 3.1절 원 쿼리 component_search_request_log, 내부 팀(team_idx 4054·4057) 제외, created_date 2026-07-06 ~ 2026-07-12, 동일 input 중 최신 1건만 모수 사용자 입력 5,371건을 --- 로 분할해 7,208 block. 아래 라벨은 시멘틱 쿼리 v2 LLM 모듈이 붙인 것이고, 1,051 block 이 시트에 실려 있다.

오너 정원석 (MORDOR) · 상태 DRAFT · 최종 갱신 2026-08-02

이 문서 훑기

절 무엇을 보나
0.1 표 20건의 줄바꿈을 어떻게 복원했나
0.2 빈 heading 11건은 왜 안 고치나
0 각 샘플의 네 칸이 무엇인가
1 질의 쪽 분포 46종 전량
2 카테고리별 샘플 58건. 이 문서의 본체다
Appendix A 골든셋 90건

0.1 표 20건의 줄바꿈은 복원한 것이다 (2026-08-02)#

이 문서에 실린 Markdown Summary 중 표를 담은 20건이 한 줄로 눌려 있었다. 그중 2건(94449_1, 95003_1)은 고정 폭 강제 개행까지 겹쳐 ** 표기 한복판이 잘려 있었다.

원본 시트(Drive 1wRBwx5...)의 summary 컬럼은 줄바꿈이 살아 있다. 표가 없는 샘플도 이 문서에서 줄바꿈이 멀쩡하다. 그래서 이 손상은 표 내용에 한정되고, 이 문서를 만드는 과정에서 생긴 것으로 본다.

복원 방법 글자는 건드리지 않고 줄바꿈만 되돌렸다. 표 밖 줄바꿈은 살아 있었으므로 표 부분만 다뤘다.

  1. 강제 개행을 되돌린다. 앞 줄이 홀수 개의 * 로 끝나면 토큰 중간에서 잘린 것이므로 붙인다.
  2. 행을 나눈다. 파이프 사이의 공백 하나(| |)가 행 경계다.
  3. 빈 셀이 있는 표는 2번이 모호하다(빈 칸도 | | 다). 그때는 구분행(:---:)에서 열 수를 읽어 그 폭으로 자른다. 95249_5 한 건이 여기 해당한다.
  4. 복원 결과가 열 수가 일정하고 구분행을 가진 표인지 검사했다. 20건 전량이 통과했다.

한계 이것은 규칙에 따른 복원이지 원본 대조가 아니다. 시트에서 20건을 다시 뽑아 글자까지 일치하는지 확인하는 일이 남아 있다 (TODO.md H절).

0.2 빈 heading 11건은 분할 찌꺼기다 (고치지 않는다)#

Markdown Summary 58건 중 11건이 # 한 글자짜리 빈 heading 으로 시작한다. Summary 생성기 출력이 아니라 검증 데이터셋을 만들 때 생긴 것이다.

근거는 분할 번호다. 11건의 summary_split_no 가 전부 2 이상이고 1은 하나도 없다. 이 데이터는 5,371개 입력을 --- 로 잘라 7,208 block 으로 나눈 것이고(sources.md 3.1절), 빈 heading 은 그 자르는 자리에서 남은 찌꺼기다.

해당 11건은 96487_3, 96487_2, 96914_4, 100088_2, 95723_2, 97109_3, 98616_3, 97965_4, 95838_2, 95249_5, 96018_3 이다.

고치지 않는다. 인용 원문이기 때문이다. 다만 두 가지에 영향이 있다.

  • 이 샘플로 생성기 품질을 판단하면 안 된다. 빈 heading 은 생성기 탓이 아니다.
  • 같은 데이터셋이 골든셋 90건과 파인튜닝 평가셋의 재료다(finetuning-plan.md 5절). 평가셋을 넓힐 때(J-1) 이 찌꺼기를 먼저 걷어내야 한다.

0. 이 문서를 읽는 법#

각 샘플은 네 칸이다.

칸 무엇인가 누가 만드나
input_text 사용자가 실제로 친 원문 사용자
summary 현행 Markdown Summary. 지금 검색에 쓰이는 것 Summary 생성기 (1단계)
layout_description 요약을 보고 시각 레이아웃 모양을 한국어로 묘사한 중간 산출물 시멘틱 쿼리 v2 모듈 1단계 (gpt-5.4-mini)
taxonomy 최종 semantic tag (대분류 / 중분류) 시멘틱 쿼리 v2 모듈 2단계 (claude-haiku-4-5)

Markdown Summary 가 잃는 것을 보려면 input_text 와 summary 를 나란히 읽으면 된다. 값과 단위, 계열, 관계, 순서 같은 슬롯이 # 와 * 로 눌리면서 사라진다. 그 손실을 되돌리려는 것이 Visual Summary 의 structure 다 (schema-v0.5.md 1절).


1. 실사용 입력의 semantic category 분포 (7,168 block 기준)#

위 모수를 시멘틱 쿼리 v2 로 라벨링한 전량 분포다. README.md 3절의 분포와는 세는 대상이 다르다. 3절은 '컴포넌트(색인 쪽)' 분포이고, 아래는 '사용자 입력(질의 쪽)' 분포다.

순위 중분류 절대수 비중 누적 데이터용 Page역할 구현 우선순위
1 List 2,291 31.96% 31.96% N N P0
2 ListingTable 1,081 15.08% 47.04% Y N P0
3 Steps 783 10.92% 57.96% N N P0
4 ComparisonTable 519 7.24% 65.20% Y N P0
5 HighlightedMessage 412 5.75% 70.95% N N P0
6 LongFormTextLayout 342 4.77% 75.72% N N P0
7 Timeline 306 4.27% 79.99% N N P0
8 Versus 223 3.11% 83.10% N N P0
9 BarChart 120 1.67% 84.77% Y N P0
10 HierarchicalTree 91 1.27% 86.04% Y N
11 BigNumber 86 1.20% 87.24% N N
12 ProfileCard 78 1.09% 88.33% N N
13 Opening 71 0.99% 89.32% N Y
14 TableOfContents 63 0.88% 90.20% N Y
15 Transformation 62 0.86% 91.06% N N
16 ProblemSolution 61 0.85% 91.91% N N
17 제거됨 55 0.77% 92.68% Y N
18 LineAreaChart 50 0.70% 93.38% Y N
19 Calendar 46 0.64% 94.02% Y N
20 Cycle 36 0.50% 94.52% N N
21 Ending 34 0.47% 94.99% N Y
22 Staircase 33 0.46% 95.45% N N
23 QnACard 33 0.46% 95.91% N N
24 Gauge 30 0.42% 96.33% Y N
25 FlowChart 28 0.39% 96.72% N N
26 PieChart 22 0.31% 97.03% Y N
27 GanttChart 20 0.28% 97.31% Y N
28 QuoteTestimonial 18 0.25% 97.56% N N
29 ComboChart 18 0.25% 97.81% Y N
30 SectionDivider 17 0.24% 98.05% N Y
31 Flow 17 0.24% 98.29% Y N
32 ResumeCV 16 0.22% 98.51% N N
33 DonutChart 14 0.20% 98.71% Y N
34 DialogueChat 13 0.18% 98.89% N N
35 CombinedResult 12 0.17% 99.06% N N
36 StackedBarChart 10 0.14% 99.20% Y N
37 Fishbone 10 0.14% 99.34% N N
38 Quadrant 10 0.14% 99.48% Y N
39 SWOT 8 0.11% 99.59% N N
40 VennDiagram 6 0.08% 99.67% N N
41 NestedScope 6 0.08% 99.75% N N
42 Funnel 5 0.07% 99.82% Y N
43 PictogramChart 4 0.06% 99.88% Y N
44 Pyramid 4 0.06% 99.94% N N
45 RadarChart 2 0.03% 99.97% Y N
46 Distribution 2 0.03% 100.00% Y N

질의 쪽과 색인 쪽의 분포가 다르다. 색인 쪽은 List 가 81.1% 인데(README.md 3절) 질의 쪽은 31.96% 다. 대신 질의 쪽에서는 ListingTable 15.08%, Steps 10.92%, ComparisonTable 7.24% 처럼 표와 절차, 비교 수요가 두텁다.

사용자가 원하는 구조는 다양한데 대치할 컴포넌트 풀이 리스트에 쏠려 있다. TODO.md D-2 "List 편중을 그대로 두고 2단계를 낼 것인가"가 왜 첫 번째 결정 사항인지가 여기서 수치로 확인된다.

데이터용 여부(Y)는 표/차트 카탈로그로 라우팅되는 라벨이다. 46종 중 18종이 데이터용이고 절대수 합은 약 27% 다.


2. 카테고리별 실사용 샘플#

위 1,051 block 중 카테고리별로 뽑은 대표 예시다. 총 38개 중분류가 실제로 관측됐다.

2.1 List(리스트)#

대분류 Enumeration(나열) · 관측 353 block

사례 1: 94497_2#

사용자 입력 (원문)

text
(7/1 기준) 참여아동 모집·선정 완료(총 12명 운영) / 유관기관 4개소 MOU 체결 / 보호자 OT 완료 및 동의서 등 서류 수집 완료/ BASA-CT는 일부 아동 수행 어려움 → 유사 검사 병행, 가능 아동부터 순차 실시(7월 내 완료 목표) / 집단음악·사회성 프로그램 각 4회기 진행(자기표현·상호작용 증가 등 초기 긍정 변화 관찰)  (향후)  7월: BASA-CT 및 ISP 점검 완료 후 맞춤 학습 지속 / 미체결 유관기관 1개소 추가 MOU / 보호자·아동 마음이해 미술상담 각 1회 실시 후 결과를 강사·담당자 공유(프로그램 연계 강화) / 하반기 민-학 간담회로 성과 공유 및 협력체계 공고화

Markdown Summary (원문)

markdown
# (7월) 향후 계획
* BASA-CT 및 ISP 점검 완료 후 맞춤 학습 지속 
* 미체결 유관기관 1개소 추가 MOU 
* 보호자·아동 마음이해 미술상담 각 1회 실시 후 결과를 강사·담당자 공유(프로그램 연계 강화) 
* 하반기 민-학 간담회로 성과 공유 및 협력체계 공고화

Layout Description (모듈 출력 원문)

text
현재 진행 현황과 7월 향후 계획을 위아래로 나눠 배치한 2단 나열형 슬라이드로, 상단에는 참여아동 모집·선정, MOU 체결, 보호자 OT, 프로그램 진행 같은 완료 항목을 묶고 하단에는 BASA-CT 및 ISP 점검, 추가 MOU, 미술상담, 간담회 계획을 이어 붙인 구성형 슬라이드

분류 일반 카탈로그 → Enumeration(나열) / List(리스트)

판단 근거 (모듈 출력 원문)

text
summary는 7월 향후 계획의 4개 실행 과제를 동등한 수준의 항목으로 나열하고 있으며, layout_description이 "2단 나열형 슬라이드"로 상단(완료 항목)과 하단(계획 항목)을 분리하여 구성했다고 명시했으므로, 시간순 절차나 인과 논리가 아닌 동등 항목의 병렬 나열로 판정된다.
사례 2: 96487_3#

사용자 입력 (원문)

text
함께하는 마음  안녕하십니까.  바쁜 현장 업무 중에도 우리 병원의 더 나은 변화를 위해 이 자리에 참여해 주셔서 진심으로 감사드립니다.  오늘 진행하는 프로그램은 ‘환자경험 공감의 날’ 『환자체험』입니다.  환자의 입장에서 병원 이용 과정을 직접 체험해보고 우리 스스로가 환자중심 실천의지를 다짐하는 자리가 되기를 바래봅니다.   환자가 병원을 이용하면서 겪는 모든 경험, 환자가 병원에서 집으로 돌아온 후 가족에게 하는 얘기들이 모두 환자경험평가에 반영이 된다고 볼 수 있습니다.   우리가 환자의 눈을 한 번 더 맞추고, 걱정 섞인 질문에 한 마디 더 친절히 답해드리는 것, 그리고 환자의 발걸음이 머무는 공간을 조금 더 편안하게 살피는 것. 이런 작은 배려들이 모여 우리 병원의 강점이 될 수 있습니다.  오늘 이 자리에 계신 여러분은 그 과정을 현장에서 직접 이끌어갈 가장 중요한 주인공들입니다.  여러분의 작은 실천이 세종충남대학교병원의 환자경험을 바꾸는 가장 큰 힘입니다. 오늘 함께해 주셔서 다시 한번 진심으로 감사드립니다.

Markdown Summary (원문)

markdown
#

# 환자경험을 바꾸는 작은 실천
* 환자의 눈을 한 번 더 맞추기 
* 걱정 섞인 질문에 한 마디 더 친절히 답하기 
* 환자의 발걸음이 머무는 공간을 조금 더 편안하게 살피기

Layout Description (모듈 출력 원문)

text
제목과 환자체험 프로그램명을 전면에 두고, 환자경험을 바꾸는 작은 실천을 중심 문구로 세운 뒤 그 아래에 3개 실천 항목을 간단히 덧붙인 핵심 메시지 슬라이드입니다.

분류 일반 카탈로그 → Enumeration(나열) / List(리스트)

판단 근거 (모듈 출력 원문)

text
layout_description이 "3개 실천 항목을 간단히 덧붙인" 복수의 항목 블록을 명시하고 있으므로, 제목이 핵심 메시지여도 분리된 항목이 3개이면 List를 선택합니다. 동등한 수준의 실천 항목 3개를 병렬 나열하는 구조입니다.

2.2 Listing Table(목록형 표)#

대분류 Enumeration(나열) · 관측 138 block

사례 1: 94449_1#

사용자 입력 (원문)

text
2030년 국가 핵심 목표 (단기 로드맵) 총 재생에너지 누적 설비 용량: 100GW 내외 확충 (태양광 + 풍력 등) 태양광 발전 목표 비율: 전체 설비의 60% 이상으로 유도 (약 60GW+ 수준 담당) 재생에너지 발전 비중: 2030년 20%, 2035년 30% 이상 달성

Markdown Summary (원문)

markdown
# 2030년 국가 핵심 목표 (단기 로드맵)

| 구분 | 목표 수치 |
| :--- | :--- |
| **총 재생에너지 누적 설비 용량** | 100GW 내외 확충 |
| **태양광 발전 목표 비율** | 전체 설비의 60% 이상 유도 (약 60GW+ 수준) |
| **재생에너지 발전 비중** | 2030년 20%, 2035년 30% 이상 달성 |

Layout Description (모듈 출력 원문)

text
2030년 국가 핵심 목표와 단기 로드맵을 수치 중심으로 전면에 내세운 빅넘버형 슬라이드로, 100GW 내외의 총 설비 용량과 60% 이상 태양광 비율, 2030년 20%와 2035년 30% 이상 발전 비중을 한눈에 비교되게 정리한 수치 강조형 구성

분류 표차트 카탈로그 → Enumeration(나열) / Listing Table(목록형 표)

판단 근거 (모듈 출력 원문)

text
summary는 국가 핵심 목표를 구분별로 나열한 정보 구조이며, 목표 수치들이 항목별 부가 정보로 정리되어 있다. layout_description에서 "수치 중심 빅넘버형"이라 표현했으나, 기준선·달성률·진행률 같은 게이지 기준이 명시되지 않았고, 항목별 목표값을 단순 나열하는 구조이므로 Listing Table이 가장 적합하다.
사례 2: 100316_2#

사용자 입력 (원문)

text
ABGA PH 7.492  7.35~7.45 PaCO2  31.2  35~45 PaO2  75.1  80~100 Hemoglbin  7.5  12~18 Osmolality  301  285~294 Prothrombin time PT(Sec) 18.7  10.0~13.5 (%)  41.3  75~130 (INR)  1.66  0.8~1.2 CBC(5종)\&Diff WBC  7.3  3.6~9.6 RBC  2.14  4~5.52 HGB  7.4  12.9~16.9 HCT  21.7  37.9~49.1 MCV  101.5  83.7~98.5 MCH  34.8  27.8~33 Seg.Neutro  81.1  37~72 Lympho  11.1  21~51 IRF  0.61  0.11~0.39 HLR  0.97  0.18~0.79 ESR  31  0~10

Markdown Summary (원문)

markdown
# Prothrombin Time (PT)

| 항목 | 값 | 정상 범위 |
|---|---:|---|
| PT (Sec) | 18.7 | 10.0~13.5 |
| PT (%) | 41.3 | 75~130 |
| INR | 1.66 | 0.8~1.2 |

Layout Description (모듈 출력 원문)

text
ABGA, PT, CBC 결과와 정상 범위를 항목별로 나란히 정리한 목록형 표 레이아웃이다. 3종의 PT 지표와 여러 혈액검사 수치가 한 화면에 촘촘히 배열된 검사 수치 정리형 구성

분류 표차트 카탈로그 → Enumeration(나열) / Listing Table(목록형 표)

판단 근거 (모듈 출력 원문)

text
혈액검사 결과(PT, INR 등)를 항목별로 측정값과 정상범위를 나열한 구조로, 항목 간 우열·선택 판단이 아니라 검사 수치를 정리·기록하는 것이 핵심이므로 Listing Table이 적합합니다.

2.3 Steps(단계)#

대분류 Process Flow(절차/단계) · 관측 125 block

사례 1: 100046_2#

사용자 입력 (원문)

text
1. 물량 산출 및 내역서 작성 (설계 내역 산출) -도면 분석: 구조 도면을 검토하여 마이크로  파일의 필요 개수, 설계 길이, 위치를 파악합니다. -내역서작성: 항목별(준비공, 천공, 자재비, 그라우팅, 인건비, 장비비 등)로 물량을 산출하여 '내역서'를 작성합니다. -발주 공고: 작성된 내역서를 바탕으로 시공 업체들에게 입찰 참여를 요청(견적 의뢰)합니다. 2. 입찰 참여 및 견적 제출 -견적 제출: 참여 업체들은 현장 상황과 본인들의 기술력, 장비 가동 효율을 고려하여 업체별 단가(낙찰가)를 기입한 견적서를 작성합니다. -기한 준수: 정해진 마감 기한 내에 견적서와 함께 시공 계획서, 장비 사양서 등을 제출합니다. 3. 입찰 심사 및 낙찰자 선정 -비교 분석: 발주처는 업체들이 제출한 견적 금액과 기술력을 비교합니다. -금액(단가): 예산 범위 내에서의 최적가 확인. 기술/품질: 공법에 대한 이해도, 장비 성능, 과거 유사 현장 실적 검토. -낙찰자 결정: 일반적으로 기술적 요구사항을 충족한 업체 중 최저가 또는 적정가격을 제시한 업체(1순위)를 최종 시공사로 선정합니다. 4. 계약 체결 및 공사 착수 -최종 조율: 계약 전 기술적인 상세 사항이나 공기(공사 기간)를 최종 조율합니다. -계약: 계약 체결 후 본격적인 공사 준비 단계로 진입합니다.

Markdown Summary (원문)

markdown
# 입찰 참여 및 견적 제출

## 1. 견적 제출
* 참여 업체 현장 상황 고려 
* 기술력 및 장비 가동 효율 고려 
* 업체별 단가(낙찰가) 기입 
* 견적서 작성

## 2. 기한 준수
* 정해진 마감 기한 내 제출 
* 시공 계획서 제출 
* 장비 사양서 제출

Layout Description (모듈 출력 원문)

text
견적 제출과 기한 준수의 두 단계를 번호형 카드로 나란히 배치하고, 참여 업체가 현장 상황과 기술력, 장비 가동 효율을 고려해 업체별 단가(낙찰가)를 적은 견적서와 함께 시공 계획서, 장비 사양서를 내는 흐름을 순차 배치한 프로세스형 슬라이드.

분류 일반 카탈로그 → Process Flow(절차/단계) / Steps(단계)

판단 근거 (모듈 출력 원문)

text
본문의 핵심이 입찰 참여 절차를 "1. 견적 제출 → 2. 기한 준수"로 순차 진행하는 단계별 프로세스이며, layout_description에서 "순차 배치한 프로세스형"으로 명시되어 있어 Process Flow 대분류의 Steps를 선택합니다.
사례 2: 100046_4#

사용자 입력 (원문)

text
1. 물량 산출 및 내역서 작성 (설계 내역 산출) -도면 분석: 구조 도면을 검토하여 마이크로  파일의 필요 개수, 설계 길이, 위치를 파악합니다. -내역서작성: 항목별(준비공, 천공, 자재비, 그라우팅, 인건비, 장비비 등)로 물량을 산출하여 '내역서'를 작성합니다. -발주 공고: 작성된 내역서를 바탕으로 시공 업체들에게 입찰 참여를 요청(견적 의뢰)합니다. 2. 입찰 참여 및 견적 제출 -견적 제출: 참여 업체들은 현장 상황과 본인들의 기술력, 장비 가동 효율을 고려하여 업체별 단가(낙찰가)를 기입한 견적서를 작성합니다. -기한 준수: 정해진 마감 기한 내에 견적서와 함께 시공 계획서, 장비 사양서 등을 제출합니다. 3. 입찰 심사 및 낙찰자 선정 -비교 분석: 발주처는 업체들이 제출한 견적 금액과 기술력을 비교합니다. -금액(단가): 예산 범위 내에서의 최적가 확인. 기술/품질: 공법에 대한 이해도, 장비 성능, 과거 유사 현장 실적 검토. -낙찰자 결정: 일반적으로 기술적 요구사항을 충족한 업체 중 최저가 또는 적정가격을 제시한 업체(1순위)를 최종 시공사로 선정합니다. 4. 계약 체결 및 공사 착수 -최종 조율: 계약 전 기술적인 상세 사항이나 공기(공사 기간)를 최종 조율합니다. -계약: 계약 체결 후 본격적인 공사 준비 단계로 진입합니다.

Markdown Summary (원문)

markdown
# 계약 체결 및 공사 착수

## 1. 최종 조율
* 계약 전 기술적 상세 사항 조율 
* 공기(공사 기간) 조율

## 2. 계약
* 계약 체결 
* 본격적인 공사 준비 단계 진입

Layout Description (모듈 출력 원문)

text
4단계로 이어지는 입찰 절차를 순차 배치한 프로세스형 슬라이드로, 물량 산출과 내역서 작성에서 시작해 견적 제출, 입찰 심사와 낙찰자 선정, 계약 체결과 공사 착수까지가 흐름으로 연결된 구성이다. 각 단계 안에는 세부 항목이 붙어 있어 단계형 타임라인에 가까운 레이아웃

분류 일반 카탈로그 → Process Flow(절차/단계) / Steps(단계)

판단 근거 (모듈 출력 원문)

text
입찰 절차의 4단계가 순차적으로 진행되는 프로세스 흐름이 핵심이며, 각 단계 내 세부 항목들이 순서대로 실행되어야 의미가 성립하는 구조이다. 시간 축보다는 절차와 순서가 중요한 단계형 구성이므로 Steps로 분류한다.

2.4 Highlighted Message(하이라이트 메시지)#

대분류 Text-based Communication(텍스트 기반 전달) · 관측 71 block

사례 1: 100088_1#

사용자 입력 (원문)

text
通いサービスの利用定員が18名、日中の勤務時間帯を6:00~21:00までの15時間。常勤の職員の勤務時間を8時間とした場合、常勤換算方法で通いの利用者3人に対して1名の介護従事者を配置すればよいことから、通いの利用者が18名の場合、日中の常勤の介護従事者は6名となり、日中の15時間の間に、 8時間(常勤の勤務時間分)×6人分=48時間分のサービスが提供されていることが必要になります。 それに加えて、日中については、常勤換算法で1名以上に訪問サービスの提供を行わせ、夜間については、夜勤1名+宿直1名に宿泊サービス及び夜間の訪問サービスに当たらせる従業者が必要となります。 具体的には、通いサービスに要する時間(延べ48時間)と 日中の訪問サービスに要する時間(8時間)合計56時間 日中の勤務時間帯の6;00~21:00の15時間の間に56時間分のサービス提供時間が必要ということです。

Markdown Summary (원문)

markdown
# 通いサービスの人員配置計算

## 利用定員と勤務時間
* 通いサービスの利用定員:18名 
* 日中の勤務時間帯:6:00~21:00(15時間)

## 常勤職員の勤務時間
* 常勤職員の勤務時間:8時間

Layout Description (모듈 출력 원문)

text
18名の通いサービス利用定員と8時間勤務の常勤職員を前提に、日中15時間の必要サービス時間を48時間分の通い対応に加えて日中訪問の8時間を合算して56時間と示す、数値を大きく見せた計算説明型スライド。

분류 일반 카탈로그 → Text-based Communication(텍스트 기반 전달) / Highlighted Message(하이라이트 메시지)

판단 근거 (모듈 출력 원문)

text
본문이 통いサービスの人員配置計算이라는 단일 주제를 정의하고, layout_description이 "数値を大きく見せた計算説明型"으로 산출식(48시간 + 8시간 = 56시간)을 강조하는 형태입니다. 실제 달성·측정된 결과 수치가 아니라 필요 시간 산출 공식을 시각적으로 크게 표현하는 것이므로 Big Number가 아니라 Highlighted Message입니다.
사례 2: 96487_2#

사용자 입력 (원문)

text
함께하는 마음  안녕하십니까.  바쁜 현장 업무 중에도 우리 병원의 더 나은 변화를 위해 이 자리에 참여해 주셔서 진심으로 감사드립니다.  오늘 진행하는 프로그램은 ‘환자경험 공감의 날’ 『환자체험』입니다.  환자의 입장에서 병원 이용 과정을 직접 체험해보고 우리 스스로가 환자중심 실천의지를 다짐하는 자리가 되기를 바래봅니다.   환자가 병원을 이용하면서 겪는 모든 경험, 환자가 병원에서 집으로 돌아온 후 가족에게 하는 얘기들이 모두 환자경험평가에 반영이 된다고 볼 수 있습니다.   우리가 환자의 눈을 한 번 더 맞추고, 걱정 섞인 질문에 한 마디 더 친절히 답해드리는 것, 그리고 환자의 발걸음이 머무는 공간을 조금 더 편안하게 살피는 것. 이런 작은 배려들이 모여 우리 병원의 강점이 될 수 있습니다.  오늘 이 자리에 계신 여러분은 그 과정을 현장에서 직접 이끌어갈 가장 중요한 주인공들입니다.  여러분의 작은 실천이 세종충남대학교병원의 환자경험을 바꾸는 가장 큰 힘입니다. 오늘 함께해 주셔서 다시 한번 진심으로 감사드립니다.

Markdown Summary (원문)

markdown
#

# 환자경험의 의미
* 환자가 병원을 이용하며 겪는 모든 경험 
* 병원에서 집으로 돌아간 후 가족에게 하는 이야기까지 포함

Layout Description (모듈 출력 원문)

text
제목과 행사명, 환자경험의 의미를 담은 긴 안내 문장을 전면에 두고, 아래에 환자 경험의 범위를 설명하는 짧은 보조 문구를 함께 배치한 핵심 메시지 슬라이드입니다.

분류 일반 카탈로그 → Text-based Communication(텍스트 기반 전달) / Highlighted Message(하이라이트 메시지)

판단 근거 (모듈 출력 원문)

text
summary의 핵심이 "환자경험이란 무엇인가"에 대한 정의·개념 설명이며, layout_description이 "핵심 메시지 슬라이드"로 제목과 안내 문장을 전면에 두고 아래 보조 문구만 함께 배치한 구조로, 시각적으로 분리된 독립 항목 블록이 없으므로 Highlighted Message를 선택합니다.

2.5 Comparison Table(비교표)#

대분류 Attribute Comparison(속성/기준 비교) · 관측 63 block

사례 1: 95003_1#

사용자 입력 (원문)

text
로드리게스 VS 김태형 ERA                        4.52            4.86 FIP                         4.52            6.39 WHIP                      1.35            1.51 피안타율                 0.252           0.280 K/9                         10.2            4.9 BB/9                         3.2            3.7 최근 선발3경기 ERA     2.37          2.00

Markdown Summary (원문)

markdown
# 로드리게스 VS 김태형

| 지표 | 로드리게스 | 김태형 |
| :--- | :---: | :---: |
| **ERA** | 4.52 | 4.86 |
| **FIP** | 4.52 | 6.39 |
| **WHIP** | 1.35 | 1.51 |
| **피안타율** | 0.252 | 0.280 |
| **K/9** | 10.2 | 4.9 |
| **BB/9** | 3.2 | 3.7 |
| **최근 선발 3경기 ERA** | 2.37 | 2.00 |

Layout Description (모듈 출력 원문)

text
두 투수의 ERA, FIP, WHIP, 피안타율, K/9, BB/9, 최근 선발 3경기 ERA를 행과 열로 나란히 비교한 좌우 대비형 비교표 슬라이드로, 각 지표의 차이를 한눈에 읽도록 정리한 다행 비교표

분류 표차트 카탈로그 → Attribute Comparison(속성/기준 비교) / Comparison Table(비교표)

판단 근거 (모듈 출력 원문)

text
두 투수(로드리게스 vs 김태형)를 동일한 7개 투수 성능 지표(ERA, FIP, WHIP 등)로 나란히 놓고 각 지표별 차이와 우열을 판단하도록 구성한 비교표입니다. 여러 기준을 한 번에 비교해 대상 간 강약점을 보게 하는 것이 핵심이므로 Comparison Table입니다.
사례 2: 100368_1#

사용자 입력 (원문)

text
사례 두가지를 비교하고 밑에 힌트를 줄건데 전 피피티랑 비슷하게 만들어줘

Markdown Summary (원문)

markdown
# 사례 비교

| 항목 | 사례 1 | 사례 2 |
|---|---|---|
| 특징 | - 공통점: 핵심 가치, 접근 방식<br>- 차별점: 독특한 요소, 강점 | - 공통점: 핵심 가치, 접근 방식<br>- 차별점: 독특한 요소, 강점 |
| 결과 | - 성과: 수치, 영향력<br>- 교훈: 배운 점, 시사점 | - 성과: 수치, 영향력<br>- 교훈: 배운 점, 시사점 |

Layout Description (모듈 출력 원문)

text
사례 1과 사례 2를 항목별로 나란히 놓고 특징과 결과를 같은 기준으로 맞춰 비교하는 비교표 슬라이드로, 아래에 힌트를 덧붙여 앞선 피피티와 비슷한 구성으로 정리한 2열 비교형 레이아웃

분류 표차트 카탈로그 → Attribute Comparison(속성/기준 비교) / Comparison Table(비교표)

판단 근거 (모듈 출력 원문)

text
두 사례를 공통 기준(특징, 결과)으로 나란히 놓고 공통점·차별점·성과·교훈을 같은 축에서 비교하는 것이 핵심이며, 레이아웃도 2열 비교형으로 차이와 우열을 판단하게 하는 구조이기 때문입니다.

2.6 Long-form Text Layout(장문 설명 레이아웃)#

대분류 Text-based Communication(텍스트 기반 전달) · 관측 43 block

사례 1: 99555_2#

사용자 입력 (원문)

text
1.우주항공 연구개발 및 우주산업 육성법  우리나라는 발사체와 위성 개발 기술은 발전했지만 재사용 발사체, 민간 발사장, 심우주 탐사 등 핵심 기술은 아직 선진국과의 격차가 존재한다. 우주산업에 대한 민간기업의 참여가 확대되고 있지만 관련 투자와 제도적 지원은 아직 충분하지 않다. 우주개발 사업이 정부 중심으로 이루어져 민간 우주산업 생태계 성장에 한계가 있다.  2. 생명윤리 및 안전에 관한 법률 일부개정법률안  현행 「생명윤리 및 안전에 관한 법률」 제47조 제5항은 배아에 대한 유전자치료를 전면 금지하고 있다. 최근 유전자 편집 기술의 발전으로 치료가 가능한 질환이 늘어나고 있음에도 법률이 기술 발전을 충분히 반영하지 못하고 있다. 치료 목적의 연구까지 제한되어 국내 바이오 연구 경쟁력이 저하될 우려가 있다.  3. 국가 전력안정 및 AI 기반 발전소 안전관리 특별법  발전소의 AI 예지정비는 일부 발전소에서만 자율적으로 도입되고 있어 전국적으로 통일된 관리  기준이 없다. 우리나라는 발전시설은 지방에, 전력 소비는 수도권에 집중되어 있어 송전망 부족 문제가 지속되고 있다. 기존 전력망 건설은 복잡한 인허가 절차와 지역 갈등으로 인해 사업이 장기간 지연되는 사례가 많 다.

Markdown Summary (원문)

markdown
# 2. 생명윤리 및 안전에 관한 법률 일부개정법률안
* 현행 법률은 배아 유전자치료를 전면 금지하고 있음 
* 유전자 편집 기술 발전으로 치료 가능한 질환이 늘어나고 있으나 법률이 기술 발전을 충분히 반영하지 못함 
* 치료 목적 연구까지 제한되어 국내 바이오 연구 경쟁력 저하 우려

Layout Description (모듈 출력 원문)

text
세 개의 법안 제목을 상단에 두고 각 법안마다 핵심 쟁점과 제도 한계를 짧은 설명문으로 이어 붙인 서술형 슬라이드이며, 특히 두 번째 법안은 세 개의 근거 문장을 줄글 흐름으로 풀어 적은 장문 설명형 구성을 띤다.

분류 일반 카탈로그 → Text-based Communication(텍스트 기반 전달) / Long-form Text Layout(장문 설명 레이아웃)

판단 근거 (모듈 출력 원문)

text
summary의 핵심이 하나의 법안 주제를 여러 문장(현행 법률 상황 → 기술 발전 → 연구 제한 문제)으로 풀어 설명하는 장문 본문이며, layout_description에서 "세 개의 근거 문장을 줄글 흐름으로 풀어 적은 장문 설명형 구성"이라고 명시했으므로 독립 항목 병렬 나열이 아닌 연속 서술 구조로 판정.
사례 2: 96914_4#

사용자 입력 (원문)

text
"현재 우리나라 농수산업은 심각한 인력난을 겪고 있습니다. 농촌의 고령화와 청년층의 농촌 기피 현상으로 인해 국내 인력만으로는 농번기와 어업 현장의 인력 수요를 감당하기 어려운 상황입니다. 이러한 문제를 해결하기 위해 정부는 외국인 계절근로자와 고용허가제 외국인 근로자를 지속적으로 확대하고 있습니다. 실제로 경기도의 2026년 외국인정책 시행계획에서도 외국인 계절근로자 확대, 장기체류 지원, 외국인 정책 추진 등 241개의 관련 사업을 운영하며 외국인 인력 활용을 적극 지원하고 있습니다.   하지만 현장의 채용 방식은 아직도 대부분 인력사무소나 지인 소개, 오픈채팅 등에 의존하고 있습니다. 이로 인해 농가는 원하는 인력을 찾기 어렵고, 외국인 근로자 역시 자신에게 맞는 일자리를 찾는 데 많은 시간과 비용을 소비하고 있습니다. 즉, 외국인 인력은 늘어나고 있지만 이를 효율적으로 연결하고 관리할 수 있는 디지털 플랫폼은 아직 부족합니다. 저희는 바로 이 문제를 해결하고자 합니다."

Markdown Summary (원문)

markdown
#

# 해결 방향
* 외국인 인력은 늘어나고 있으나, 이를 효율적으로 연결하고 관리할 수 있는 디지털 플랫폼이 부족함 
* 현장의 비효율적인 채용 방식을 개선하고, 농가와 외국인 근로자를 효율적으로 매칭할 수 있는 디지털 솔루션이 필요함

Layout Description (모듈 출력 원문)

text
문제 인식과 해결 방향을 길게 설명하는 서술형 슬라이드로, 외국인 인력 확대와 현장 채용의 비효율, 디지털 플랫폼의 필요성을 하나의 흐름으로 풀어낸 구성으로 읽힌다.

분류 일반 카탈로그 → Text-based Communication(텍스트 기반 전달) / Long-form Text Layout(장문 설명 레이아웃)

판단 근거 (모듈 출력 원문)

text
summary가 외국인 인력 확대의 현황, 현장의 비효율성, 필요한 솔루션을 하나의 논리적 흐름으로 풀어 설명하는 장문 구성이며, layout_description도 "서술형 슬라이드로 하나의 흐름으로 풀어낸 구성"이라고 명시했으므로 장문 설명 레이아웃으로 분류합니다.

2.7 Timeline(타임라인)#

대분류 Timeline / Schedule(시간/일정) · 관측 39 block

사례 1: 100119_1#

사용자 입력 (원문)

text
(7/24. 금)  4:30- 5:00 등록 5:00-5:30 오프닝 5:30-6:30 레크레이션 6:30-7:30 저녁 식사 7:30-9:00 기도회 9:00-10:00 야식 및 교제 10:00~ 취침

Markdown Summary (원문)

markdown
# 7/24 (금) 일정표

| 시간 | 활동 |
| :--- | :--- |
| 16:30 - 17:00 | 등록 |
| 17:00 - 17:30 | 오프닝 |
| 17:30 - 18:30 | 레크레이션 |
| 18:30 - 19:30 | 저녁 식사 |
| 19:30 - 21:00 | 기도회 |
| 21:00 - 22:00 | 야식 및 교제 |
| 22:00 - | 취침 |

Layout Description (모듈 출력 원문)

text
시간대별로 등록, 오프닝, 레크레이션, 저녁 식사, 기도회, 야식 및 교제, 취침을 한 줄씩 순서대로 담은 일정 그리드형 슬라이드로, 날짜와 활동이 표 형태의 행으로 정리된 구성입니다.

분류 표차트 카탈로그 → Timeline / Schedule(시간/일정) / Timeline(타임라인)

판단 근거 (모듈 출력 원문)

text
시간대별 사건(등록, 오프닝, 레크레이션 등)을 시간 순서로 배열한 일정 표로, 시간 축을 기준으로 순차적 활동을 보여주는 구조입니다.
사례 2: 95364_1#

사용자 입력 (원문)

text
*사업 개요  과제명 AI 반도체 기반 AI 엣지 포터블 엑스레이 개발 및 해외 실증  사업기간 2025.04 ~ 2026.12 (2차년도)  주관: ㈜오톰 참여수행기관: 유엑스펙토리/자유로운소프트   사업목표  국산 AI 반도체 기반 AI 엣지 포터블 엑스레이 개발 O-View AI 의료영상 분석 기술 적용 동남아 의료현장 실증(PoC) 글로벌 시장 진출 기반 마련 * 추진 현황 및 주요 성과 1차년도  ✔ AI 엣지 포터블 엑스레이 시제품 제작  ✔ 국산 AI 반도체 적용  ✔ O-View AI 연동  ✔ AI 모델 학습 및 성능 고도화  ✔ 해외 현장 실증 준비 완료  2차년도  ✔ 태국·인도네시아 현장 실증(PoC)  ✔ 의료기관 사용성 평가  ✔ 의료서비스 운영 플랫폼 구축  ✔ 글로벌 사업화 추진

Markdown Summary (원문)

markdown
# AI 반도체 기반 AI 엣지 포터블 엑스레이 개발 및 해외 실증

## 사업 개요

| 구분 | 내용 |
|---|---|
| 과제명 | AI 반도체 기반 AI 엣지 포터블 엑스레이 개발 및 해외 실증 |
| 사업기간 | 2025.04 ~ 2026.12 (2차년도) |
| 주관 | ㈜오톰 |
| 참여수행기관 | 유엑스펙토리, 자유로운소프트 |

Layout Description (모듈 출력 원문)

text
사업 개요를 상단에 두고 과제명과 사업기간, 주관 및 참여수행기관을 함께 정리한 뒤, 사업목표와 1차년도·2차년도 추진 현황 및 주요 성과를 번호형 카드처럼 순차 배치한 프로세스형 슬라이드입니다. 국산 AI 반도체 기반 엣지 포터블 엑스레이 개발에서 해외 실증과 글로벌 사업화로 이어지는 흐름이 보이도록 구성된 단계형 구성

분류 표차트 카탈로그 → Timeline / Schedule(시간/일정) / Timeline(타임라인)

판단 근거 (모듈 출력 원문)

text
사업기간(2025.04~2026.12)과 1차년도·2차년도 추진 현황을 시간 순서로 단계별로 배치한 구조이며, 개발→해외 실증→글로벌 사업화로 이어지는 프로세스형 흐름을 보여주는 타임라인 형태입니다.

2.8 Versus(버서스)#

대분류 Attribute Comparison(속성/기준 비교) · 관측 31 block

사례 1: 94474_1#

사용자 입력 (원문)

text
1. 실무자 자체 제작 홍보물 현황 및 규정: 현재 매뉴얼상 CI 위치 규정만 존재 (좌측 상단, 중앙, 좌측 하단, 하단 중앙 배치로 제한)  문제점: 비주얼 레이아웃상 우측에 배치하지 못할 합리적인 이유가 없음에도, 경직된 규정으로 인해 오히려 실무 디자인 제작 시 불필요한 제약으로 작용함 ➡️ [개정안] 해당 위치 제한 규정 삭제 원함  2. 외주 업체 제작 공식 홍보물 현황 및 규정: CI 위치 규정 및 K-템플릿 시스템 배경(오른쪽 볼록한 곡선형 형태) 적용 의무화  문제점:  단일 외주 업체의 스타일로 통일은 되었으나, 이것이 과연 우리 브랜드 고유의 프리미엄한 정체성을 담고 있는가에 대한 비주얼적 해답을 주지 못함  K-템플릿 배경이 디자인적으로 세련되지 못하며, 다양한 포맷의 지면에 적용 시 오히려 심각한 디자인적 제약으로 작용함  방향성: K-템플릿 배경 규정 전면 삭제 (또는 내부 행사용 배너 등 한정된 어플리케이션에만 제한적으로 적용하는 방안 검토)

Markdown Summary (원문)

markdown
# 1. 실무자 자체 제작 홍보물

## 현황 및 규정
* 현재 매뉴얼상 CI 위치 규정만 존재 
* 좌측 상단, 중앙, 좌측 하단, 하단 중앙 배치로 제한

## 문제점
* 비주얼 레이아웃상 우측에 배치하지 못할 합리적인 이유가 없음에도 경직된 규정이 존재 
* 실무 디자인 제작 시 불필요한 제약으로 작용

## 개정안
* 해당 위치 제한 규정 삭제 원함

Layout Description (모듈 출력 원문)

text
실무자 자체 제작 홍보물과 외주 업체 제작 공식 홍보물의 CI 위치 규정 및 K-템플릿 적용 의무를 두 축으로 나란히 대비하고, 각 현황과 문제점을 카드처럼 배치한 비교형 슬라이드. 한쪽은 위치 제한 규정 삭제를, 다른 한쪽은 K-템플릿 배경 규정 전면 삭제 또는 제한적 적용을 제안하는 좌우 대비형 구성

분류 일반 카탈로그 → Attribute Comparison(속성/기준 비교) / Versus(버서스)

판단 근거 (모듈 출력 원문)

text
summary의 핵심이 현황·문제점·개정안을 두 가지 대상(실무자 제작 vs 외주 제작)에 대해 나열하고 있으며, layout_description이 명시적으로 "좌우 대비형 구성"으로 두 축을 대조하는 형태이므로 Versus로 분류합니다.
사례 2: 95383_1#

사용자 입력 (원문)

text
- CAPEX: 반응기·furnace·압축기·분리탑·정제장치 등 설비비용 - OPEX: 원료비·에너지·촉매·유지보수·정제 비용 - 스팀크래킹: furnace fuel, cryogenic 분리 비중 큼 -바이오공정: 원료가·수분제거 부담이 경제성 좌우

Markdown Summary (원문)

markdown
# CAPEX & OPEX 구성

## CAPEX
* 반응기 
* furnace 
* 압축기 
* 분리탑 
* 정제장치 
* 설비비용

## OPEX
* 원료비 
* 에너지 
* 촉매 
* 유지보수 
* 정제 비용

Layout Description (모듈 출력 원문)

text
CAPEX와 OPEX를 두 축으로 나눠 각각의 구성 항목을 병렬로 정리하고, 아래에 스팀크래킹과 바이오공정의 비용 포인트를 짧게 덧붙인 2단 구성의 비교형 카드 레이아웃.

분류 일반 카탈로그 → Attribute Comparison(속성/기준 비교) / Versus(버서스)

판단 근거 (모듈 출력 원문)

text
CAPEX와 OPEX라는 두 개의 대상을 서로 대응시켜 각각의 구성 항목을 직접 대조하는 비교 구조이며, layout_description에서 "두 축으로 나눠 병렬로 정리한 비교형 카드 레이아웃"으로 명시되어 있기 때문입니다.

2.9 Bar Chart(막대 차트)#

대분류 Magnitude / Ranking(규모/순위) · 관측 22 block

사례 1: 94669_1#

사용자 입력 (원문)

text
성별 임금 격차 (29위 - 최악):임금 격차가 31.5%로 OECD 평균(13.5%)의 2배가 넘음

Markdown Summary (원문)

markdown
# 성별 임금 격차 현황

| 항목 | 수치 |
|---|---:|
| 성별 임금 격차 | 31.5% |
| OECD 평균 | 13.5% |
* 한국의 성별 임금 격차는 OECD 평균(13.5%)의 2배 이상인 31.5%로, OECD 회원국 중 29위에 해당

Layout Description (모듈 출력 원문)

text
성별 임금 격차와 OECD 평균을 두 수치로 나란히 대비해 차이를 한눈에 드러내는 비교형 막대 차트 슬라이드이며, 31.5%와 13.5%를 핵심 수치로 강조한 수치 강조형 구성

분류 표차트 카탈로그 → Magnitude / Ranking(규모/순위) / Bar Chart(막대 차트)

판단 근거 (모듈 출력 원문)

text
두 항목(한국 성별 임금 격차 vs OECD 평균)의 수치 크기를 직접 비교하는 것이 핵심이며, layout_description에서 "두 수치를 나란히 대비해 차이를 한눈에 드러내는 비교형 막대 차트"로 명시되어 있기 때문입니다.
사례 2: 96985_3#

사용자 입력 (원문)

text
- 주이용시간 오전 9시-12시: 8개 13-17시: 49개 18시-21시: 19개  - 방문목적 독서 17개 공부 12개 휴식 30개 문화생활 23개  - 연령층 0~10대: 42개 20대: 11개 30대: 4개 40대: 10개 50대: 6개 60대~: 6개  - 정원 선호 분위기 대화할 수 있는 체류형 공간: 33개 식물들을 중점적으로 볼 수 있는 관상용 공간: 13개 탁 트인 개방형 공간: 13개  - 정원에서 가장 하고 싶은 것  포토존: 4개 야외 독서: 18개 대화: 20개 산책: 35개

Markdown Summary (원문)

markdown
# 연령층

| 연령대 | 건수 |
|---|---:|
| 0~10대 | 42 |
| 20대 | 11 |
| 30대 | 4 |
| 40대 | 10 |
| 50대 | 6 |
| 60대~ | 6 |

Layout Description (모듈 출력 원문)

text
주이용시간, 방문목적, 연령층, 정원 선호 분위기, 정원에서 가장 하고 싶은 것을 각각 건수로 비교해 한 화면에 묶은 막대 차트형 인포그래픽 구성으로, 항목별 수치 차이가 바로 보이도록 정리한 다중 비교형 슬라이드

분류 표차트 카탈로그 → Magnitude / Ranking(규모/순위) / Bar Chart(막대 차트)

판단 근거 (모듈 출력 원문)

text
summary는 연령대별 건수를 나열한 항목별 수치 비교 데이터이고, layout_description에서 "항목별 수치 차이가 바로 보이도록" 막대 차트형으로 정리했다고 명시되어 있어 Magnitude/Ranking 대분류의 Bar Chart가 적합합니다.

2.10 Big Number(빅넘버)#

대분류 KPI / Key Metric(핵심 지표) · 관측 19 block

사례 1: 100088_2#

사용자 입력 (원문)

text
通いサービスの利用定員が18名、日中の勤務時間帯を6:00~21:00までの15時間。常勤の職員の勤務時間を8時間とした場合、常勤換算方法で通いの利用者3人に対して1名の介護従事者を配置すればよいことから、通いの利用者が18名の場合、日中の常勤の介護従事者は6名となり、日中の15時間の間に、 8時間(常勤の勤務時間分)×6人分=48時間分のサービスが提供されていることが必要になります。 それに加えて、日中については、常勤換算法で1名以上に訪問サービスの提供を行わせ、夜間については、夜勤1名+宿直1名に宿泊サービス及び夜間の訪問サービスに当たらせる従業者が必要となります。 具体的には、通いサービスに要する時間(延べ48時間)と 日中の訪問サービスに要する時間(8時間)合計56時間 日中の勤務時間帯の6;00~21:00の15時間の間に56時間分のサービス提供時間が必要ということです。

Markdown Summary (원문)

markdown
#

# 通いサービスの配置基準
* 利用者3人に対して1名の介護従事者を配置 
* 利用者18名の場合、日中の常勤介護従事者は6名

## サービス提供時間の計算
* 8時間(常勤の勤務時間)×6人分=48時間分のサービス提供

Layout Description (모듈 출력 원문)

text
18명 이용 정원과 15시간의 근무 시간대를 전제로, 가운데에 "8시간 × 6인 = 48시간"과 "56시간" 같은 수치를 크게 두고 아래로 통근 서비스와 방문 서비스의 필요 시간을 이어 붙인 빅넘버형 수치 강조 슬라이드입니다. 전체 계산 흐름은 짧은 계산식과 보조 문구가 나란히 배치된 단문 강조형 구성

분류 일반 카탈로그 → KPI / Key Metric(핵심 지표) / Big Number(빅넘버)

판단 근거 (모듈 출력 원문)

text
summary의 핵심이 "8시간 × 6인 = 48시간"과 "56시간" 같은 이미 산출된 핵심 수치를 강조하는 것이고, layout_description에서 "수치를 크게 두고" "빅넘버형 수치 강조 슬라이드"로 명시되어 있으므로 Big Number로 분류합니다.
사례 2: 95723_2#

사용자 입력 (원문)

text
- 세부사업명: 노인학대 신고접수 - 실적: 학대판정 89건, 학대상담 1,936회, 일반판정 205건, 일반상담 1,093회 - 성과 □ 노인학대 신고 접수 목표 초과 달성 ◦상반기 목표 조기 달성  - 2026년 5월 기준 총 294건 접수 (연간 계획 실적 432건 대비 68.1% 달성) ◦ 신고 인식 수준 향상: 다각적인 예방 및 홍보 활동을 통한 가시적 성과 창출  - 이동상담 운영 및 학대예방경찰관(APO) 간담회 개최  - 지역사회 캠페인 및 SNS 비대면 홍보 강화  - 노인인권 및 학대예방 교육 지속 실시 □ 24시간 신속·전문적 대응 체계 확립 ◦공백 없는 상담망 운영: 노인학대신고전화(1577-1389)를 통한 365일 24시간 상시 대응 체계 유지 ◦ 현장 중심의 적극적 사례 개입: 신속하고 전문적인 위기 개입을 통해 학대피해노인의 안전 확보 및 신변 보호 ◦ 종합적 회복 지원: 심리·정서적 안정 도모 및 근본적인 문제 해결을 위한 맞춤형 서비스 연계 강화

Markdown Summary (원문)

markdown
#

# 상반기 목표 초과 달성
* 2026년 5월 기준 총 294건 접수 
* 연간 계획 실적 432건 대비 68.1% 달성 
* 상반기 목표 조기 달성

Layout Description (모듈 출력 원문)

text
빅넘버형 수치 강조형 슬라이드로, 2026년 5월 기준 총 294건 접수와 연간 계획 실적 432건 대비 68.1% 달성을 크게 전면에 두고 상반기 목표 조기 달성을 함께 부각한 구성이다. 노인학대 신고접수의 실적 4개 수치와 목표 초과 달성 성과를 한 화면에서 강조하는 빅넘버형 레이아웃

분류 일반 카탈로그 → KPI / Key Metric(핵심 지표) / Big Number(빅넘버)

판단 근거 (모듈 출력 원문)

text
Summary의 핵심이 2026년 5월 기준 294건, 68.1% 달성률 같은 측정된 핵심 수치들을 강조하는 것이고, layout_description이 "빅넘버형 수치 강조형", "수치와 목표 초과 달성 성과를 한 화면에서 강조"라고 명시했으므로 Big Number로 분류한다.

2.11 Profile Card(프로필 카드)#

대분류 Portrait / Profile(인물 프로필) · 관측 19 block

사례 1: 93551_1#

사용자 입력 (원문)

text
(좌)경희대 김홍유 교수 (우)더휴베이스 박태웅 대표 2025 동아일보 소비자선호도 1위 브랜드 대상 수상-식품(조미료 소스류 부문 수상) 더휴베이스는 소비자 만족도와 제품 품질을 종합적으로 평가 받아 2025년 동아일보 소비자선호도 1위 브랜드 대상(식품-조미료, 소스류 부문)을 수상 이 수상은 소비자에게 믿고 선택할 수 있는 기준이 되어주며 가정의 식탁에 오르는 모든 한 끼가 더 건강하고 풍요로워질 수 있음을 의미

Markdown Summary (원문)

markdown
# 2025 동아일보 소비자선호도 1위 브랜드 대상 수상
* 식품(조미료·소스류) 부문 수상

## 수상 의미
* 소비자 만족도와 제품 품질을 종합적으로 평가받아 선정 
* 소비자가 믿고 선택할 수 있는 기준 제시 
* 가정의 식탁에 오르는 모든 한 끼가 더 건강하고 풍요로워질 수 있음을 의미

Layout Description (모듈 출력 원문)

text
좌우에 김홍유 교수와 박태웅 대표를 배치하고, 가운데에 2025 동아일보 소비자선호도 1위 브랜드 대상 수상 내용을 크게 두며 아래로 수상 의미를 3개 항목으로 정리한 인물 소개와 수상 설명이 결합된 카드형 나열 슬라이드.

분류 일반 카탈로그 → Portrait / Profile(인물 프로필) / Profile Card(프로필 카드)

판단 근거 (모듈 출력 원문)

text
슬라이드의 핵심이 인물 소개(김홍유 교수, 박태웅 대표)와 그들의 수상 내용을 함께 보여주는 것이므로 [Portrait / Profile / Profile Card]로 분류. layout_description에서 "좌우에 인물 배치"와 "카드형 나열"이 명시되었으나, 콘텐츠 의미상 인물 프로필 소개가 핵심이므로 인물 타입 신호가 우선된다.
사례 2: 93551_1#

사용자 입력 (원문)

text
(좌)경희대 김홍유 교수 (우)더휴베이스 박태웅 대표 2025 동아일보 소비자선호도 1위 브랜드 대상 수상-식품(조미료 소스류 부문 수상) 더휴베이스는 소비자 만족도와 제품 품질을 종합적으로 평가 받아 2025년 동아일보 소비자선호도 1위 브랜드 대상(식품-조미료, 소스류 부문)을 수상 이 수상은 소비자에게 믿고 선택할 수 있는 기준이 되어주며 가정의 식탁에 오르는 모든 한 끼가 더 건강하고 풍요로워질 수 있음을 의미

Markdown Summary (원문)

markdown
# 2025 동아일보 소비자선호도 1위 브랜드 대상 수상
* 식품(조미료·소스류) 부문 수상

## 수상 의미
* 소비자 만족도와 제품 품질을 종합적으로 평가받아 선정 
* 소비자가 믿고 선택할 수 있는 기준 제시 
* 가정의 식탁에 오르는 모든 한 끼가 더 건강하고 풍요로워질 수 있음을 의미

Layout Description (모듈 출력 원문)

text
수상 제목을 중심에 두고 좌우로 경희대 김홍유 교수와 더휴베이스 박태웅 대표를 배치한 뒤, 아래에 식품 조미료 소스류 부문 수상과 수상 의미를 보조 설명으로 덧붙인 인물 소개 결합형 수상 기념 슬라이드.

분류 일반 카탈로그 → Portrait / Profile(인물 프로필) / Profile Card(프로필 카드)

판단 근거 (모듈 출력 원문)

text
슬라이드의 핵심이 수상 기념 인물 소개(경희대 김홍유 교수, 더휴베이스 박태웅 대표)이며, layout_description에서 "좌우로 경희대 김홍유 교수와 더휴베이스 박태웅 대표를 배치"한 인물 소개 결합형으로 명시되어 있다. 수상 의미는 보조 설명일 뿐 핵심 메시지는 인물 프로필 소개이다.

2.12 Table of Contents(목차)#

대분류 Page Navigation(페이지 안내) · 관측 15 block

사례 1: 97927_2#

사용자 입력 (원문)

text
01 프로젝트 한눈에 보기 02 2단계 파이프라인 필요성 03 데이터 구성 & 라벨링 기준 04 M1 정보성 분류 모델 05 M1 검증 결과 06 M2 위험도 분류 모델 07 M2 검증 결과 08 End-to-End Pipeline 09 Demo 결과 10 한계점 및 개선 방향

Markdown Summary (원문)

markdown
# 03 데이터 구성 & 라벨링 기준
* 데이터 수집 및 구성 
* 라벨링 기준 정의 
* 품질 관리 방안

# 04 M1 정보성 분류 모델
* 모델 구조 및 특징 
* 학습 과정 
* 주요 성능 지표

# 05 M1 검증 결과
* 검증 데이터셋 성능 
* 실제 적용 사례 
* 모델 한계 및 보완점

Layout Description (모듈 출력 원문)

text
10개 항목으로 프로젝트 전체 흐름을 순서대로 안내하는 목차 슬라이드이며, 2단계 파이프라인 필요성부터 데이터 구성과 라벨링 기준, M1과 M2의 모델 및 검증 결과, End-to-End Pipeline, Demo 결과, 한계점 및 개선 방향까지 이어지는 번호형 아젠다 구성입니다.

분류 일반 카탈로그 → Page Navigation(페이지 안내) / Table of Contents(목차)

판단 근거 (모듈 출력 원문)

text
발표의 전체 구성과 순서를 10개 항목의 번호형 아젠다로 안내하는 목차 슬라이드입니다. 프로젝트 전체 흐름을 단계별로 보여주는 페이지 안내 역할이 핵심입니다.
사례 2: 97927_1#

사용자 입력 (원문)

text
01 프로젝트 한눈에 보기 02 2단계 파이프라인 필요성 03 데이터 구성 & 라벨링 기준 04 M1 정보성 분류 모델 05 M1 검증 결과 06 M2 위험도 분류 모델 07 M2 검증 결과 08 End-to-End Pipeline 09 Demo 결과 10 한계점 및 개선 방향

Markdown Summary (원문)

markdown
# 01 프로젝트 한눈에 보기
* 프로젝트 개요 및 핵심 목표 요약

# 02 2단계 파이프라인 필요성
* 기존 시스템의 한계 
* 2단계 파이프라인 도입 이유 
* 기대 효과

Layout Description (모듈 출력 원문)

text
프로젝트 한눈에 보기부터 End-to-End Pipeline과 Demo 결과, 한계점 및 개선 방향까지 10개 섹션을 순서대로 안내하는 목차 슬라이드입니다. 2단계 파이프라인 필요성처럼 중간 전환 성격의 항목도 함께 포함된 순차 배치형 목차 슬라이드

분류 일반 카탈로그 → Page Navigation(페이지 안내) / Table of Contents(목차)

판단 근거 (모듈 출력 원문)

text
layout_description이 "10개 섹션을 순서대로 안내하는 목차 슬라이드"이며 "순차 배치형 목차"라고 명시했으므로, 발표의 전체 구성과 섹션 순서를 안내하는 페이지 역할이 핵심입니다.

2.13 Transformation(전후 변화)#

대분류 Transformation(변화) · 관측 13 block

사례 1: 94894_1#

사용자 입력 (원문)

text
1 Discover Instagram  첫 인지 접점 형성  Before (문제) 비전공자가 Entegris를 알 기회 부족  After (변화) 학생 일상 속에서 자연스럽게 발견  남는 가치 (지속 자산) ✓ 콘텐츠 플랫폼 (SNS 채널) ✓ 학생 맞춤 소통 채널 ✓ 성과 데이터 축적 및 분석  02 Experience ESG Plogging  Clean Technology → Clean Action  Before (문제) 기술 가치(오염 제어 등)를 직접 체감하기 어려움  After (변화) Entegris의 Clean 가치를 직접 경험하고 공감  남는 가치 (지속 자산) ✓ ESG 활동 운영 모델 ✓ 행사 매뉴얼 및 가이드 ✓ 참여 경험 콘텐츠(사진·영상)  03 Learn & Sustain 보드게임 (러닝 키트)  어려운 기술 → 쉬운 경험  Before (문제) 반도체 공정 이해 장벽이 높아 흥미와 학습 지속이 어려움  After (변화) 게임을 통해 공정을 쉽고 재미있게 학습, 이해도와 관심도 향상  남는 가치 (지속 자산) ✓ STEM 교육 키트 (러닝 키트) ✓ 다음 기수 활용 가능 자료 ✓ 확장 가능한 교육 프로그램

Markdown Summary (원문)

markdown
# 1 Discover Instagram

## Before (문제)
* 비전공자가 Entegris를 알 기회 부족

## After (변화)
* 학생 일상 속에서 자연스럽게 발견

## 남는 가치 (지속 자산)
* 콘텐츠 플랫폼 (SNS 채널) 
* 학생 맞춤 소통 채널 
* 성과 데이터 축적 및 분석

Layout Description (모듈 출력 원문)

text
세 개의 단계인 1 Discover Instagram, 02 Experience ESG Plogging, 03 Learn & Sustain 보드게임 (러닝 키트)을 앞세워 각 단계마다 Before와 After, 남는 가치를 차례로 대비시키는 3단 프로세스형 비교 레이아웃이다. 각 단계는 제목 아래에 문제와 변화, 지속 자산을 항목으로 정리한 카드형 나열이 붙어 있어 순차 배치와 전후 비교가 함께 드러나는 구성이다

분류 일반 카탈로그 → Transformation(변화) / Transformation(전후 변화)

판단 근거 (모듈 출력 원문)

text
핵심이 Before(문제)→After(변화)의 전후 상태 변화이며, layout_description이 "3단 프로세스형 비교 레이아웃"으로 각 단계마다 Before와 After를 대비시키는 구조이므로 Transformation으로 분류한다.
사례 2: 98458_1#

사용자 입력 (원문)

text
다양한 산업에서의 영업, 고객 응대, 시장조사, 경영지원 경험을 바탕으로 고객과 시장을 이해하는 실무 역량을 쌓아왔습니다. 앞으로는 데이터를 기반으로 시장을 분석하고, 고객의 니즈를 정확하게 파악하여 기업의 성과를 창출하는 마케팅·영업 전문가로 성장하는 것이 목표입니다.  이를 위해 시장 분석 역량과 데이터 활용 능력을 지속적으로 강화하고, 고객 중심의 영업 전략과 마케팅 기획 역량을 체계적으로 발전시키겠습니다. 단기적으로는 현장에서 고객과 시장을 가장 가까이에서 이해하는 실무 경험을 축적하고, 장기적으로는 고객 인사이트를 바탕으로 신규 시장을 발굴하고 브랜드의 성장을 이끄는 마케팅·영업 전문가로 성장하고자 합니다.

Markdown Summary (원문)

markdown
# 실무 역량 기반 성장 목표

## 현재까지의 경험
* 영업 
* 고객 응대 
* 시장조사 
* 경영지원

## 앞으로의 목표
* 데이터를 기반으로 시장을 분석 
* 고객의 니즈를 정확하게 파악 
* 기업의 성과를 창출하는 마케팅·영업 전문가로 성장

Layout Description (모듈 출력 원문)

text
현재까지의 영업, 고객 응대, 시장조사, 경영지원 경험과 앞으로의 성장 목표를 위아래로 나란히 배치해 실무 역량에서 미래 지향 목표로 이어지는 2개 축 구성의 단문 강조형 슬라이드. 데이터 기반 시장 분석, 고객 니즈 파악, 마케팅·영업 전문가 성장 같은 핵심 문구를 중심에 두고 아래로 단기와 장기 목표를 서술형으로 덧붙인 구성

분류 일반 카탈로그 → Transformation(변화) / Transformation(전후 변화)

판단 근거 (모듈 출력 원문)

text
현재 경험(영업, 고객 응대, 시장조사, 경영지원)과 앞으로의 성장 목표(데이터 기반 분석, 고객 니즈 파악, 전문가 성장)를 위아래 2개 축으로 대비하는 AS-IS→TO-BE 구조. 개인의 현재 상태에서 미래 지향 목표로의 변화·발전을 강조하는 전후 변화 구성입니다.

2.14 Hierarchical Tree(계층 트리)#

대분류 Hierarchy(위계) · 관측 10 block

사례 1: 97685_1#

사용자 입력 (원문)

text
대분류하나 중분류 3개 중분류 3개중 2개는 소분류 3개 중분류 1개는 소분류 1개

Markdown Summary (원문)

markdown
# 대분류하나

## 중분류 1
* 소분류 1-1 
* 소분류 1-2 
* 소분류 1-3

## 중분류 2
* 소분류 2-1 
* 소분류 2-2 
* 소분류 2-3

## 중분류 3
* 소분류 3-1

Layout Description (모듈 출력 원문)

text
대분류하나를 상위로 두고 중분류 3개를 가지치기처럼 펼친 뒤, 그중 2개는 소분류 3개씩, 나머지 1개는 소분류 1개로 내려가는 트리형 위계 다이어그램 레이아웃

분류 일반 카탈로그 → Hierarchy(위계) / Hierarchical Tree(계층 트리)

판단 근거 (모듈 출력 원문)

text
Summary의 핵심이 상위 개념(대분류)에서 하위 개념(중분류, 소분류)으로 계층적으로 분해되는 구조이고, layout_description이 명시적으로 "트리형 위계 다이어그램"으로 가지치기 형태의 상하 연결 관계를 보여주므로 Hierarchical Tree로 분류합니다.
사례 2: 99115_1#

사용자 입력 (원문)

text
1. 인건비 (100단위) 110 인건비: 공무원의 봉급, 각종 수당(겸임, 정근, 성과상여금 등) 및 기간제 근로자, 공무직, 계약제 교원의 보수를 포함합니다 . 2. 물건비 (200단위) 210 운영비: 기관 운영을 위한 일반수용비(사무용품, 인쇄비, 소모품 등), 공공요금, 피복비, 급량비, 시설장비유지비 등 가장 기본적인 행정 경비입니다 . 220 여비: 국내 및 국외 출장, 외빈 초청 및 훈련을 위한 여비입니다 . 230 업무추진비: 기관 운영 및 사업 추진을 위한 경비입니다 . 240 복리후생비: 맞춤형 복지비, 교직원 복리후생, 교원연구비 등입니다 . 250 직무수행경비: 직책급 업무수행경비 및 특정업무경비 등입니다 . 260 연구개발비: 정책 연구 및 조사 등에 소요되는 경비입니다 . 280 경상교육지원사업비: 교육청 및 교육지원청의 기본 운영을 지원하는 사업비입니다 . 3. 이전지출 (300단위) 310 보전금: 포상금, 보상금, 법정부담금 등을 포함합니다 . 320 민간이전: 민간경상보조, 민간위탁금, 보험료부담금 등 민간에 지원하는 경비입니다 . 330 자치단체등이전: 자치단체경상/자본보조, 자치단체대행사업비 등 타 지자체나 공공기관에 지원하는 자금입니다 . 340 해외이전: 해외 경상 이전 및 국제부담금입니다

Markdown Summary (원문)

markdown
# 예산 분류 체계

## 1. 인건비 (100단위)
* 110 인건비
* 공무원의 봉급
* 각종 수당(겸임, 정근, 성과상여금 등)
* 기간제 근로자, 공무직, 계약제 교원의 보수

Layout Description (모듈 출력 원문)

text
인건비, 물건비, 이전지출의 3개 큰 묶음 아래에 110, 210, 220, 230, 240, 250, 260, 280, 310, 320, 330, 340 항목이 단계적으로 가지치기되는 트리(위계)형 다이어그램 레이아웃. 각 항목은 설명 문장이 붙은 항목 정리 카드처럼 펼쳐지고, 상위 분류에서 하위 세부 항목으로 내려가는 구조가 강조된 구성

분류 일반 카탈로그 → Hierarchy(위계) / Hierarchical Tree(계층 트리)

판단 근거 (모듈 출력 원문)

text
summary의 핵심이 예산 항목의 상위-하위 분류 체계이고, layout_description이 명시적으로 "3개 큰 묶음 아래에 세부 항목이 가지치기되는 트리형 다이어그램"으로 상위-하위 소속 관계를 보여주므로 Hierarchical Tree를 선택합니다.

2.15 Gauge(게이지)#

대분류 KPI / Key Metric(핵심 지표) · 관측 8 block

사례 1: 100129_1#

사용자 입력 (원문)

text
현재 39% 목표 50%로 해서 원 표 만들어죠 거기에 현재 성인 독서율 독자수 197,643명 (전체 496,205명 기준) 39%로 크게 써주고 목표 50% 약 54,583 독자 전환 필요하다고 만들어죠 어떻게 만드냐면 원모양의 차트로

Markdown Summary (원문)

markdown
# 성인 독서율 현황 및 목표

| 구분 | 비율 | 인원 |
|------|------|------|
| 현재 | 39% | 197,643명 |
| 목표 | 50% | 248,103명 |
* 약 54,583명 독자 전환 필요 (현재 대비)

Layout Description (모듈 출력 원문)

text
현재 39%와 목표 50%를 원형 차트로 비교하고, 39%인 현재 성인 독서율과 54,583명 전환 필요 수치를 크게 강조한 빅넘버형 도넛 차트 슬라이드입니다.

분류 표차트 카탈로그 → KPI / Key Metric(핵심 지표) / Gauge(게이지)

판단 근거 (모듈 출력 원문)

text
현재 39%와 목표 50%를 비교하는 구조로, 목표 대비 현재 달성 상태(39/50 = 78% 진행)를 보여주는 것이 핵심입니다. 도넛 차트 형태이지만 단순 구성비가 아니라 목표 대비 달성률과 필요 전환 인원을 강조하는 KPI 성격이므로 Gauge가 가장 적합합니다.
사례 2: 100183_2#

사용자 입력 (원문)

text
. 신체적 학대의 정의 신체적 학대란 물리적인 힘 또는 도구를 이용하여 노인에게 신체적 혹은 정신적 손상, 고통, 장애 등을 유발하는 행위를 의미합니다 . 2. 신체적 학대의 발생 비율 (2025년 기준) 발생 비율: 2025년 전체 노인 학대 유형 중 **44.2%**를 차지하며 가장 빈번하게 발생하는 것으로 나타났습니다 , . 발생 건수: 총 5,215건이 보고되었으며, 이는 2024년(4,797건) 대비 약 8.7% 증가한 수치입니다 . 요약하자면, 신체적 학대는 물리적 가해를 통해 노인에게 고통을 주는 행위이며, 2025년 통계상 대한민국 노인 학대 유형 중 가장 높은 비율을 차지하고 있습니다 , .

Markdown Summary (원문)

markdown
# 2025년 신체적 학대 발생 현황

| 구분 | 수치 |
|---|---:|
| 발생 비율 | 44.2% |
| 발생 건수 | 5,215건 |
* 2024년 4,797건 대비 약 8.7% 증가

Layout Description (모듈 출력 원문)

text
정의 문단과 2025년 발생 비율, 발생 건수 두 수치를 함께 배치해 핵심 내용을 먼저 보여주고, 하단에는 2024년 대비 증가폭을 짧게 덧붙인 수치 강조형 슬라이드

분류 표차트 카탈로그 → KPI / Key Metric(핵심 지표) / Gauge(게이지)

판단 근거 (모듈 출력 원문)

text
슬라이드의 핵심이 2025년 신체적 학대 발생 비율(44.2%)과 건수(5,215건)라는 두 개의 대표 지표를 강조하는 것이며, 2024년 대비 증가폭(8.7%)을 함께 제시해 변화 추이를 맥락으로 제공하고 있다. 수치 강조형 레이아웃으로 핵심 성과 지표를 부각하는 구조이므로 Gauge가 가장 적합하다.

2.16 Problem-Solution(문제-해결)#

대분류 Reasoning Structure(원인/해결 논리) · 관측 8 block

사례 1: 94587_1#

사용자 입력 (원문)

text
-24년 1학기 말  전공 수업중 훈련단과 스파링을 하다 큰 부상을 당함  학점 이수 구조의 한계 및 졸업 위기  매 학기 필수 전공(3학점) 및 심화 전공(2학점) 이수 필요 졸업을 하지 못할 수 있다는 불안감이 생김  실기 수업 참여 불가로 인한 '졸업 불가 불안감' 고조

Markdown Summary (원문)

markdown
# 졸업 위기 및 불안감

## 부상 및 학업 차질
* 24년 1학기 말 전공 수업 중 훈련단과 스파링을 하다 큰 부상을 당함 
* 실기 수업 참여가 어려워짐

## 학점 이수 구조의 한계
* 매 학기 필수 전공(3학점) 및 심화 전공(2학점) 이수가 필요 
* 수업을 듣지 못하면 학점 이수가 어려워짐

## 졸업 불가 불안감 고조
* 졸업을 하지 못할 수 있다는 불안감이 생김 
* 실기 수업 참여 불가로 인해 졸업 불가에 대한 불안감이 더욱 커짐

Layout Description (모듈 출력 원문)

text
부상과 학점 이수 구조의 한계를 축으로 원인에서 결과로 이어지게 정리한 3단계 인과 흐름형 슬라이드로, ‘부상 및 학업 차질’과 ‘학점 이수 구조의 한계’, ‘졸업 불가 불안감 고조’를 순차 배치해 불안이 커지는 과정을 보여주는 구성입니다.

분류 일반 카탈로그 → Reasoning Structure(원인/해결 논리) / Problem-Solution(문제-해결)

판단 근거 (모듈 출력 원문)

text
summary의 핵심이 부상(문제) → 학점 이수 불가(원인) → 졸업 불안(결과)로 이어지는 인과 논리 구조입니다. layout_description이 "3단계 인과 흐름형"으로 명시했으므로 원인-결과 연결이 핵심이며, 이는 Reasoning Structure 대분류에 해당합니다. 같은 그룹 내에서 문제의 원인과 그로 인한 결과(불안감 고조)를 순차적으로 연결한 구조이므로 Problem-Solution으로 분류합니다.
사례 2: 97109_3#

사용자 입력 (원문)

text
한페이지로 만들어야해. / Situation  3일간 진행된 학과 축제 주점 운영  제한된 예산 내 재료 구매 및 운영 방문객 규모에 따라 수익성이 크게 달라지는 행사 재고 부족과 재고 폐기를 동시에 고려해야 하는 상황 Problem 1 방문객 수요 예측의 어려움  방문객 규모를 정확히 예측하기 어려워 과잉 재고 또는 재고 부족 위험 존재  Solution 1 다양한 데이터를 활용한 수요 예측 이전 축제 운영 데이터 분석 일자별 아티스트 라인업 비교 학교 커뮤니티 반응 확인 SNS 관심도 확인 축제 기간 교통편 증편 여부 확인 마지막 날(금요일) 일정 고려  ↓  3일차 방문객 증가 예상  ↓  재고 약 20% 추가 준비 제안  Problem 2 특정 메뉴 재고 폐기 위기 발생  1일차 종료 전 판매량이 예상보다 저조  Solution 2 할인 판매 전략 제안 마감 1시간 30분 전 할인 판매 폐기보다 판매가 더 효율적이라고 판단 마진 구조를 고려한 의사결정

Markdown Summary (원문)

markdown
#

# Problem 2
* 특정 메뉴 재고 폐기 위기 발생 
* 1일차 종료 전 판매량이 예상보다 저조

## Solution 2
* 할인 판매 전략 제안 
* 마감 1시간 30분 전 할인 판매 시작 
* 폐기보다 판매가 더 효율적이라고 판단 
* 마진 구조를 고려한 의사결정

Layout Description (모듈 출력 원문)

text
문제와 해결을 좌우로 나란히 대비하는 AS-IS vs TO-BE 구성 위에, 3일간 축제 운영의 수요 예측과 재고 대응을 단계적으로 풀어낸 프로세스형 슬라이드이다. 아래에는 방문객 규모에 따른 재고 추가 준비와 특정 메뉴의 할인 판매 판단을 항목별로 정리해, 한 페이지 안에서 의사결정 흐름이 보이도록 한 비교형 카드형 구성

분류 일반 카탈로그 → Reasoning Structure(원인/해결 논리) / Problem-Solution(문제-해결)

판단 근거 (모듈 출력 원문)

text
Summary의 핵심이 특정 메뉴 재고 폐기 위기(문제)와 할인 판매 전략(해결책)을 명확히 대응시키는 구조이고, layout_description이 "문제와 해결을 좌우로 나란히 대비하는 AS-IS vs TO-BE 구성"이라고 명시했으므로 Problem-Solution으로 분류한다.

2.17 Staircase(계단형)#

대분류 Progression(발전/상승) · 관측 8 block

사례 1: 98616_3#

사용자 입력 (원문)

text
## Slide 3 — AX 성숙도 프레임워크: 미국 금융권 3~4단계, 당사 3단계 진입  > Gartner AI Maturity Model 등 주요 프레임워크에 따르면 AX(AI 전환)는 5단계로 진행되며, 미국 금융권은 3~4단계, 당사는 3단계 진입 구간에 위치  - Gartner·Deloitte·Accenture·McKinsey·KPMG 등 주요 성숙도 프레임워크는 세부 명칭은 다르지만 공통적으로 **5단계(또는 이에 준하는 4~5단계)**로 수렴 — AX는 "도구 도입 여부"가 아니라 **"단계를 거쳐 전환하는 과정"** - 경쟁의 축은 이제 '도입했는가'가 아니라 '어느 단계까지 왔는가' — 당사의 현재 위치를 프레임워크 위에서 객관적으로 짚어야 다음 논의(전환 포인트, Slide 4)가 성립  | 단계 | 1. 인식 (Awareness) | 2. 활동 (Active) | 3. 운영 (Operational) | 4. 시스템화 (Systemic) | 5. 전환 (Transformational) | |---|---|---|---|---|---| | **정의** | AI 대화는 있으나 전략·파일럿 부재 | PoC·파일럿 등장, 지식공유·표준화 시작 | 최소 1개 프로젝트 프로덕션 진입, 전담예산·임원스폰서 확보 | 신규 프로젝트에 AI가 기본 전제, 조직 전반에 내재화 | AI가 비즈니스 모델·상품·조직 DNA 자체를 재정의 | | **현재 위치** | | | **당사 — 진입** | **미국 금융권 — 선두 그룹 (JPM, Capital One 등)** | | | | | | **미국 금융권 — 업종 평균** | | |  - **미국 금융권 (3~4단계)**: JPMorgan·Capital One 등 선두 그룹은 4단계(시스템화) 진입 — 전 인력 다수가 정기 사용, 수백 개 

Markdown Summary (원문)

markdown
#

# 당사 현황
* **단계:*
* 3단계 운영 진입 
* **현황:*
*
* M365 Copilot 전사 확산 진행 중
* GenOS 기반 에이전트 개발 착수
* 도구·플랫폼은 확보했으나 전사 확산·성과 측정은 아직 확보 전 단계

Layout Description (모듈 출력 원문)

text
5단계 성숙도 프레임워크를 기준으로 1. 인식부터 5. 전환까지의 정의와 현재 위치를 한 화면에 정리하고, 가운데에는 당사 3단계 진입과 미국 금융권 3~4단계를 함께 놓아 비교가 드러나도록 짠 비교표 슬라이드이다. 아래에는 미국 금융권 선두 그룹과 업종 평균, 당사의 진입 현황과 예시를 덧붙여 단계별 격차를 설명하는 구성으로 이어진 표 형 구성

분류 일반 카탈로그 → Progression(발전/상승) / Staircase(계단형)

판단 근거 (모듈 출력 원문)

text
성숙도 프레임워크의 5단계(인식→전환)를 단계적으로 정의하고, 당사의 3단계 진입 위치를 명시하는 것이 핵심이므로 발전/상승 구조로 판정. 아래 비교표는 단계별 격차 설명의 보조 역할이므로 주요 의미 신호는 성숙도 단계 상승에 있음.
사례 2: 97029_1#

사용자 입력 (원문)

text
- 0~2세: 영상보다 사람 우선 - 3~5세: 교육적이고 짧은 콘텐츠 - 초등: 검색과 알고리즘 이해 교육 - 연령 제한과 내용 확인하기

Markdown Summary (원문)

markdown
# 연령대별 디지털 미디어 활용 가이드

## 0~2세
* 영상보다 사람 우선

## 3~5세
* 교육적이고 짧은 콘텐츠

## 초등
* 검색과 알고리즘 이해 교육 
* 연령 제한과 내용 확인하기

Layout Description (모듈 출력 원문)

text
0~2세, 3~5세, 초등의 연령대별 권장 기준을 단계처럼 순차 배치하고, 각 구간에 핵심 지침을 붙인 3개 항목 프로세스형 나열 슬라이드로 보인다. 연령이 올라갈수록 내용이 바뀌는 흐름이 드러나는 단계형 구성

분류 일반 카탈로그 → Progression(발전/상승) / Staircase(계단형)

판단 근거 (모듈 출력 원문)

text
연령대가 올라갈수록 디지털 미디어 활용 기준이 단계적으로 발전·고도화되는 구조이며, layout_description에서 "단계처럼 순차 배치"와 "단계형 구성"으로 명시되어 있어 발전/상승의 의미를 담은 계단형 구성으로 판정됨.

2.18 Opening(오프닝)#

대분류 Page Navigation(페이지 안내) · 관측 6 block

사례 1: 94621_1#

사용자 입력 (원문)

text
2026. 7. 10. (금) 오후 2시

Markdown Summary (원문)

markdown
# 2026. 7. 10. (금) 오후 2시

Layout Description (모듈 출력 원문)

text
날짜와 시간이 크게 중심이 되는 표지형 슬라이드로, 제목 없이 "2026. 7. 10. (금) 오후 2시"만 간결하게 배치한 오프닝 구성

분류 일반 카탈로그 → Page Navigation(페이지 안내) / Opening(오프닝)

판단 근거 (모듈 출력 원문)

text
발표 슬라이드의 맨 앞장으로, 날짜와 시간만 크게 강조한 표지형 구성이다. 실질 콘텐츠 정보 없이 발표 시작을 알리는 페이지 역할이 핵심이므로 Opening으로 분류한다.
사례 2: 94453_1#

사용자 입력 (원문)

text
IAB Award start 이 문장을 가운데 정렬해서 점점크게 페이드인되게 만들어 줘

Markdown Summary (원문)

markdown
# IAB Award start

Layout Description (모듈 출력 원문)

text
가운데에 "IAB Award start" 문구 하나를 두고 점점 크게 페이드인되도록 연출한 핵심 메시지 슬라이드.

분류 일반 카탈로그 → Page Navigation(페이지 안내) / Opening(오프닝)

판단 근거 (모듈 출력 원문)

text
발표의 맨 앞장을 구성하는 슬라이드로, 본문 콘텐츠 없이 "IAB Award start"라는 단일 핵심 문구를 페이드인 연출로 강조하는 페이지 역할 중심의 구성입니다.

2.19 Weighted Cards (가중 카드)#

대분류 Enumeration(나열) · 관측 6 block

사례 1: 97965_4#

사용자 입력 (원문)

text
## 디자인 가이드라인 (Design System)메인 컬러 (보훈부 블루): #0F2C59 (신뢰감과 무게감을 주는 딥 네이비 블루)서브 컬러 (포인트 블루): #1E56A0 (데이터 강조용 미디엄 블루)배경 색상: #F8F9FA (인쇄 시 깔끔하고 눈이 편안한 라이트 그레이쉬 화이트)폰트 계층화 (Font Hierarchy):메인 타이틀: KoPub바탕체 Bold 또는 프리텐다드 Bold (28pt / 서브텍스트와 확실한 대비)본문/데이터: 프리텐다드 Medium~Regular (12pt~14pt)숫자 데이터: 에스코어 드림 6 Bold 또는 Arial Black (크게 배치)## 슬라이드 레이아웃 구성안1. 상단 영역 (Header Area)좌측 상단 (태그): [국제보훈협력국] ➔ 옅은 블루 박스 안에 흰색 글씨 (10pt)메인 타이틀: 유엔참전국 현황 및 지원 사업 개요 (24pt, #0F2C59, Bold)서브 타이틀: 6·25전쟁 참전 22개국과의 연대 강화 및 보훈 외교 성과 (14pt, #555555)우측 상단: 국가보훈부 로고 들어갈 자리 확보2. 중앙 영역: 핵심 지표 (Key Metrics 3분할 카드)디자인 팁: 가로로 긴 3개의 미니멀한 화이트 카드를 배치하고, 은은한 그림자(Shadow) 효과를 주어 입체감을 살립니다. 각 카드 좌측에는 직관적인 단색(Solid) 블루 아이콘을, 우측에는 대형 숫자 데이터를 배치합니다.분류좌측 아이콘핵심 데이터 (크고 굵게)하단 설명 (Small Text)참전 규모🌍 전투·의료 지원국 아이콘총 22개국전투지원 16개국 / 의료지원 6개국참전 인원👥 군인 실루엣 아이콘1,957,733 명미·영·캐나다 등 연합군 총계보훈 외교🤝 악수/연대 아이콘100% 네트워크22개국 전원 초청 및 현지 위문 사업3. 하단 영역

Markdown Summary (원문)

markdown
#

## 중앙 영역: 핵심 지표 (Key Metrics 3분할 카드)

| 분류 | 좌측 아이콘 | 핵심 데이터 | 하단 설명 |
|------|-------------|-------------|------------|
| 참전 규모 | 🌍 전투·의료 지원국 아이콘 | 총 22개국 | 전투지원 16개국 / 의료지원 6개국 |
| 참전 인원 | 👥 군인 실루엣 아이콘 | 1,957,733 명 | 미·영·캐나다 등 연합군 총계 |
| 보훈 외교 | 🤝 악수/연대 아이콘 | 100% 네트워크 | 22개국 전원 초청 및 현지 위문 사업 |

Layout Description (모듈 출력 원문)

text
빅넘버형 핵심 지표 카드 3개를 가로로 배치하고, 아래에는 전투지원국 16개국과 의료지원국 6개국을 국기와 국가명으로 나눈 4열 4행과 3열 2행의 카드 그리드로 펼친 인포그래픽 구성입니다. 상단에는 유엔참전국 현황 및 지원 사업 개요 제목과 서브 타이틀, 우측 로고 자리를 두고 하단에는 보훈부명을 넣어 정돈한 카드형 슬라이드

분류 표차트 카탈로그 → Enumeration(나열) / Weighted Cards (가중 카드)

판단 근거 (모듈 출력 원문)

text
summary에서 3개의 핵심 지표(참전 규모, 참전 인원, 보훈 외교)를 강조하고 있으며, layout_description에서 빅넘버형 카드 3개를 가로로 배치하고 아래에 국가별 카드 그리드를 펼친 구조로 설명하고 있습니다. 동등한 정보 단위(지표)를 나열하되 시각적 강조 차이를 주는 카드 레이아웃이 핵심이므로 Weighted Cards가 적합합니다.
사례 2: 95263_1#

사용자 입력 (원문)

text
북카페등 라운지 35% 상담실 9% 동아리실 7% 소규모 휴게공간 16% DVD 보드게임룸 6% AI 면접실 12% 공연장 10% 작은주방 3% 기타 2%(체육시설, 회의슬 등) 그중 선택은 북카페등 라운지, 소규모 휴게공간, DVD 보드게임룸 , AI면접실, 작은주방이 선택되었어

Markdown Summary (원문)

markdown
# 공간별 선택 비율 현황

| 공간 구분 | 선택 비율 |
|---|---:|
| 북카페 등 라운지 | 35% |
| 상담실 | 9% |
| 동아리실 | 7% |
| 소규모 휴게공간 | 16% |
| DVD 보드게임룸 | 6% |
| AI 면접실 | 12% |
| 공연장 | 10% |
| 작은주방 | 3% |
| 기타 (체육시설, 회의실 등) | 2% |

Layout Description (모듈 출력 원문)

text
공간별 선택 비율을 항목별로 정리한 목록형 표 슬라이드로, 북카페 등 라운지 35%를 가장 크게 두고 소규모 휴게공간 16%, AI 면접실 12%, 공연장 10% 등 각 공간의 선택 비율이 한눈에 보이게 배열한 구성이다.

분류 표차트 카탈로그 → Enumeration(나열) / Weighted Cards (가중 카드)

판단 근거 (모듈 출력 원문)

text
공간별 선택 비율을 항목 목록으로 나열하되, 북카페 35%를 가장 크게 강조하고 나머지 항목들을 상대적 크기로 배치하는 구조로, 동등한 카테고리 항목들 중 특정 항목(북카페)의 중요도·우선순위를 시각적으로 부각하는 가중 카드 레이아웃입니다.

2.20 Flow Chart(플로우차트)#

대분류 Logic Flow(논리/분기 흐름) · 관측 6 block

사례 1: 95838_3#

사용자 입력 (원문)

text
1. 메시지 교환 흐름 (평상시, CAN1/CAN2 상시 오가는 것들) [CAN1: Linux ↔ Primary/Standby VCU] Linux  → VCU   : authority_status_t(제어권 지정) / status_broadcast_t(센서상태) / vehicle_limit_t(속도제한) / joystick_t VCU    → Linux : heartbeat_t(100ms 주기) / ultrasonic_data_t / accel/imu 데이터 / fault_payload_t(고장 발생 시) VCU    ↔ VCU   : heartbeat_t(상대 생존 확인) / MSG_ULTRASONIC_TRIGGER_NOTIFY(초음파 턴 패싱) [CAN2: VCU ↔ Actuator FW] VCU    → FW    : act_cmd_payload_t(좌/우 구동 명령) / owner_notify_t(소유권 통지) FW     → VCU   : drive_command_ack_t(엔코더 좌/우 피드백) / owner_notify_ack_t 2. 제어권(Control Authority) 흐름 [부팅] → NONE(무송신, 기본값)       ↓ Linux가 authority_status_t로 이 보드를 owner 지정    ACQUIRING → owner_notify_t를 FW에 반복 통지(이때부터 cmd 송신 시작, FW가 source_id로 필터링해 안전)       ↓ FW로부터 owner_notify_ack_t 수신    ACTIVE → CAN2로    ※ lease 타임아웃(authority_status_t 300ms 미수신) 또는 owner가 상대로 넘어가면 → 즉시 NONE 3. 제어(조이스틱 → 구동) 흐름 Linux "joy" 입력    → CAN1 joystick_

Markdown Summary (원문)

markdown
# 2. 제어권(Control Authority) 흐름
* [부팅] → NONE(무송신, 기본값) 
* Linux가 authority_status_t로 이 보드를 owner 지정 
* ACQUIRING
* owner_notify_t를 FW에 반복 통지
* cmd 송신 시작
* FW가 source_id로 필터링해 안전하게 처리 
* FW로부터 owner_notify_ack_t 수신 
* ACTIVE → CAN2로 전환 
* lease 타임아웃(authority_status_t 300ms 미수신) 또는 owner 변경 시 즉시 NONE

Layout Description (모듈 출력 원문)

text
세 단계 상태 전환과 제어권 획득 조건을 화살표로 잇고, 중간에 반복 통지와 타임아웃 조건이 붙는 순차 배치 프로세스형 슬라이드다. 아래에는 Linux, VCU, FW 사이의 메시지 교환과 조이스틱 입력에서 구동 명령, 엔코더 피드백 검증까지의 흐름이 한 화면에 이어지는 가로형 타임라인 흐름도 구성

분류 일반 카탈로그 → Logic Flow(논리/분기 흐름) / Flow Chart(플로우차트)

판단 근거 (모듈 출력 원문)

text
Summary의 핵심이 부팅 상태에서 ACQUIRING → ACTIVE로 전환되는 조건부 상태 흐름이며, lease 타임아웃·owner 변경 시 NONE으로 복귀하는 의사결정 분기 구조다. Layout_description에서 "화살표로 잇고 조건이 붙는" 표현이 조건·분기·예외 처리를 명시하므로 Flow Chart로 분류한다.
사례 2: 95838_2#

사용자 입력 (원문)

text
1. 메시지 교환 흐름 (평상시, CAN1/CAN2 상시 오가는 것들) [CAN1: Linux ↔ Primary/Standby VCU] Linux  → VCU   : authority_status_t(제어권 지정) / status_broadcast_t(센서상태) / vehicle_limit_t(속도제한) / joystick_t VCU    → Linux : heartbeat_t(100ms 주기) / ultrasonic_data_t / accel/imu 데이터 / fault_payload_t(고장 발생 시) VCU    ↔ VCU   : heartbeat_t(상대 생존 확인) / MSG_ULTRASONIC_TRIGGER_NOTIFY(초음파 턴 패싱) [CAN2: VCU ↔ Actuator FW] VCU    → FW    : act_cmd_payload_t(좌/우 구동 명령) / owner_notify_t(소유권 통지) FW     → VCU   : drive_command_ack_t(엔코더 좌/우 피드백) / owner_notify_ack_t 2. 제어권(Control Authority) 흐름 [부팅] → NONE(무송신, 기본값)       ↓ Linux가 authority_status_t로 이 보드를 owner 지정    ACQUIRING → owner_notify_t를 FW에 반복 통지(이때부터 cmd 송신 시작, FW가 source_id로 필터링해 안전)       ↓ FW로부터 owner_notify_ack_t 수신    ACTIVE → CAN2로    ※ lease 타임아웃(authority_status_t 300ms 미수신) 또는 owner가 상대로 넘어가면 → 즉시 NONE 3. 제어(조이스틱 → 구동) 흐름 Linux "joy" 입력    → CAN1 joystick_

Markdown Summary (원문)

markdown
#

# CAN2: VCU ↔ Actuator FW

### VCU → FW
* act_cmd_payload_t(좌/우 구동 명령) 
* owner_notify_t(소유권 통지)

### FW → VCU
* drive_command_ack_t(엔코더 좌/우 피드백) 
* owner_notify_ack_t

Layout Description (모듈 출력 원문)

text
Linux에서 VCU와 FW로 이어지는 제어권과 구동 명령의 교환 관계를 화살표로 풀어낸 흐름도형 프로세스 슬라이드로, 부팅의 NONE에서 ACQUIRING, ACTIVE로 넘어가는 제어권 단계와 Linux의 joy 입력이 joystick_t, control task, 스키드 스티어 계산을 거쳐 drive_command_ack_t로 되돌아오는 순차 배치가 함께 보이는 구성이다.

분류 일반 카탈로그 → Logic Flow(논리/분기 흐름) / Flow Chart(플로우차트)

판단 근거 (모듈 출력 원문)

text
summary의 핵심이 VCU와 FW 간 양방향 메시지 교환(명령 → 피드백)이고, layout_description이 "화살표로 풀어낸 흐름도형"과 "제어권 단계(NONE → ACQUIRING → ACTIVE)"를 명시하므로, 조건·분기·상태 전이를 포함한 논리 흐름 구조로 판정되어 Flow Chart를 선택한다.

2.21 Section Divider(간지)#

대분류 Page Navigation(페이지 안내) · 관측 5 block

사례 1: 100090_1#

사용자 입력 (원문)

text
「遊佐モデル」を核としたグローバルアライアンス:次世代環境インフラの国内外展開戦略

Markdown Summary (원문)

markdown
# 「遊佐モデル」を核としたグローバルアライアンス

## 次世代環境インフラの国内外展開戦略
* 「遊佐モデル」を核に、国内外パートナーとのグローバルアライアンスを構築 
* 次世代環境インフラの国内外展開を戦略的に推進

Layout Description (모듈 출력 원문)

text
제목과 부제인 「遊佐モデル」과 글로벌 아라이언스, 그리고 다음 세대 환경 인프라의 국내외 전개 전략을 크게 전면에 배치한 제목 중심 슬라이드.

분류 일반 카탈로그 → Page Navigation(페이지 안내) / Section Divider(간지)

판단 근거 (모듈 출력 원문)

text
발표 중간 섹션 전환을 알리는 제목 중심 슬라이드로, 「遊佐モデル」과 글로벌 아라이언스라는 새로운 주제 영역으로의 장 전환을 표현하고 있습니다. 실질 정보 콘텐츠보다 페이지 역할이 핵심입니다.

2.22 Gantt Chart(간트 차트)#

대분류 Timeline / Schedule(시간/일정) · 관측 5 block

사례 1: 95249_5#

사용자 입력 (원문)

text
# 신협 디지털 금융서비스 신규 상품 출시 전략  ## 「힘내요 소상공인 대출」 출시 계획  ---  # 1. 추진배경  * 금융산업 전반에 AI 기반 데이터 분석, 디지털 금융 플랫폼, 비대면 서비스 확대가 빠르게 진행되면서 금융서비스의 경쟁 기준이 상품 중심에서 고객 맞춤형·비대면 중심으로 전환되고 있음 * 경기침체와 고금리 지속으로 초기 창업자 및 영세 소상공인의 자금 수요는 증가하고 있으나, 기존 금융권의 담보·신용등급 중심 심사로 인해 금융 접근성은 여전히 제한적임 * 지역경제 침체와 소상공인 경영환경 악화로 인해 생활밀착형 금융지원과 신속한 자금공급에 대한 수요가 확대되고 있음 * 신협은 지역밀착형 협동조합 금융기관으로서 조합원 중심의 상생금융을 실현해야 하는 역할과 책임이 있음 * 신협은 전국적인 지역 네트워크와 조합원 기반의 신뢰를 보유하고 있어, 이를 활용한 생활밀착형 디지털 금융상품 개발에 강점을 가짐 * 기존 예·적금 및 여신 중심의 전통적 상품 구조에서 벗어나, 디지털 채널을 활용한 차별화된 금융서비스를 통해 조합원 편익을 높일 필요가 있음 * 내부적으로도 비대면 금융 수요 증가에 대응하여 신협의 디지털 경쟁력 강화, 신규 고객 유입 확대, 지역 상생 기반 강화가 필요한 시점임  ---  ## 2. 현황  ○ 인터넷전문은행의 성장세가 지속되고 있음.  * 카카오뱅크·케이뱅크·토스뱅크의 2024년 1분기 고객 수는 각각 2,390만명, 1,250만명, 1,000만명 수준으로 확대되었고, 3사 합산 수신잔액은 약 96.6조원에 달함. (각 사 2024년 1분기 실적발표/경영공시) * 인터넷전문은행은 비대면 계좌개설, AI 기반 신용평가, 모바일 대출 실행을 앞세워 신규 고객 유입과 여신 확대를 지속하고 있음. (각 사 2024년 1분기 실적발표자료)  ○ 소상공인의 

Markdown Summary (원문)

markdown
#

# 추진 일정

| 추진 과제 | 1월 | 2월 | 3월 | 4월 | 5월 | 6월 |
| :--- | :---: | :---: | :---: | :---: | :---: | :---: |
| 시장조사 및 요구사항 분석 | ■ | ■ | | | | |
| 상품 기획 및 제도 설계 | | ■ | ■ | | | |
| 전산 개발 및 시스템 구축 | | | ■ | ■ | | |
| 시범 운영(Pilot) | | | | ■ | ■ | |
| 마케팅 및 직원 교육 | | | | | ■ | ■ |
| 상품 출시 및 성과관리 | | | | | | ■ |

Layout Description (모듈 출력 원문)

text
제목과 출시 계획을 전면에 두고, 추진 배경과 문제점, 해결방안은 짧은 문단형 설명으로 정리하며 가운데에는 6단계 프로세스형 흐름을 배치하고 하단에는 6월까지의 간트 차트와 기대효과 수치를 함께 담은 인포그래픽형 포스터 구성입니다

분류 표차트 카탈로그 → Timeline / Schedule(시간/일정) / Gantt Chart(간트 차트)

판단 근거 (모듈 출력 원문)

text
summary의 표는 월별(1월~6월) 시간 축을 열로, 추진 과제 6개를 행으로 배치하고 각 과제의 시작·종료·기간을 ■ 기호로 표현하는 전형적인 간트 차트 구조입니다. layout_description에서도 "6월까지의 간트 차트"로 명시되어 있으며, 업무 기간·진척 관리가 핵심입니다.

2.23 Line / Area Chart(선/영역 차트)#

대분류 Trend(추세) · 관측 5 block

사례 1: 99665_1#

사용자 입력 (원문)

text
1970년도에는 남성 미혼율 1.70%, 여성 미혼율 3.33% 1980년도에는 남성 2.60% , 여성 4.45% 1990년도에는 남성 5.57%, 여성 4.33% 2000년도에는 남성 12.57%, 여성 5.82% 2010년도에는 남성 20.10%, 여성 10.60% 2020년도에는 남성 28.225%, 여성 17.81%

Markdown Summary (원문)

markdown
# 연도별 미혼율 추이 (남성 vs 여성)

| 연도 | 남성 미혼율 (%) | 여성 미혼율 (%) |
|------|-----------------|-----------------|
| 1970 | 1.70% | 3.33% |
| 1980 | 2.60% | 4.45% |
| 1990 | 5.57% | 4.33% |
| 2000 | 12.57% | 5.82% |
| 2010 | 20.10% | 10.60% |
| 2020 | 28.23% | 17.81% |

Layout Description (모듈 출력 원문)

text
연도 축을 따라 남성 미혼율과 여성 미혼율의 변화를 두 개의 선으로 나란히 보여주는 꺾은선 차트 슬라이드로, 1970년부터 2020년까지의 추이를 비교하는 구성이다.

분류 표차트 카탈로그 → Trend(추세) / Line / Area Chart(선/영역 차트)

판단 근거 (모듈 출력 원문)

text
Summary에서 1970~2020년 시간 축을 따라 남성과 여성의 미혼율 변화 추이를 강조하고 있으며, layout_description에서 두 개의 선으로 연도별 변화를 나란히 표현하는 꺾은선 차트 구조로 명시되어 있다. 시간에 따른 수치 변화와 추세 비교가 핵심이므로 Trend 대분류의 Line / Area Chart가 적합하다.

2.24 Calendar(캘린더)#

대분류 Timeline / Schedule(시간/일정) · 관측 4 block

사례 1: 100120_1#

사용자 입력 (원문)

text
(7/25. 토) 8:00-9:00 기상 및 아침 식사 9:00-9:30 공과 진행 9:30-11:30 주제활동 1) 구원과 모으심 2) 새롭게 하심 3) 함께하심 4) 온 세상을 다스리심 11:30-12:20 점심 식사 12:20-1:00 랜덤 빙수 만들기 1:00-2:00 예배 2:00-2:20 사진 촬영 및 물놀이 준비 2:20-4:00 물놀이 4:00-4:30 간식 4:30-5:00 뒷정리 및 귀가

Markdown Summary (원문)

markdown
# 7/25. 토요일 일정표

| 시간 | 활동 내용 |
| :--- | :--- |
| 08:00 - 09:00 | 기상 및 아침 식사 |
| 09:00 - 09:30 | 공과 진행 |
| 09:30 - 11:30 | 주제활동<br>1) 구원과 모으심<br>2) 새롭게 하심<br>3) 함께하심<br>4) 온 세상을 다스리심 |
| 11:30 - 12:20 | 점심 식사 |
| 12:20 - 13:00 | 랜덤 빙수 만들기 |
| 13:00 - 14:00 | 예배 |
| 14:00 - 14:20 | 사진 촬영 및 물놀이 준비 |
| 14:20 - 16:00 | 물놀이 |
| 16:00 - 16:30 | 간식 |
| 16:30 - 17:00 | 뒷정리 및 귀가 |

Layout Description (모듈 출력 원문)

text
시간대별로 하루 일정을 촘촘히 정리한 일정 그리드이며, 표 안에 7/25 토요일의 흐름과 함께 09:30 - 11:30 구간의 주제활동 1) "구원과 모으심" 2) "새롭게 하심" 3) "함께하심" 4) "온 세상을 다스리심"이 묶여 들어간 일정표 레이아웃

분류 표차트 카탈로그 → Timeline / Schedule(시간/일정) / Calendar(캘린더)

판단 근거 (모듈 출력 원문)

text
시간대별로 하루 일정을 시간 순서로 정렬한 일정표이며, 특정 날짜(7/25 토요일)의 시간 단위 이벤트와 활동을 시간 축 위에 배치한 구조입니다. 다만 달력 형태는 아니고 시간 그리드 형식이므로, Timeline과 Calendar 중에서는 시간 단위 일정 관리가 핵심인 점을 고려하면 Calendar가 가장 적합합니다.

2.25 Cycle(순환 구조)#

대분류 Cycle(순환) · 관측 4 block

사례 1: 95018_1#

사용자 입력 (원문)

text
가만히 있을 때 돌아가는 '악순환의 고리'  [스트레스 발생] ➡️ [집에서 종일 누워있기 / 가만히 있기] ➡️ [머릿속에 나쁜 생각과 걱정이 끊임없이 피어남] ➡️ [기분이 더 우울하고 무기력해짐 (반복)]

Markdown Summary (원문)

markdown
# 가만히 있을 때 돌아가는 '악순환의 고리'
* [스트레스 발생] 
* [집에서 종일 누워있기 / 가만히 있기] 
* [머릿속에 나쁜 생각과 걱정이 끊임없이 피어남] 
* [기분이 더 우울하고 무기력해짐 (반복)]

Layout Description (모듈 출력 원문)

text
스트레스 발생에서 시작해 집에서 종일 누워있기와 가만히 있기, 머릿속에 나쁜 생각과 걱정이 끊임없이 피어남, 기분이 더 우울하고 무기력해짐이 화살표로 이어져 다시 반복되는 순환 도식형 슬라이드. ৪단계가 고리처럼 되돌아가는 구조를 강조한 순환 도식 구성

분류 일반 카탈로그 → Cycle(순환) / Cycle(순환 구조)

판단 근거 (모듈 출력 원문)

text
스트레스 발생 → 무기력 → 부정적 사고 → 우울감 심화 → 다시 스트레스로 돌아가는 4단계가 화살표로 연결되어 반복되는 악순환 루프 구조. 마지막 단계가 처음으로 돌아가는 순환 도식이 핵심이므로 Cycle로 분류.

2.26 Resume / CV(이력서/CV)#

대분류 Portrait / Profile(인물 프로필) · 관측 4 block

사례 1: 96347_1#

사용자 입력 (원문)

text
- 광주광역시 지방건설기술심의위원 - 광주광역시 건축위원회 위원 - 도시철도건설·도시공사 기술자문위원 및 전라남도 도로관리 자문 - 남구 도시계획·건축위원회, 광산구·남구 사전재해영향평가위원 - 전남대학교 토목공학과 공학박사 - 토목시공기술사

Markdown Summary (원문)

markdown
# 주요 경력 및 자격

## 위원회 및 자문 활동
* 광주광역시 지방건설기술심의위원 
* 광주광역시 건축위원회 위원 
* 도시철도건설·도시공사 기술자문위원 및 전라남도 도로관리 자문 
* 남구 도시계획·건축위원회, 광산구·남구 사전재해영향평가위원

## 학력 및 자격
* 전남대학교 토목공학과 공학박사 
* 토목시공기술사

Layout Description (모듈 출력 원문)

text
위원회 및 자문 활동과 학력 및 자격을 두 묶음으로 나눠, 광주광역시와 전라남도 관련 경력 4개와 전남대학교 공학박사, 토목시공기술사를 함께 정리한 2개 항목 카드형 레이아웃

분류 일반 카탈로그 → Portrait / Profile(인물 프로필) / Resume / CV(이력서/CV)

판단 근거 (모듈 출력 원문)

text
summary의 핵심이 인물의 경력, 학력, 자격을 이력 중심으로 정리한 것이고, layout_description이 "2개 항목 카드형"으로 경력과 학력을 구분하여 제시하는 이력서 형식이므로 Resume / CV로 분류합니다.

2.27 Q&A Card(Q&A 카드)#

대분류 Q&A Card(Q&A 카드) · 관측 3 block

사례 1: 98141_1#

사용자 입력 (원문)

text
만두 시식하면서 작성하기   1. 사랑하는 사람과의 추억이 있는 노래 하나를 써주세요.  노래 이름 :                                   뮤지션 :  ▶ 이 음악과 어떤 추억이 있나요?  2. 여름밤과 잘 어울리는 노래 하나를 써주세요.  노래 이름 :                                   뮤지션 :   3. 작년에 제일 많이 들은 노래 하나를 써주세요.  노래 이름 :                                   뮤지션 :   4. 중2병 걸렸을 때 들었던 노래 하나를 써주세요.  노래 이름 :                                   뮤지션 :   5. 이별했을 때 들었던 노래 하나를 써주세요.  노래 이름 :                                   뮤지션 :   6. 통째로 듣기 좋은 앨범 하나를 서주세요.  노래 이름 :                                   뮤지션 :

Markdown Summary (원문)

markdown
# 만두 시식하면서 작성하기

## 1. 사랑하는 사람과의 추억이 있는 노래
* 노래 이름 : 
* 뮤지션 : 
* ▶ 이 음악과 어떤 추억이 있나요?

## 2. 여름밤과 잘 어울리는 노래
* 노래 이름 : 
* 뮤지션 :

## 3. 작년에 제일 많이 들은 노래
* 노래 이름 : 
* 뮤지션 :

## 4. 중2병 걸렸을 때 들었던 노래
* 노래 이름 : 
* 뮤지션 :

## 5. 이별했을 때 들었던 노래
* 노래 이름 : 
* 뮤지션 :

## 6. 통째로 듣기 좋은 앨범
* 노래 이름 : 
* 뮤지션 :

Layout Description (모듈 출력 원문)

text
사랑하는 사람과의 추억, 여름밤, 작년 최다 재생, 중2병 시기, 이별, 통째로 듣기 좋은 앨범까지 6개의 음악 주제를 질문 카드처럼 순차 배치하고 각 항목마다 노래 이름과 뮤지션을 적게 한 설문형 나열 슬라이드.

분류 일반 카탈로그 → Q&A Card(Q&A 카드) / Q&A Card(Q&A 카드)

판단 근거 (모듈 출력 원문)

text
6개의 음악 주제가 질문 형식으로 제시되고, 각 항목마다 "노래 이름"과 "뮤지션"을 답변하도록 구성된 질문-답변 대응 형식의 설문형 카드 나열이기 때문입니다.

2.28 Quote / Testimonial(인용/후기)#

대분류 Utterance(발화/인용) · 관측 3 block

사례 1: 96018_3#

사용자 입력 (원문)

text
본 제안은 관할권의 한계를 영리하게 우회하여, 연맹이 선언한 '2026년 4대 핵심 가치'를 실현하고 리그의 지속 가능한 성장을 견인할 최종 마스터키임을 확언함.  [1] 기대 효과 (Expected Benefits) 정량적 기대 효과:  악성 여론 제어: 즉각적인 규정 팩트 체크를 통해 라운드 직후 발생하는 디지털 공간 내 판정 관련 음모론 및 악성 댓글 수치 30% 이상 감소 유도.  팬 만족도 향상: 경기장 내 정보 진공 상태 해소를 통해 직관 관중의 '경기 운영 품질 만족도 지표' 수직 상승.  신규 팬 안착: 김해, 용인, 파주 등 신규 연고지 확대로 유입된 라이트(Light) 팬들이 판정 규칙을 쉽게 이해하도록 도와 리그 이탈률 최소화.  정성적 기대 효과:  브랜드 가치 및 ESG 신뢰 회복: 숨기지 않고 소통하는 '투명한 리그' 이미지를 구축함으로써 브랜드 공정성을 확보하고, 연맹이 지향하는 건강한 축구 생태계의 신뢰 가치 실현.  상생의 문화 조성: 무조건적인 심판 비난 문화를 '규칙 기반의 건전한 비평 문화'로 전환하여 선수, 심판, 팬이 상호 존중하는 환경 조성.  [2] 최종 제언 (Closing Remarks) "판정을 내리는 주체는 KFA일지라도, 그 판정이 담긴 K리그라는 상품을 소비하고 사랑해 주는 '팬들을 향한 책임'은 온전히 프로축구연맹에 있습니다. 2026년, 29개 구단이라는 유례없는 대가족 체제를 맞이한 K리그가 외형적 확장에 걸맞은 질적 성장을 이루기 위해서는 팬들의 마음속 응어리인 '판정 불신'을 반드시 해결해야 합니다. 본 투명성·소통 프로토콜은 연맹의 권한 내에서 기술과 미디어를 활용해 팬덤을 보호하고 리그의 상업적 가치를 수호할 가장 영리하고 필수적인 전략이 될 것입니다."

Markdown Summary (원문)

markdown
#

# [2] 최종 제언 (Closing Remarks)
* "판정을 내리는 주체는 KFA일지라도, 그 판정이 담긴 K리그라는 상품을 소비하고 사랑해 주는 팬들을 향한 책임은 온전히 프로축구연맹에 있습니다. 2026년, 29개 구단이라는 유례없는 대가족 체제를 맞이한 K리그가 외형적 확장에 걸맞은 질적 성장을 이루기 위해서는 팬들의 마음속 응어리인 판정 불신을 반드시 해결해야 합니다. 본 투명성·소통 프로토콜은 연맹의 권한 내에서 기술과 미디어를 활용해 팬덤을 보호하고 리그의 상업적 가치를 수호할 가장 영리하고 필수적인 전략이 될 것입니다."

Layout Description (모듈 출력 원문)

text
기대 효과와 최종 제언을 함께 담아, 정량 효과와 정성 효과를 항목별로 정리한 뒤 아래에는 긴 발화와 인용문을 중심으로 배치한 장문 설명형 슬라이드입니다. 핵심 수치와 메시지가 함께 서 있으면서도 본문은 2개 항목 나열과 발화 카드가 결합된 구성형 슬라이드

분류 일반 카탈로그 → Utterance(발화/인용) / Quote / Testimonial(인용/후기)

판단 근거 (모듈 출력 원문)

text
summary의 핵심이 프로축구연맹의 최종 제언을 담은 장문의 발화·인용문이며, layout_description에서 "긴 발화와 인용문을 중심으로 배치한 장문 설명형"이라고 명시했으므로 발화 형식의 콘텐츠 강조가 우선한다. 앞의 정량/정성 항목 나열은 보조 구성이고, 슬라이드의 가장 중요한 메시지는 인용된 발화 자체다.

2.29 Ending(엔딩)#

대분류 Page Navigation(페이지 안내) · 관측 3 block

사례 1: 98122_9#

사용자 입력 (원문)

text
1. The Gospel and Hangeul in a Hybrid Linguistic Space   2. Research Question  * Translation as defining characteristic of Christianity * Gospel → Vernacular model’s limitations * Complex linguistic environment in Korean context * How should translation be understood?   ⸻  3. Translation Theory in World Christianity  * Andrew Walls’ Indigenizing Principle * Lamin Sanneh’s translatability argument * Limitation: assumes unified vernacular * less attention to the receiving linguistic environment   ⸻  4. Linguistic Environment of Late Chosŏn  * Hanmun (Classical Chinese) tradition * Hangeul vernacular script * Mixed-script writing and Sino-Korean vocabulary * A hybrid linguistic space in the future  ⸻  5. Beyond the Binary  * Elite use of Hangeul in personal writings * Middle-class preference for Classical Chinese * Ordinary people’s use of Chinese characters * Linguistic practices crossed s

Markdown Summary (원문)

markdown
# 10. Conclusion
* Recognizing the complexity of the receiving context 
* The subjectivity of Gospel recipients 
* Complexity as God's gift, not obstacle 
* Mission as genuine encounter

Layout Description (모듈 출력 원문)

text
목차를 중심으로 연구 질문과 세계 기독교의 번역 이론, 조선 후기의 언어 환경, 로스와 이수정, Fenwick와 Trollope의 번역 전략, 그리고 결론과 감사 인사까지 순서대로 나열한 아젠다형 슬라이드 구성이다. 각 항목이 번호로 구분된 카드형 순차 배치로 이어지며 마지막에는 질문과 토론 안내가 붙는 목차 슬라이드

분류 일반 카탈로그 → Page Navigation(페이지 안내) / Ending(엔딩)

판단 근거 (모듈 출력 원문)

text
발표의 맨 마지막 장으로 결론(Conclusion)을 제시하고 있으며, layout_description에서 "마지막에는 질문과 토론 안내가 붙는 목차 슬라이드"라고 명시되어 발표 종결 역할을 하는 페이지이다. 본문 내용의 결론 요약이 아니라 발표 종료를 알리는 슬라이드 역할이 핵심이다.

2.30 Flow(지도 흐름)#

대분류 Geo(지역/지도) · 관측 2 block

사례 1: 97148_1#

사용자 입력 (원문)

text
난타 공연팀과 참가자가 함께 양동이 드럼을 연주하며 송지호 백사장에서 해안도로까지 행진하는 참여형 개막 퍼레이드

Markdown Summary (원문)

markdown
# 참여형 개막 퍼레이드
* 난타 공연팀과 참가자가 함께 양동이 드럼을 연주하며 송지호 백사장에서 해안도로까지 행진하는 참여형 개막 퍼레이드

Layout Description (모듈 출력 원문)

text
난타 공연팀과 참가자가 양동이 드럼을 함께 연주하며 송지호 백사장에서 해안도로까지 이동하는 장면을 가로로 길게 펼친 행진형 퍼레이드 슬라이드이며, 참여자와 동선의 흐름이 전면에 드러나는 순차 배치형 구성

분류 일반 카탈로그 → Geo(지역/지도) / Flow(지도 흐름)

판단 근거 (모듈 출력 원문)

text
summary의 핵심이 송지호 백사장에서 해안도로까지의 지역 간 이동·행진 흐름이고, layout_description이 "가로로 길게 펼친 행진형" "동선의 흐름이 전면에 드러나는 순차 배치"로 지리적 위치 간 이동 경로를 시각화하고 있으므로 [Geo / Flow]로 분류합니다.

2.31 Donut Chart(도넛 차트)#

대분류 Proportion / Composition(비율/구성) · 관측 2 block

사례 1: 96612_1#

사용자 입력 (원문)

text
"노인일자리 사업은 총 34.4억 원 예산 중 상반기 11.2억 원(41.5%) 집행  일자리 사업비: 90.7% (10.25억 원)   인건비(전담인력 등): 8.2% (0.93억 원)  반환금 및 기타: 1.1% (0.11억 원) 원형 그래프로 만들어줘

Markdown Summary (원문)

markdown
# 노인일자리 사업 예산 집행 현황

| 항목 | 금액 | 비율 |
|---|---:|---:|
| 상반기 집행 | 11.2억 원 | 41.5% |
| 미집행 잔액 | 15.8억 원 | 58.5% |
| 합계 | 27.0억 원 | 100% |

Layout Description (모듈 출력 원문)

text
상반기 집행 11.2억 원과 미집행 잔액 15.8억 원의 비중을 중심으로, 전체 예산 집행 현황을 원형으로 나눈 도넛 차트가 전면에 오고 그 옆이나 아래에 일자리 사업비 90.7%, 인건비 8.2%, 반환금 및 기타 1.1%를 보조 수치로 덧붙인 수치 강조형 원형 그래프 레이아웃

분류 표차트 카탈로그 → Proportion / Composition(비율/구성) / Donut Chart(도넛 차트)

판단 근거 (모듈 출력 원문)

text
Summary에서 전체 예산 27.0억 원 대비 상반기 집행(41.5%)과 미집행 잔액(58.5%)의 구성 비율이 핵심이며, layout_description에서 명시적으로 "도넛 차트"로 전체 예산 집행 현황을 원형으로 나누어 표현하고 있기 때문에 Donut Chart로 분류합니다.

2.32 Pie Chart(파이 차트)#

대분류 Proportion / Composition(비율/구성) · 관측 2 block

사례 1: 96612_2#

사용자 입력 (원문)

text
"노인일자리 사업은 총 34.4억 원 예산 중 상반기 11.2억 원(41.5%) 집행  일자리 사업비: 90.7% (10.25억 원)   인건비(전담인력 등): 8.2% (0.93억 원)  반환금 및 기타: 1.1% (0.11억 원) 원형 그래프로 만들어줘

Markdown Summary (원문)

markdown
# 예산 세부 내역

| 항목 | 금액 | 비율 |
|---|---:|---:|
| 일자리 사업비 | 10.25억 원 | 90.7% |
| 인건비(전담인력 등) | 0.93억 원 | 8.2% |
| 반환금 및 기타 | 0.11억 원 | 1.1% |
| 합계 | 11.2억 원 | 100% |

Layout Description (모듈 출력 원문)

text
노인일자리 사업의 상반기 집행 예산을 원형 도식으로 보여주며, 전체 11.2억 원을 기준으로 "일자리 사업비", "인건비(전담인력 등)", "반환금 및 기타"의 비율을 조각으로 나눠 강조한 원형 차트 레이아웃

분류 표차트 카탈로그 → Proportion / Composition(비율/구성) / Pie Chart(파이 차트)

판단 근거 (모듈 출력 원문)

text
전체 예산 11.2억 원을 기준으로 세 항목(일자리 사업비, 인건비, 반환금 및 기타)의 구성 비율을 원형 조각으로 나누어 표현하는 구조로, 전체 대비 각 항목의 비중을 직관적으로 보여주는 파이 차트가 적합합니다.

2.33 Pyramid(피라미드)#

대분류 Hierarchy(위계) · 관측 1 block

사례 1: 98593_1#

사용자 입력 (원문)

text
기여도 인포그래픽

Markdown Summary (원문)

markdown
# 기여도 인포그래픽

## 1. 핵심 성과 및 목표
* **기여도:*
* 40% 
* **목표:*
* 전략적 성장 및 혁신

## 2. 주요 프로젝트 및 실행
* **기여도:*
* 30% 
* **목표:*
* 운영 효율성 및 실행력 강화

## 3. 팀 역량 및 협업
* **기여도:*
* 20% 
* **목표:*
* 인재 개발 및 시너지 창출

## 4. 시장 및 고객
* **기여도:*
* 10% 
* **목표:*
* 고객 중심 가치 및 시장 확대

Layout Description (모듈 출력 원문)

text
핵심 성과 및 목표, 주요 프로젝트 및 실행, 팀 역량 및 협업, 시장 및 고객의 4개 항목을 기여도 40%, 30%, 20%, 10%으로 나눠 보여주는 4분면 카드형 인포그래픽 레이아웃. 각 항목에서 목표를 함께 붙여 비중 차이를 한눈에 읽히게 정리한 수치 강조형 구성

분류 일반 카탈로그 → Hierarchy(위계) / Pyramid(피라미드)

판단 근거 (모듈 출력 원문)

text
Summary의 핵심이 4개 항목의 기여도 비중(40%, 30%, 20%, 10%)을 계층적으로 강조하는 것이고, layout_description이 4분면 카드형으로 비중 차이를 시각화한 구성이므로 위계·우선순위를 면적으로 표현하는 Pyramid으로 분류합니다.

2.34 Pictogram Chart(픽토그램 차트)#

대분류 Proportion / Composition(비율/구성) · 관측 1 block

사례 1: 96805_1#

사용자 입력 (원문)

text
많이 발송할수록 내려가는 단가 픽토그램

Markdown Summary (원문)

markdown
# 발송량 증가에 따른 단가 구조

| 발송량 | 단가 |
|---|---|
| 적은 발송량 | 높은 단가 |
| 많은 발송량 | 낮은 단가 |

Layout Description (모듈 출력 원문)

text
발송량이 많아질수록 단가가 내려가는 흐름을 픽토그램으로 직관적으로 보여주면서, 적은 발송량과 많은 발송량의 단가 차이를 나란히 대비한 비교형 픽토그램 차트 슬라이드

분류 표차트 카탈로그 → Proportion / Composition(비율/구성) / Pictogram Chart(픽토그램 차트)

판단 근거 (모듈 출력 원문)

text
발송량 증가에 따른 단가 감소라는 역비례 관계를 직관적으로 표현하며, 적은 발송량과 많은 발송량의 단가 차이를 아이콘 채움 정도로 시각화하는 픽토그램 구조입니다.

2.35 Nested Scope#

대분류 Scope / Coverage(범위/포괄) · 관측 1 block

사례 1: 94773_1#

사용자 입력 (원문)

text
현실 조건을 반영한 부등식 수립지구 환경 방어선 (최소 감축 총량):$$x + y \\ge 5000개도국 생존 제약 (경제적 감축 마진):y \\le 2000선진국 기술 제약 (현실적 감축 한계):x \\le 4500 [SLIDE 05] 04. 유효 합의 영역(Feasible Region) 분석영역 도출: 세 부등식의 공통 범위를 좌표평면 상에 시각화 (닫힌 다각형 영역 생성)교점 (3000, 2000)의 수학적 시사점:개도국이 최대 한계치(y = 2000)를 투입해도, 시스템이 성립하려면 선진국은 최소 3000이상 감축해야 함 (x \\ge 3000)결론: 기후 정의는 도덕적 선의가 아니라, 시스템의 해(Solution)가 존재하기 위한 수학적 필요조건임

Markdown Summary (원문)

markdown
# 현실 조건을 반영한 부등식 수립

## 지구 환경 방어선 (최소 감축 총량)
* \\( x + y \\geq 5000 \\)

## 도국 생존 제약 (경제적 감축 마진)
* \\( y \\leq 2000 \\)

## 선진국 기술 제약 (현실적 감축 한계)
* \\( x \\leq 4500 \\)

Layout Description (모듈 출력 원문)

text
세 개의 부등식을 좌표평면 위에 겹쳐 공통으로 성립하는 닫힌 다각형 유효 영역을 드러내고, 교점 3000, 2000을 기준으로 해의 존재 조건을 설명하는 수학 도식형 슬라이드입니다. 상단에 부등식 조건을 두고 아래에 제한선과 교차 영역이 강조되는 좌표평면 분석형 구성

분류 일반 카탈로그 → Scope / Coverage(범위/포괄) / Nested Scope

판단 근거 (모듈 출력 원문)

text
세 개의 부등식 조건이 좌표평면 위에서 겹쳐 공통으로 성립하는 닫힌 다각형 유효 영역을 형성하는 구조로, 안쪽(교점 영역)에서 바깥쪽(전체 평면)으로 확장되는 범위·포함 관계를 시각화합니다. 각 제약 조건이 중첩되어 최종 해의 범위를 정의하는 것이 핵심이므로 Nested Scope로 분류합니다.

2.36 Funnel(퍼널)#

대분류 Conversion(전환/축소) · 관측 1 block

사례 1: 94795_1#

사용자 입력 (원문)

text
*   **Title:** 물 새는 퍼널(Funnel) 막기: 증상별 처방전 *   **데이터로 보는 고객 여정 이탈 분석**     *   👀 **노출 높음 / 클릭 낮음: 소재 매력도 부족 > 카피/이미지 A/B 테스트, 외부 요인 모니터링       *   🖱️ **클릭 높음 / 체류 낮음: '어그로성 소재/‘상세페이지와 맞지 않는 소재로 인한 불일치 > 소재 필터링 및 우수 소재 기반 소재 추가, 랜딩 페이지 점검      *   🛒 **체류 높음 / 장바구니 낮음:** 상세페이지 설득력 부족 > 리뷰 추가, 혜택 보강  *   사례 : 마인드카페:** '심리상담'이라는 높은 허들 > 상세페이지에서 이탈 >  기존 검색광고 단독 진행 + 바이럴 광고 추가 진행으로 이탈률 방어

Markdown Summary (원문)

markdown
# 물 새는 퍼널(Funnel) 막기: 증상별 처방전

## 데이터로 보는 고객 여정 이탈 분석

| 구간 | 지표 | 문제 | 처방 |
|---|---|---|---|
| 1단계 | 노출 높음 / 클릭 낮음 | 소재 매력도 부족 | 카피·이미지 A/B 테스트, 외부 요인 모니터링 |
| 2단계 | 클릭 높음 / 체류 낮음 | 어그로성 소재 / 상세페이지와 불일치 | 소재 필터링, 우수 소재 기반 추가, 랜딩 페이지 점검 |
| 3단계 | 체류 높음 / 장바구니 낮음 | 상세페이지 설득력 부족 | 리뷰 추가, 혜택 보강 |

Layout Description (모듈 출력 원문)

text
원인과 증상, 처방을 3단계로 세로로 이어 보여주되 각 단계의 지표와 문제, 대응책이 함께 묶인 프로세스형 슬라이드이며, 아래에는 마인드카페 사례를 덧붙여 이탈 지점을 설명하는 구성형 인포그래픽

분류 표차트 카탈로그 → Conversion(전환/축소) / Funnel(퍼널)

판단 근거 (모듈 출력 원문)

text
고객 여정이 3단계로 진행되면서 각 단계에서 지표(노출→클릭→체류→장바구니)가 순차적으로 변화하고, 각 단계별 이탈 문제와 처방이 함께 제시되는 전형적인 퍼널 전환 구조입니다. 단계별로 대상(고객)이 선별·축소되는 흐름을 보여주는 것이 핵심입니다.

2.37 Dialogue / Chat(대화/채팅)#

대분류 Utterance(발화/인용) · 관측 1 block

사례 1: 95262_1#

사용자 입력 (원문)

text
- '엄마가 해준 맛' 감성 전달 - 정겨운 말투로 작성 요청 - 반찬 만들 시간 없는 고객 니즈 - 5분 방송 오프닝 대본 - 같은 틀에 상품만 변경 - 신선함과 정성 강조

Markdown Summary (원문)

markdown
# 5분 방송 오프닝 대본

## 1. 도입
* "안녕하세요, 여러분! 오늘도 바쁜 일상 속에서 따뜻한 한 끼를 준비하는 여러분을 위해 찾아왔습니다."

## 2. 감성 포인트
* "'엄마가 해준 맛'처럼 정성 가득한 반찬, 직접 만들 시간 없을 때도 포기하지 마세요."

## 3. 고객 니즈 공감
* "반찬 만들 시간은 없고, 가족에게는 늘 신선하고 정성 어린 식사를 해주고 싶은 마음, 저희도 잘 알고 있습니다."

## 4. 핵심 메시지
* "그래서 준비했습니다. 신선함과 정성을 담은 반찬으로, 여러분의 식탁을 든든하게 채워드릴게요."

## 5. 마무리
* "5분 후, 오늘의 추천 메뉴와 함께 다시 찾아뵙겠습니다. 끝까지 함께해 주세요!"

Layout Description (모듈 출력 원문)

text
정겨운 말투로 바쁜 일상 속 반찬 고민을 공감하고, "'엄마가 해준 맛'"과 "신선함과 정성"을 전면에 세운 5개 구간의 방송 오프닝 대본형 슬라이드다. 도입, 감성 포인트, 고객 니즈 공감, 핵심 메시지, 마무리가 순서대로 이어지는 단계형 구성

분류 일반 카탈로그 → Utterance(발화/인용) / Dialogue / Chat(대화/채팅)

판단 근거 (모듈 출력 원문)

text
본문이 방송 진행자의 발화 대본으로 구성되어 있으며, 청중을 향한 일방적 말하기(도입→감성→공감→핵심→마무리)를 순차적으로 전개하는 발화 형식이다. 레이아웃이 5개 구간의 단계형 대본 구성이므로 대화/채팅 형태의 발화 표현에 해당한다.

2.38 Combo Chart(복합 차트)#

대분류 Trend(추세) · 관측 1 block

사례 1: 98357_1#

사용자 입력 (원문)

text
- 2022년 총매출: 5,832만원(기준) - 2023년 총매출: 7,467만원(YoY +28.03%) - 2024년 총매출: 8,817만원(YoY +18.08%) - 2025년 총매출: 1억 240만원(YoY +13.69%) - 3년 CAGR: 19.78%, 중장기 펀더멘털 견고

Markdown Summary (원문)

markdown
# 연도별 총매출 현황

| 연도 | 총매출 | YoY 성장률 |
|------|--------|------------|
| 2022년 | 5,832만원 | - |
| 2023년 | 7,467만원 | +28.03% |
| 2024년 | 8,817만원 | +18.08% |
| 2025년 | 1억 240만원 | +13.69% |
* 3년 CAGR: 19.78% 
* 중장기 펀더멘털 견고

Layout Description (모듈 출력 원문)

text
연도별 총매출의 증가 추이를 시간 축에 따라 보여주되, 2022년부터 2025년까지의 매출과 YoY 성장률을 함께 읽히게 한 꺾은선 차트형 수치 강조 레이아웃이다. 하단에는 3년 CAGR 19.78%와 중장기 펀더멘털 견고라는 핵심 수치를 보조 메시지로 붙인 구성이다

분류 표차트 카탈로그 → Trend(추세) / Combo Chart(복합 차트)

판단 근거 (모듈 출력 원문)

text
Summary에서 연도별 시간 축을 따라 총매출(절대값)과 YoY 성장률(백분율)이라는 서로 다른 단위의 지표 2개를 함께 강조하고 있으며, layout_description에서 "꺾은선 차트형"으로 두 지표를 동시에 표현한다고 명시했으므로 Combo Chart가 적합하다.

3. 여기서 보이는 Markdown Summary 의 한계#

샘플을 훑으면 손실 유형이 반복된다.

  1. 수치 슬롯이 문장에 녹는다. Big Number, Gauge, 차트 계열 입력에서 value, unit, target 이 불릿 텍스트로 흡수돼 '이 숫자가 게이지 값'이라는 사실이 남지 않는다.
  2. 계열(series)이 평탄화된다. 채널별, 분기별 수치가 한 층의 불릿으로 눌려 dimensions × series 행렬 구조를 잃는다.
  3. 관계가 사라진다. Versus, Venn 계열에서 '이 둘이 대비쌍'이라는 정보가 두 개의 ## 섹션으로만 남아 나열형과 구분되지 않는다.
  4. 순서와 계층이 구분되지 않는다. Steps 와 List 가 같은 * 불릿으로 표현돼, 순서가 메시지인지 아닌지를 Summary 만 보고는 못 가린다. Hierarchical Tree 와 들여쓴 리스트도 마찬가지다 (schema-v0.4.md 4-1절 계층).
  5. 표가 리스트로 fallback된다. Listing Table 과 Comparison Table 이 질의 쪽 2위와 5위인데(1절), 행렬 구조를 담을 수단이 Markdown Summary 에 없다. v0.4 4절 역방향 갭의 Table 항목이 이것이었고, v0.5 는 matrix 로 담는다(schema-v0.5.md 2.5절).

이 다섯 가지가 structure 의 entities, relations, dataset 세 필드가 존재하는 이유다.


4. 관련 문서#


Appendix A. 골든셋: 라벨이 붙은 평가 케이스 90건#

같은 시트에 골든셋이 따로 실려 있다. 위 2절이 '실사용 로그에 자동 라벨을 붙인 것'이라면, 이쪽은 분류기를 평가하려고 손으로 고른 케이스다. case_id 는 GC-nnnn, 구현우선순위는 P0 / P1 이다.

구성 P0 50건, P1 40건, 중분류 17종

쓰임새 두 가지. (1) 시멘틱 쿼리 v2 분류기의 정오 판정 기준이다. (2) Visual Summary 생성기가 나오면 같은 입력으로 회귀 테스트를 돌릴 수 있는 셋이다. 같은 summary 에서 같은 structure 가 나오는지, component.type 가 아래 taxonomy_minor 와 어긋나지 않는지를 본다.

여기에는 summary 만 있고 input_text 는 없다. 골든셋은 Summary 단계 이후의 분류만 평가하기 때문이다.

일반 / AttributeComparison / Versus (6건)#

GC-0043 (P0)#
markdown
#

# 주요 대상
* 65세 이상 기초생활수급자, 차상위계층, 기초연금 수급자 중 유사중복사업 자격에 해당되지 않는 어르신
* 제도적으로는 요양등급이 없거나 돌봄이 필요한 취약 노인

### 유사중복 사업자격자
* 노인장기 요양등급자
* 가사·간병방문지원 이용자
* 국가보훈 보훈재가복지서비스 이용자
* 장애인 활동지원사업 이용자
* 기타 국가 및 지방단체에서 시행하는 서비스 중 노인맞춤돌봄서비스와 유사한 재가서비스를 받고 있는 대상자
GC-0044 (P0)#
markdown
# EU AI ACT vs AI 기본법

## 다국가 공동체 vs 단일 국가
* EU와 한국의 가장 큰 차이는 EU는 단일 국가가 아니라 여러 회원국으로 구성된 다국가 공동체
* EC의 AI Act는 단순히 하나의 법을 만드는 것이 아닌, 여러 회원국에 공통으로 적용될 기준을 만들고 이를 조정하는 과정
* 한국은 단일 국가이기 때문에 법 제정과 정책 집행이 EU보다 상대적으로 빠를 수 있음
* 그러나 다양한 이해관계를 조정하는 제도적 장치나 전문 감독체계 미흡
GC-0045 (P0)#
markdown
# Lpar1 Deletion Models

## VillinCreERT2; Lpar1Fl/Fl Model
* Deletion of Lpar1 within the intestinal epithelium

## PdgfraCreERT2; Lpar1Fl/Fl Model
* Depletion of Lpar1 within the intestinal mesenchyme
GC-0046 (P0)#
markdown
#

# 3. 질병을 바라보는 관점

### 동양의학
* 몸 전체의 균형이 깨진 상태로 이해
* 같은 병이라도 사람마다 원인이 다름
* 체질과 몸 상태를 중요하게 생각함
* 병명보다 환자의 상태를 중시

### 서양의학
* 질병의 원인을 정확하게 찾는 데 중점
* 바이러스, 세균 등 원인 확인
* 혈액검사, CT, MRI 등 객관적 데이터 활용
* 질병 중심 관점
GC-0047 (P0)#
markdown
#

# 4. 치료의 차이

### 동양의학
* 침, 뜸, 한약 등으로 몸의 균형 회복
* 자연치유력 강화
* 예방과 건강 유지도 중요

### 서양의학
* 약물치료, 수술, 백신 활용
* 질병 원인 제거
* 증상 빠르게 개선
* 응급치료와 감염병 대응에 강점
GC-0048 (P0)#
markdown
# ACTN3 유전자 형태
* R형
* α-액티닌-3 단백질을 정상적으로 생성하는 형태
* X형
* 단백질을 생성하지 못하는 형태(유전자의 변이)

일반 / Cycle / Cycle (2건)#

GC-0001 (P0)#
markdown
# 가만히 있을 때 돌아가는 '악순환의 고리'
* [스트레스 발생]
* [집에서 종일 누워있기 / 가만히 있기]
* [머릿속에 나쁜 생각과 걱정이 끊임없이 피어남]
* [기분이 더 우울하고 무기력해짐 (반복)]
GC-0002 (P0)#
markdown
# 순환 구조
* 1. 죄
* 2. 압제를 당함
* 3. 간구
* 4. 구원
* 5. 망각

일반 / Enumeration / List (7건)#

GC-0008 (P0)#
markdown
# 다람쥐 전기충전 자동차

## 핵심 개념
* 다람쥐
* 전기충전
* 자동차
GC-0009 (P0)#
markdown
#

# 2. 친환경 주택 및 수소 도시
* 연료전지 주택
* 도시가스나 LPG에서 수소를 추출해 대기 중 산소와 반응시켜 전기와 열을 생산
* 가정 전력 공급
* 난방과 온수 활용
* 수소 시범 아파트
* 인근 산업단지 부생수소나 수소 배관 활용
* 아파트 단지 전체에 전기와 온수 공급
* 100% 탄소중립 주거 단지 모델 운영
GC-0010 (P0)#
markdown
#

# 인구 및 산업
* 전남 1위 귀농귀촌 인구유입
* 6년간 9,444명
* 전국 3위
* 3대 특구(기회발전, 교육발전, 분산에너지)
* 기업이 찾아오는 준비된 해남
* 25조원+ 대기업 투자 유치 발표
* 삼성 17조
* AI컴퓨팅센터 2.5조
* LS전선, 한전 KDN 등
GC-0011 (P0)#
markdown
# 관리 및 준수 사항
* 출결 사항 철저 관리
* 증빙서류 및 내부결재
* 지각
* 생활기록부 기재기준 준수
* 마감기한 준수
GC-0012 (P0)#
markdown
# MEOVV RUN CLUB

## LEAVE YOUR MARK

### 기획 의도
* 최근 러닝은 단순한 운동을 넘어 하나의 라이프스타일이자 자기 표현 문화로 자리 잡고 있다.
* MEOVV RUN CLUB은 팬들과 함께 달리며 자신만의 속도와 흔적을 만들어가는 웰니스 프로젝트로, SCRATCH의 메시지를 오프라인 경험으로 확장하고자 기획하였다.
GC-0013 (P0)#
markdown
# 04. 멘토의 윤리와 활동 자세
* 좋은 멘토는 태도에서 시작

### 1. 약속을 지킵니다
* 시간과 약속을 소중히 생각

### 2. 존중하는 언어를 사용합니다
* 따뜻한 말과 행동으로 대함

### 3. 안전을 먼저 생각합니다
* 멘티가 안전하게 활동할 수 있도록 배려

### 4. 혼자 해결하려 하지 않습니다
* 어려운 상황은 지도교사와 함께 해결

### 5. 끝까지 책임을 다합니다
* 맡은 역할을 성실하게 수행
* 신뢰는 작은 약속을 지키는 것에서 시작
GC-0014 (P0)#
markdown
# 보험 가입 전 필요 보장 체크리스트

## 1. 기본 생활 보장
* **생활비 및 생계 지원*
*
* 예) 가족 부양, 주택 대출
* **기본 의료비*
*
* 예) 입원, 수술, 치료비

## 2. 건강 관련 보장
* **중대 질병 보장*
*
* 예) 암, 뇌졸중, 심근경색
* **장기 요양 및 재활*
*
* 예) 치매, 장애, 지속 치료

## 3. 가족 및 상속 계획
* **자녀 교육 자금*
*
* 예) 학자금, 미래 교육비
* **유산 및 상속 준비*
*
* 예) 재산 정리, 상속세 대비

일반 / Hierarchy / HierarchicalTree (16건)#

GC-0060 (P1)#
markdown
# 조직 현황
* 부산본부
* 진주지사
* 진주지점
* CS3팀(동진주)
* 이화영
GC-0061 (P1)#
markdown
#

# 교육 분야
* 중등교사
* 대학교수
GC-0062 (P1)#
markdown
#

# 가족노년학
* 고령사회에서 노인과 가족의 삶의 질 향상을 위한 제반 환경 학습

### 대표 교과목
* 노인복지론
* 성인발달과노화
GC-0063 (P1)#
markdown
# 도로명주소

## 구성 요소
* **시도*
*
* **시군구*
*
* **도로명*
*
* **건물번호*
*
* **상세주소**
GC-0064 (P1)#
markdown
# 갈래로 나누기

## 문학 갈래
* 시
* 소설
* 수필
* 극

## 비문학 갈래
* 설명문
* 논설문
* 안내문
* 기록문
GC-0065 (P1)#
markdown
# 조직도
* 센터장 이희섭

## 기획운영팀
* 기획운영팀

## 위생팀
* 위생팀

## 영양팀
* 영양팀
* 어린이팀
* 사회복지팀
GC-0066 (P1)#
markdown
# 가계도

## 1세대
* 조부모

## 2세대
* 부모

## 3세대
* 본인
* 배우자

## 4세대
* 자녀
GC-0067 (P1)#
markdown
# 3. 학원
* 수용인원 300명 이상인 학원
* 수용인원 100명 이상 300명 미만인 학원
* 하나의 건축물에 학원과 기숙사가 함께 있는 학원
* 하나의 건축물에 학원이 둘 이상 있는 경우 중 수용인원 300명 이상인 학원
* 하나의 건축물에 다중이용업과 학원이 함께 있는 경우
GC-0068 (P1)#
markdown
#

# 4. 개선 필요 과제
* Payroll 검증체계 구축
* 작업파일 표준화
* 검증프로세스 구축
* Cross Check 체계
* 채용프로세스 개선
* ATS 기능 활용
* 역량검사 도입 검토
* 근태관리 자동화
* App 기반
* 관리자 승인
* 근태 자동연동
* 성과평가 체계 구축
* 평가제도
* 시스템 도입
* 컨설팅 검토
* 연말정산 고도화
* 스마트 연말정산 도입
* 취업규칙 및 노무 점검
* 제도 정비
* 노동관계법 점검
* 리스크 예방
GC-0069 (P1)#
markdown
# 1. 단어의 형성
* 1) 단일어
* 2) 복합어
* (1) 합성어
* (2) 파생어
GC-0090 (P1)#
markdown
# 직무를 위해 더 준비하거나 공부해야 할 것

## 핵심 역량 강화
* **전문 지식 심화*
*
* 최신 트렌드 및 기술 습득
* **기술 스킬 향상*
*
* 실무 도구 및 소프트웨어 숙련

## 실무 경험 확대
* **프로젝트 참여*
*
* 다양한 역할 및 과제 경험
* **문제 해결 능력*
*
* 실전 사례 중심 훈련

## 소프트 스킬 개발
* **커뮤니케이션*
*
* 효과적인 협업 및 보고
* **시간 관리*
*
* 우선순위 설정 및 효율성

## 지속적인 학습
* **교육 프로그램 활용*
*
* 온·오프라인 강의 및 워크숍
* **멘토링 및 네트워킹*
*
* 선배 및 전문가와의 교류
GC-0091 (P1)#
markdown
# 광고 속 성차별 양상

## 1. 성 역할 고정관념
* LG 퓨리케어 미니 공기청정기 광고
* 유모차를 끄는 사람은 오직 여성

## 2. 외모지상주의·성적 대상화

### (1) 서울우유 유기농 우유 광고
* 불법촬영 연상
* 여성을 젖소에 비유

### (2) MLB 모자 광고
* 여성의 외모 규범 강요
* 화장하지 않은 얼굴을 부정적으로 표현
GC-0092 (P1)#
markdown
# 우리는 기술과 상생으로 웰니스의 새로운 의식(Ritual)을 만듭니다

## Vision
* 약국 매대에서 인테리어 홈카페 공간으로의 웰니스 패러다임 시프트

## Mission

### 상생 (Upcycle)
* 규격 외 농산물의 고부가가치 자원화

### 기술 (Bio-Tech)
* 고압 추출 환경에 최적화된 하이브리드 식품 공학

### 문화 (Ritual)
* 바쁜 현대인에게 30초 만에 찾아오는 완벽한 휴식 제공
GC-0093 (P1)#
markdown
# 예산 분류 체계

## 1. 인건비 (100단위)
* 110 인건비
* 공무원의 봉급
* 각종 수당(겸임, 정근, 성과상여금 등)
* 기간제 근로자, 공무직, 계약제 교원의 보수
GC-0094 (P1)#
markdown
# 庄司酒店グループ

## 小売業
* (株)庄司酒店
* 飲食店
* 個人

## 卸売業、仕入れ事業
* (株)酒商
* (株)全日本酒類販売
GC-0095 (P1)#
markdown
#

# 3. 이전지출 (300단위)
* 310 보전금
* 포상금, 보상금, 법정부담금
* 320 민간이전
* 민간경상보조
* 민간위탁금
* 보험료부담금
* 330 자치단체등이전
* 자치단체경상/자본보조
* 자치단체대행사업비
* 타 지자체나 공공기관 지원 자금
* 340 해외이전
* 해외 경상 이전
* 국제부담금

일반 / KPIKeyMetric / BigNumber (11건)#

GC-0049 (P1)#
markdown
#

# 상반기 목표 초과 달성
* 2026년 5월 기준 총 294건 접수
* 연간 계획 실적 432건 대비 68.1% 달성
* 상반기 목표 조기 달성
GC-0050 (P1)#
markdown
#

# 事業会社経営と実績
* 福岡丸福水産など複数企業の経営にも携わる
* 実際の事業会社で障がい者が活躍できる環境づくりを進める
* 九州で14拠点を運営
* グループ売上15億円超
* 2012年以降累計60回以上のメディア掲載実績
* 障がい者雇用の実績を活かし、『障がい者を戦力化する』仕組みづくりを行ってきた福祉グループ
GC-0051 (P1)#
markdown
#

# 신고 인식 수준 향상
* 이동상담 운영 및 학대예방경찰관(APO) 간담회 개최
* 지역사회 캠페인 및 SNS 비대면 홍보 강화
* 노인인권 및 학대예방 교육 지속 실시
GC-0052 (P1)#
markdown
# 2024년 총선을 앞두고 정외과 여섯 명이 시작한 프로젝트
* 구독자 12명에서 시작해 프로젝트 출범 2년 새 약 1200명 달성
* 압도적인 20대 독자 비율
* 우리의 가치에 공감해주는 청년들이 많았다
GC-0053 (P1)#
markdown
# 150+ Change Order 검토
* USD $20K 추가 할인
* USD $15K 인건비 절감
* USD $4K PCO 취소
GC-0054 (P1)#
markdown
# 30~35%

## 범위
* 30%
* 35%
GC-0055 (P1)#
markdown
# 시범사업 효과 분석

## 1차 개정 효과
* 본인부담금 감소
* 참여기관 증가: 55개 → 67개
* 수가 청구 빈도 유의한 증가
GC-0056 (P1)#
markdown
#

# 7. 기대효과

### 정량적 효과
* 신규 소상공인 고객 5,000명 이상 유치
* 사업자 결제계좌 신규 가입률 30% 증가
* 디지털 금융서비스 이용률 25% 향상
* 기업여신 취급액 500억 원 이상 확대
* 비대면 대출 신청 비중 40% 이상 달성

### 정성적 효과
* 초기 창업자 및 영세 소상공인의 금융 접근성 확대
* 조합원 중심의 포용금융 실현
* 디지털 금융 경쟁력 강화 및 금융서비스 혁신
* 지역경제 활성화 및 상생금융 실현
* 신협 브랜드 이미지 및 고객 신뢰도 제고
* 지역밀착형 금융기관으로서의 차별화된 경쟁력 확보
GC-0057 (P1)#
markdown
# 通いサービスの人員配置計算

## 利用定員と勤務時間
* 通いサービスの利用定員:18名
* 日中の勤務時間帯:6:00~21:00(15時間)

## 常勤職員の勤務時間
* 常勤職員の勤務時間:8時間
GC-0058 (P1)#
markdown
#

# 경제 및 관광
* 2조 1천억원 농수축산 소득
* 고소득 농업인 806명
* 6년 연속 도내 최다
* 8,300억원 해남사랑상품권 누적판매
* 지역에 돈이 도는 민생경제
* 984만명 연간 관광객
* 1,000만 시대 눈앞
* LPGA 해남 개최
GC-0059 (P1)#
markdown
# 해남 성과 및 현황

## 재정 및 평가
* 6년 연속 재정 1조원 시대
* 전국 군단위 최초
* 2017년 대비 2배
* 8년 연속 공약이행평가 최우수(SA)
* 110건 가관평가 역대 최다
* 적극행정 전국 군단위 1위

일반 / PageNavigation / Ending (1건)#

GC-0080 (P1)#
markdown
#

# 5. 결론
* 의협과 한의계의 갈등은 문화와 철학의 차이에서 비롯됨
* 어느 한쪽이 절대적으로 맞고 틀린 것이 아님
* 최근에는 통합의학에 대한 관심이 높아짐
* 서양의학의 강점과 동양의학의 장점을 함께 활용하려는 시도

일반 / PageNavigation / Opening (1건)#

GC-0081 (P1)#
markdown
# 나만의 역사책 만들기

## 표지에 꼭 들어가야 할 요소
* 이름
* 제목
* 그림
* 내가 생각하는 역사란

일반 / PageNavigation / TableOfContents (1건)#

GC-0084 (P1)#
markdown
# PPT 순서에 맞는 진행순서 안내

## 1. 도입부
* 인사 및 소개
* 발표 목적 및 개요

## 2. 핵심 내용
* 주요 포인트 1
* 주요 포인트 2
* 주요 포인트 3

## 3. 결론부
* 요약 및 정리
* 향후 계획 및 제안
* 질의응답

일반 / ProcessFlow / Steps (6건)#

GC-0027 (P0)#
markdown
# 입찰 참여 및 견적 제출

## 1. 견적 제출
* 참여 업체 현장 상황 고려
* 기술력 및 장비 가동 효율 고려
* 업체별 단가(낙찰가) 기입
* 견적서 작성

## 2. 기한 준수
* 정해진 마감 기한 내 제출
* 시공 계획서 제출
* 장비 사양서 제출
GC-0028 (P0)#
markdown
#

# 4. 치료 및 예후
* 일반적인 자발성 종격동기종은 예후가 매우 좋음
* 보존적 치료
* 아기의 활력 징후가 안정적이고 호흡 곤란이 없다면 치료 없이 면밀히 모니터링
* 고인 공기는 보통 수일 내에 자연적으로 체내에 흡수됨
* 산소 요법
* 필요한 경우 산소를 공급하여 체내 공기(질소)의 배출 및 흡수를 촉진
* 인공호흡기 조절
* 인공호흡기를 달고 있는 아기라면 폐에 가해지는 압력(PIP, PEEP)을 최소화하여 공기가 더 새지 않도록 조절
* 긴장성 종격동기종 처치
* 극히 드물게 공기 양이 너무 많아 심장과 큰 혈관을 압박하는 긴장성(Tension) 상태가 되면 바늘을 찔러 공기를 빼내는 긴급 배액술이 필요할 수 있음
GC-0029 (P0)#
markdown
# 서비스 파이프라인 프로세스

## Step 1. 자연어 질의 분석
* 사용자 입력 예시
* "서면에 카페 차릴 건데 어디가 좋아?"
* 처리 과정
* Gemini(LLM)가 문장에서 지역(서면)과 업종(카페)을 추출
* 분석을 위한 데이터 준비 시작
GC-0030 (P0)#
markdown
# 권역별 전문가 협의체 및 현업 맞춤형 개선 체계

## (1) 3대 권역별 전문가 협의체 구축
* 3개 권역 분할
* 전국을 중부권, 호남권, 영남권으로 세분화하여 체계적 관리
* 인적 네트워크
* 본사·지역본부 설계기준 담당자
* 연구진
* 분야별 전문가
GC-0031 (P0)#
markdown
#

# 나. 과정

### 1. 센서 연결
* MAX30102 센서의 핀을 아두이노 우노에 다음과 같이 연결
* Vin → 5V
* SDA → A4
* SCL → A5
* GND → GND

### 2. 라이브러리 설치
* [SparkFun MAX3010x Sensor Library](https://github.com/sparkfun/SparkFun_MAX3010x_Sensor_Library) 다운로드
* Arduino IDE에서 [스케치] - [라이브러리] - [zip 라이브러리 추가]를 클릭
* 다운로드한 zip 파일 선택하여 라이브러리 추가
* 예제 코드와 함께 라이브러리 설치 완료

### 3. 예제 코드 실행
* Arduino IDE에서 [예제] - [SparkFun_MAX3010x_Sensor_Library] - 'HeartRate' 파일 열기
* 코드를 업로드한 후 센서에 손가락을 갖다 대어 테스트

### 4. 임계값 설정
* 일정 혈류량 이하일 때 시리얼 모니터에 표시되도록 스케치 수정
* 수정한 코드 업로드
GC-0032 (P0)#
markdown
# 단계별 평가 파이프라인

## 1단계
* 소요시간 비교
* 바이어 추천 결과 비교

## 2단계
* 정보 채워짐 정도 평가
* 홈페이지
* 이메일
* 취급 품목
* 기업 최근 활동 데이터 추가량 평가

## 3단계
* 리랭킹 후 변화 정도 측정

## 4단계
* 기업 자체 평가

일반 / Progression / Staircase (5건)#

GC-0022 (P0)#
markdown
# 속도증가 레벨 트레이닝

## 레벨 1: 기본 속도
* 안정적인 자세와 정확한 동작
* 기본 속도 유지

## 레벨 2: 중간 속도
* 반응 속도 향상
* 중간 속도 도입

## 레벨 3: 고속 반응
* 빠른 판단과 실행
* 고속 반응 훈련

## 레벨 4: 최고 속도
* 한계 속도 도전
* 최고 속도 목표
GC-0023 (P0)#
markdown
# 2단계 점진적 상향 프로세스 구조

## 1단계: 기초 구축 및 준비
* 초기 자원 투입
* 기본 역량 개발
* 데이터 수집 및 분석

## 2단계: 확장 및 고도화
* 시스템 통합 및 최적화
* 고부가가치 창출
* 지속 가능한 성장
GC-0024 (P0)#
markdown
# 단계별 성장 로드맵

## 1단계: 기초 구축
* 학습 및 준비
* 초기 시스템 도입
* 핵심 역량 확보

## 2단계: 성장 및 확장
* 시장 진입 및 확대
* 제품/서비스 고도화
* 고객 기반 확장

## 3단계: 성숙 및 최적화
* 운영 효율화
* 데이터 기반 의사결정
* 지속 가능한 경쟁력 확보
GC-0025 (P0)#
markdown
#

# 미국 금융권 현황
* **선두 그룹 (4단계 시스템화)*
*
* 전 인력 정기 사용
* 수백 개 유스케이스 프로덕션 동시 운영
* 예: JPMorgan, Capital One
* **업종 평균 (3단계 운영)*
*
* EY-Parthenon 조사(2025)
* 100개 은행 대상
* 77%가 GenAI 출시
* 47%가 전면 롤아웃 (2023년 10% → 급증)
GC-0026 (P0)#
markdown
#

# 당사 현황
* **단계:*
* 3단계 운영 진입
* **현황:*
*
* M365 Copilot 전사 확산 진행 중
* GenOS 기반 에이전트 개발 착수
* 도구·플랫폼은 확보했으나 전사 확산·성과 측정은 아직 확보 전 단계

일반 / ReasoningStructure / ProblemSolution (8건)#

GC-0070 (P1)#
markdown
# 우리의 해결 방안
* AI 예측에 그치지 않는 실행 중심 플랫폼
* 건강검진 데이터를 예방상담 → 예방중재 → 추적관리로 연결
* 기관의 의사결정을 지원하는 데이터 운영 플랫폼 구축
GC-0071 (P1)#
markdown
#

# 3. 문제점
* 초기 창업자 및 영세 소상공인의 금융 접근성이 부족
* 디지털 기반 맞춤형 금융서비스가 부족
* 금융상품의 차별성이 부족
GC-0072 (P1)#
markdown
# 보존과 생계의 딜레마
* 세계 최대 맹그로브 숲 vs 지역 주민의 생계
* 난개발

## 실천적 롤모델 탐색
* 지속 가능한 생태 비즈니스 발굴이 가장 시급한 당면 과제
GC-0073 (P1)#
markdown
# 작업 환경(정전기) 개선을 통한 포장 이물 불량 저감 결과

## 1. 추진 배경 및 문제 정의
* 최근 포장 공정 완료 제품에서 미세 이물 발생 빈도 증가
* 제품 자체의 결함이 아닌 포장 단계에서 외부 이물이 유입·흡착되어 품질 저하 및 고객사 클레임 리스크 발생
* 포장 공정 특성상 자재 및 설비 간 마찰로 고전압 정전기가 발생하고, 이로 인해 공기 중 미세 이물을 끌어당기는 정전기 흡착 현상이 불량의 주요 원인으로 추정
GC-0074 (P1)#
markdown
# 구매 결정 요소

## 고객 고민
* "제대로 배송될까?"
* "사진과 같은 상품일까?"
* "믿을 수 있는 업체일까?"

## 필요 콘텐츠
* 실제 배송 사례
* 고객 후기
* 상품 추천 정보
GC-0075 (P1)#
markdown
# 1. 실무자 자체 제작 홍보물

## 현황 및 규정
* 현재 매뉴얼상 CI 위치 규정만 존재
* 좌측 상단, 중앙, 좌측 하단, 하단 중앙 배치로 제한

## 문제점
* 비주얼 레이아웃상 우측에 배치하지 못할 합리적인 이유가 없음에도 경직된 규정이 존재
* 실무 디자인 제작 시 불필요한 제약으로 작용

## 개정안
* 해당 위치 제한 규정 삭제 원함
GC-0076 (P1)#
markdown
#

# 3. 상세 분석

### ① 패킷 레벨 분석
* tcpdump를 이용해 사무실 구간 실시간 트래픽 캡처
* 개별 패킷 단위로 발신지·목적지·전송 시각 분석
* 파일서버 통신 지연 시점에 사무실 내 IPTV가 발생시키는 멀티캐스트 트래픽 확인
* 해당 트래픽이 특정 방송 스트림을 위해 반복 전송되는 대용량 패킷 형태로 유입
* 정상 구조라면 멀티캐스트 트래픽은 실제 수신 기기(IPTV)로만 전달되어야 함
* 그러나 사무실 내 모든 단말로 무차별 전달(플러딩)되고 있음

### ② 라우터 설정 점검
* 라우터(다산 스위치) 관리자 화면에서 멀티캐스트 트래픽 제어 관련 설정 점검
* IGMP 스누핑 기능이 "블로킹 모드"로 설정되어 있음 확인
* IGMP 스누핑은 스위치가 멀티캐스트 트래픽을 실제 수신을 요청한 기기에만 선별 전달하도록 하는 기능
* 이 기능이 정상 동작(기본 모드)해야 불필요한 트래픽 확산을 막을 수 있음
* 그러나 "블로킹 모드" 오설정으로 인해 스위치가 멀티캐스트 트래픽의 목적지를 제대로 식별하지 못함
* 결과적으로 사무실 LAN 전체에 무차별 전달(플러딩)되는 상태였음

### 근본 원인
* IGMP 스누핑 오설정으로 인한 IPTV 멀티캐스트 트래픽의 사무실 LAN 전체 플러딩
* 이 멀티캐스트 트래픽이 파일서버 통신을 포함한 일반 데이터 트래픽과 네트워크 대역을 잠식·경합
* 지연·패킷 드랍 유발
* 클라이언트가 이를 서버 응답 없음으로 판단하여 강제 로그아웃 처리
GC-0077 (P1)#
markdown
# 프로그램 반영방안

## 1. 신규 주민 참여 확대
* 신규 참여자 모집
* 세대별 프로그램
* 가족 참여 프로그램

## 2. 생활체육 특화
* 한궁 기자재 지원
* 건강체조
* 생활체육 리그
* 건강챌린지

## 3. 치유·건강 프로그램 강화
* 근력운동
* 낙상예방
* 건강측정
* 운동처방
* 백운관 연계

## 4. 주민활동가 육성
* 마을 건강코디네이터
* 프로그램 서포터즈
* 주민강사 양성
* 활동가 인증제

## 5. 미래형 아동 프로그램
* AI 체험
* 영상편집
* 3D펜
* 디지털 콘텐츠 제작
* 문화체험

일반 / RelationalComparison / SWOT (1건)#

GC-0021 (P0)#
markdown
# SWOT Analysis

## Strengths
* Internal advantages and core competencies
* What the organization does well
* Unique resources and capabilities

## Weaknesses
* Internal areas for improvement
* Resource gaps and skill shortages
* Processes or systems that hinder performance

## Opportunities
* External factors the organization can capitalize on
* Emerging market trends and growth areas
* Gaps in the market or unmet customer needs

## Threats
* External challenges and risks
* Competitive pressures and market shifts
* Regulatory changes or economic downturns

일반 / RelationalComparison / VennDiagram (2건)#

GC-0078 (P1)#
markdown
# 핵심 역량 벤다이어그램

## 구성 요소
* 끈기
* 적응력
* 문제해결력 (겹침 영역)
GC-0079 (P1)#
markdown
# 4개 영역 교집합 분석

## 4개 영역 공통 교집합
* **공통 영역:*
* 4개 영역이 모두 겹치는 중앙 교집합

## 3개 영역 공통 교집합
* **3개 영역 공통:*
* 3개 영역이 겹치는 교집합

## 2개 영역 공통 교집합
* **2개 영역 공통:*
* 2개 영역이 겹치는 교집합

## 개별 영역
* **개별 영역:*
* 각 영역별로 독립된 부분

일반 / TextBasedCommunication / HighlightedMessage (6건)#

GC-0003 (P0)#
markdown
# ARB 중 유일한 CV 보호 적응증 보유
* ARB (Angiotensin Receptor Blocker) 계열 약물 중에서 유일하게 심혈관 보호(CV Protection) 적응증을 보유한 제품이 존재함
* 해당 약물은 단순 혈압 강하제 역할을 넘어 심혈관계 위험 감소에 대한 추가적인 임상적 근거를 확보한 상태임
GC-0004 (P0)#
markdown
# 소득 없는 상황의 대출, 막막함 속에서 흔들리지 말아야 할 것
* 대학생, 주부, 이직 준비 중인 무직자분들이라면 누구나 한 번쯤 겪는 막막한 순간입니다.
* 하지만 마음이 급하다고 해서 출처도 모르는 고금리 사금융이나 불법 대출 광고에 손을 대면 절대 안 됩니다.
* 소득이 없다고 무조건 대출이 거절 당하거나 고금리 불법대출을 찾아야 하는 건 아닙니다.
GC-0005 (P0)#
markdown
# 도심 속 휴식
* 바쁜 도심 속에서도 조용한 휴식 공간은 우리 곁에 존재
* 카페, 미술관, 공원 등
* 짧은 시간이라도 온전히 나를 위한 휴식으로 일상의 활력
GC-0006 (P0)#
markdown
# 강의실을 넘어 현장으로
* 안전의 답을 찾아 일본으로
* 새로운 여행이 시작됩니다
GC-0007 (P0)#
markdown
# 전략 방향
* 도구의 신뢰도 및 공신력 강화
* 구간별 연결률 상승 집중
GC-0089 (P0)#
markdown
# MERRY CHRISTMAS
* M
* E
* R
* R
* Y
* C
* H
* R
* I
* S
* T
* M
* A
* S

일반 / TextBasedCommunication / LongFormTextLayout (6건)#

GC-0015 (P0)#
markdown
# 견진교리 1학기 정리
* 이번 주로 신앙학교 1학기 대면교리가 모두 끝나게 됩니다.
* 그간 교리를 돌아보면, 우리는 나, 하느님, 세상에 대해서 학기 초에 함께 이야기를 나누었습니다.
* 나와 이 세상을 만드신 하느님의 뜻을 알고, 이 세상을 구원하러 오신 예수님에 대해서 배웠습니다.
GC-0016 (P0)#
markdown
#

# 교회사
* 지난달에는 초대교회가 어떻게 만들어졌는지, 그 이후에 종파가 어떻게 갈라졌는지를 아는 세계 교회사에 대해 배웠습니다.
* 지난주에는 한국 천주교회의 역사에 대해서 배웠습니다.
* 한국 천주교회의 가장 큰 특징은 성직자나 선교사가 아닌 평신도가 스스로 찾아서 믿음을 시작했다는 점입니다.
GC-0017 (P0)#
markdown
# 탐구 배경
* 국어 시간에 염상섭의 소설 《만세전》을 배우며 식민지 수탈 속 농민들의 구체적 현실에 대해 궁금해짐
* 당시 조선 경제의 대부분을 차지했던 농민들이 어떤 불이익과 착취를 당했는지 확인하고자 함
* 지식인의 관찰을 넘어 직접 고통받던 이들의 삶을 구체적으로 확인하고자 함
* 1930년대 농촌을 배경으로 한 박영준의 소설 《모범경작생》을 찾아 읽음
* 두 소설 속에 나타난 불공정한 경제 구조를 분석하고 올바운 사회적 책임이 무엇인지 확인하고자 탐구를 진행함
GC-0018 (P0)#
markdown
#

# 완충용액의 화학적 원리와 혈액 속 탄산 완충계

### 완충용액의 기본 원리
* 완충 용액은 약한 산과 그 짝염기로 구성된 용액으로, pH 변화를 완화하는 역할을 함
* 유입된 산은 짝염기가 수소 이온을 받아들여 소모하고, 유입된 염기는 약산이 수산화 이온과 반응해 중화함
* 완충작용은 산도 변화를 완전히 없애는 것이 아니라, 화학 평형의 이동을 통해 변화 폭을 줄여 시스템을 안정적으로 유지하는 방어 구조임

### 구강과 혈액의 완충 작용 비교
* 구강 내 타액은 완충 효과를 통해 산성화를 줄여 치아 손상을 막는 국소적 역할을 함
* 혈액은 전신의 세포 기능을 보호해야 하므로 더 복잡하고 정교한 체계를 가짐
* 혈액 속 물질들은 한 방향으로만 반응하지 않고, 조건 변화에 따라 가역적으로 반응 방향이 조절되는 화학 평형을 형성함

### 탄산-탄산수소 이온 완충계(중탄산염 완충계)
* 이산화탄소와 물이 결합해 탄산이 되고, 탄산이 다시 수소 이온과 탄산수소 이온으로 나누어지는 가역 반응이 핵심임
* 혈액 내 수소 이온 농도가 증가하면 르샤틀리에 원리에 따라 역반응이 우세한 방향으로 평형이 이동함
* 유입된 수소 이온은 탄산수소 이온과 결합해 탄산으로 변환되어 pH가 지나치게 낮아지는 것을 억제함
* 반대로 수산화 이온이 유입되면 수소 이온 농도가 감소하고, 이를 보충하기 위해 정반응이 우세한 방향으로 평형이 이동함
* 탄산이 이온화되는 정반응이 우세한 방향으로 평형이 이동해 pH가 지나치게 높아지는 것을 억제함
GC-0019 (P0)#
markdown
# pH와 수소 이온 농도

## pH 정의
* pH = -log[H⁺]

## 로그 관계
* pH는 수소 이온 농도와 단순 비례하지 않고 로그 관계를 가짐
* 수소 이온 농도가 10분의 1로 감소하면 pH는 1 증가
* 수소 이온 농도가 100분의 1로 감소하면 pH는 2 증가

## 시사점
* 물이나 음식물이 위액을 희석하더라도 pH가 섭취량에 비례하여 급격히 변한다고 보기 어려움
* pH 변화는 단순히 물이나 음식물의 양뿐 아니라 수소 이온 농도 변화와 체내 조절 작용을 함께 고려해야 함
GC-0020 (P0)#
markdown
# 기회는 지나갔고, 내 인생에 영향이 생길 수도 있다
* 아니 생긴 것 같다.
* 화가 많이 났지만, 로봇에게 할 수 있는 복수는 없었다.

## 하지만 AI는 이런 원망스러운 일을 나한테만 한 것이 아니다
* 실제로 오픈AI가 청소년을 자살로 이끈 사례가 있다.
* AI가 청소년의 자살을 부추기고, 돕는 답변을 해버린 것이다.
* 이런 극단적인 사례로 유가족의 소송이 진행되었다.

## AI의 발전으로 사람들은 AI에 심하게 의존했고, 그로 인해 이런 일이 발생했다
* 물론 AI에 너무 의존하고, 결국 행동을 한 것은 인간이기에 AI 탓을 하는 게 웃기다고 하는 견해도 있을 수 있다.
* 하지만 이러한 경험을 한 나는 AI 의존이 인간의 세상에 정말 위협이 될 수도 있다는 것을 체험한 것 같다.

## 그래서 나는 AI가 조금 싫어졌고, 원망스러워졌다
* 나쁘게 말하면 이런 표현이 맞는지는 모르겠지만, 인간 세상에 이런 식으로 점점 침투할 것이라는 게 조금 역겨웠다.
* 물론 복수할 수 있는 건 아니었다.

일반 / TimelineSchedule / Timeline (6건)#

GC-0033 (P0)#
markdown
# 단어 암기 반복법
* 1회차 학습 후 20분~1시간 사이 복습
* 2회차는 1일 뒤 복습
* 3회차는 1주일 뒤 복습
* 4회차는 2주~1달 뒤 복습
GC-0034 (P0)#
markdown
# 7. Lee Su-jung's Mixed-Script Approach
* Confucian scholar and high official background
* Gospel of Mark (1884), Matthew (1885)
* Selective use of Hangeul and Chinese characters
* Prioritizing theological precision
GC-0035 (P0)#
markdown
# 경력 타임라인
* 모니터요원
* 서비스강사
* 사무기획
* 홍보마케팅
* 연수원운영
* 컨설팅
* 브랜딩
* NCS프로그램개발
* 조직경영
* MBA
* 대학강의
* 책 작가
GC-0036 (P0)#
markdown
# 설립 및 운영 개요
* 설립시기: 2024년 10월
* 운영주기: 상시운영

## 연혁
* 2024년 10월: 설립
* 2024년 12월: 왓이즈러브 팝업스토어 주최
* 2025년 2월: 북콘서트
* 2025년 4월: 피스 오브 미 주최
* 2025년 5월: 피스 오브 러브 강연 개설
* 2025년 8월: 나만의 음악키링만들기 (재능 기부)
* 2026년 1월: 리셋파티 주최
* 2026년 3월: 파인딩미 MOU 체결
* 2026년 5월: 마인드컬러 MOU 체결
GC-0037 (P0)#
markdown
# 기술의 발전과 혜택의 분배

## 기술 발전의 흐름
* 과거: 농업 중심, 산업 혁명, 기계화
* 현재: 디지털 혁명, 인공지능, 데이터 기반
* 미래: 첨단 기술, 자동화, 초연결 사회
GC-0038 (P0)#
markdown
# CERTIFICATION STATUS | 인증현황

## 2003.08
* ISO 9001 인증

## 2011.05
* 벤처기업 등록

## 2013.03
* ISO 14001 인증

## 2014.03
* 기술혁신형 중소기업(INNO-BIZ) 인증 획득

## 2018.08
* 친환경 기업 인증 획득

## 2022.10
* KS Q ISO 45001 / ISO 45001 인증

일반 / Transformation / Transformation (5건)#

GC-0039 (P0)#
markdown
# E-Plate (글로벌 일품 전문 코너)

## 운영 방향
* 기존 샐러드 제공을 줄이고 메인 요리 양과 서브찬 구성에 집중
* 변화된 포인트를 가시적으로 전달
GC-0040 (P0)#
markdown
# 엣지 데이터센터 필연성

## 10MW 미만 규제 우회
* 소형 스펙으로 구성하여 거대한 신규 송전선 없이 기존 도심 배전망 활용 승인

## 민원 소지 최소화
* 외관이 일반 오피스 빌딩과 동일하며, 도심형 수랭식 냉각으로 전자파·소음 문제 완화

## 10ms 이하 초저지연 구현
* 실시간 연산이 핵심인 도심 속 테넌트(AI 추론, 금융 등)를 바로 옆에서 즉각 처리
GC-0041 (P0)#
markdown
# 3 Learn & Sustain 보드게임 (러닝 키트)

## Before (문제)
* 반도체 공정 이해 장벽이 높아 흥미와 학습 지속이 어려움

## After (변화)
* 게임을 통해 공정을 쉽고 재미있게 학습, 이해도와 관심도 향상

## 남는 가치 (지속 자산)
* STEM 교육 키트 (러닝 키트)
* 다음 기수 활용 가능 자료
* 확장 가능한 교육 프로그램
GC-0042 (P0)#
markdown
# 2026년, 췌장장애가 새로운 장애 유형으로 신설되었습니다

## 기존
* 장애 유형 15개

## 2026년부터
* 췌장장애 신설
* 총 16개 장애 유형
GC-0088 (P0)#
markdown
# 5단계 교육연구단 패널 개편 방향
* 학문 특성을 극대화하는 3대 핵심 트랙으로 재편

## 1. 4단계 구조
* 미래인재양성(기초학문, 응용학문)
* 혁신인재양성(신산업분야, 산업사회문제해결)

## 2. 5단계 구조

### 기초학문분야인재(기초인재)
* 기초과학, 인문사회, 예술체육 등 시장 논리에 취약한 학문 보호
* 한국형 지표(k-index) 고도화를 통해 학문분야 특수성 반영
* 전통 단일 학과의 융통성 있는 참여를 통한 학문의 깊이 보존

### 학문기술융합분야인재(융합인재)
* 국가전략기술 공통 18개 분야 단일형 구축
* 다층적 융합 촉진을 위한 연합형(학문 간, 지역 간, 기관 간) 대폭 확대

### AI기술복합분야인재(AI인재)
* AI 기술 자체를 고도화하는 AI 단일형
* 다양한 도메인과 AI를 결합하는 연합형(AI+X, X+AI) 신설(Bottom-up)

↑ 목차로 · 6부 머리로

40장골든셋 초안 1,000건 회수 요약 참고오분류 축 분석

원문 reference/golden-set-draft-recovery.md GitHub 에서 보기 11 KB


title: 골든셋 초안 1,000건 회수 요약 description: 폐기한 초안 CSV 에서 뽑아 둔 것. FAIL 162건 중 138건을 손으로 읽어 종별 오분류 축을 세웠고, 실유저 input 75건의 형태를 재 두었다. 초안의 판정이 input_text 가 아니라 렌더된 슬라이드를 보고 내려졌다는 사실과, 표 계열에서 판정 기준이 규약과 어긋난다는 사실도 여기 남긴다.#

골든셋 초안 1,000건 회수 요약#

이 문서의 역할 폐기한 초안 CSV 에서 되쓸 값만 남긴다. 골든셋 v1 의 boundary 난이도 설계와 input 제작 지침이 여기를 근거로 삼는다.

오너 황현진 (MORDOR) · 상태 보존 · 최종 갱신 2026-08-24

관련 plan/golden-set-blueprint.md 3절 · 5.1절 · 7절 · 9절 · MOR-2165

이 문서가 SSoT이다. 원본 Visua- Summary-골든셋-초안.csv 는 2026-08-24 에 지웠다. 폐기는 승인됐고(2026-08-24 황현진: "이 원본은 사라져도돼. 이미 백업은 있어") 골든셋 v1 이 input 을 새로 만들므로 본문은 필요 없다.


0. 먼저 알아야 하는 것#

초안의 PASS/FAIL 은 input_text 를 보고 내린 판정이 아니다. 렌더된 슬라이드를 보고 내린 판정이다.

판정문 25건을 전수로 읽어 확인했다. 근거로 쓰인 사실이 시각 정보다. "4개의 카드가 화살표로 순차 연결되어 있으며", "2×2 카드 그리드로 나열하고 있으며", "슬라이드 면적과 정보량의 절반 이상을 차지하여" 같은 문장이 판정의 중심에 있다. 이 사실들은 입력 텍스트에 없다.

가장 또렷한 예가 idx 41163 이다. input_text 는 계속 두드리기' 여덟 자인데 라벨은 Steps(단계) 이고, 판정문은 이렇게 적었다.

"번호와 연결선이 있지만 이는 읽기 방향과 시각적 연결감을 주는 보조 장치에 가깝고, 각 항목을 반드시 순서대로 실행해야만 의미가 성립하는 절차적 흐름(Process Flow/Steps)으로 보기에는 무리가 있어"

그 번호와 연결선은 입력 텍스트에 없다. 여덟 자로는 어떤 카테고리도 정할 수 없다.

여기서 두 가지가 따라온다.

첫째, 초안의 라벨은 input_text 의 정답이 아니다. 설계 8절이 초안 input 재활용을 기각한 것은 결과적으로 맞았다. 다만 기각 근거로 적힌 "두 계보가 섞여 사람이 더 많이 봐야 한다"보다 강한 근거가 있다. 초안의 라벨은 우리가 풀려는 문제의 답이 아니다.

둘째, 83.8%를 게이트 통과율의 기준선으로 쓸 근거가 약하다. 판정자가 이미지를 보고 있었고 골든셋 v1 은 이미지 없이 간다(설계 9절). 정보량이 다른 두 과제의 정답률을 견줄 수 없다. 설계 7절의 추정은 파일럿 실측으로 대체해야 한다.


1. 무엇을 지웠고 무엇을 남겼나#

지운 것은 1,000행 전량이다. 열은 idx, input_text, summary, layout_description, taxonomy_minor, image_url, P/F, judge_reason, 코멘트 아홉이었다.

남긴 것은 다섯이다.

남긴 것 어디에 쓰나 절
판정의 근거가 이미지였다는 사실 7절 추정의 기준선을 파일럿 실측으로 바꿀 근거 0
라벨별 정답률 종별 난이도의 사전 정보 2
ProcessFlow.Steps 오분류 축 25건 전수 파일럿 10건의 boundary, ambiguous 슬롯 3
ProcessFlow.Steps 실유저 input 75건의 형태 통계 제작 input 이 실사용 분포를 벗어나지 않게 하는 지침 4
나머지 11종의 오분류 축 84건 나머지 44종의 슬롯 설계 5

FAIL 162건 중 138건을 손으로 읽었다. Steps 축 25건(3절)과 표본이 3건 이상인 11종의 84건(5절), 그리고 Section Divider 14건(6절)이다. 남긴 20건은 라벨마다 1~2건뿐이라 축으로 세우지 않았다. 표본 하나로 경계를 정하면 그 한 장의 특성이 45종의 설계가 된다.

정규식으로 카테고리 이름을 세는 방식은 쓰지 않았다. 판정문이 "이것은 List 가 아니다"와 "이것은 List 다"를 같은 낱말로 쓰므로 방향이 뒤집힌다. 처음에 그 방식으로 집계했다가 버렸다.


2. 실측 재확인과 45 어휘 보정#

전수 파싱 결과가 설계 3절의 값과 일치했다. 1,000행, 라벨 36종, PASS 838건, FAIL 162건이다.

다만 83.8%는 45종 기준 정답률이 아니다. 라벨 36종 중 셋이 어휘 45종 밖이고, 그 60행에서 FAIL 33건이 나왔다.

어휘 밖 라벨 행 FAIL 왜 어휘 밖인가
Definition Card(정의 카드) 57 31 표차트 카탈로그 계보. 규약 45종에 없다
Weighted Cards(가중 카드) 2 1 같음
Flow(지도 흐름) 1 1 Geo_Flow. 2026-08-24 싱크 결정 1번이 생성기 어휘에서 뺐다

어휘 밖 60행을 걷어내면 940행 중 FAIL 129건, 정답률 86.3% 다. 어휘 밖 라벨의 정답률이 45%(60건 중 27건)로 유독 낮아서 전체 값을 2.5포인트 끌어내리고 있었다.

라벨별 정답률에서 눈에 띄는 것이 셋이다.

라벨 행 PASS 정답률 읽기
List(리스트) 508 489 96.3% 최종 대안이라 틀릴 자리가 적다
Steps(단계) 75 70 93.3% 파일럿 종. 어휘 안에서 높은 편이다
Section Divider(간지) 24 10 41.7% 어휘 안에서 최악이다 (6절)
Gauge(게이지) 8 2 25.0% 표본이 작아 판단을 걸지 않는다

파일럿 통과율은 45종 평균보다 높게 나올 것으로 본다(추정). Steps 의 초안 정답률 93.3%가 940행 평균 86.3%보다 7.0포인트 높다. 파일럿 실측치를 45종 전체에 그대로 옮기면 낙관적인 계획이 된다. 0절의 이유로 이 비교 자체도 간접 근거일 뿐이다.


3. ProcessFlow.Steps 오분류 축#

Steps 를 건드린 FAIL 은 25건이고, 판정문을 전수로 읽어 방향을 갈랐다.

끌어당기는 쪽이 압도적이다. 20건이 다른 라벨에서 Steps 로 와야 했고, Steps 에서 나간 것은 5건이다.

방향 건수 판정이 짚은 신호
List → Steps 10 항목이 화살표로 이어지고 앞 단계의 결과로 뒤가 성립한다
Timeline → Steps 4 날짜, 기간, 마일스톤이 없고 인과 절차가 핵심이다
Steps → List 4 항목이 독립적으로 읽히고 순서 의존성이 없다
FlowChart → Steps 1 조건, 분기, 예외 처리가 전혀 없다
Staircase → Steps 1 수준의 상승이 아니라 실행 순서다
Transformation → Steps 1 전후 두 시점이 아니라 네 단계가 흐른다
ProblemSolution → Steps 1 문제와 해결의 짝이 아니라 문제, 원인, 개입의 3단계다
DialogueChat → Steps 1 말풍선이 보조 요소이고 5단계 진행이 주다
Versus → Steps 1 두 대상의 대조가 부수적이고 절차가 주다
Steps → ProblemSolution 1 네 블록이 독립적인 분석 영역이다

List 축이 25건 중 14건이다. 어느 쪽으로도 흐르고, 양쪽 다 굵다. Timeline 축이 4건으로 다음이다. capability.ts 의 ProcessFlow_Steps 의 fallback 이 ['TimelineSchedule_Timeline', 'Enumeration_List'] 이므로, 판정 데이터의 굵기 순서와 규약의 대안 순서가 어긋나지 않는다.

3.1 판정이 실제로 쓴 판별 기준#

판정문 25건이 같은 문장 구조를 반복한다. 인접 카테고리마다 배제 근거가 하나씩 정해져 있다.

무엇과 가르나 판정이 본 것
List 순서 의존성. "앞 단계의 결과로 이어져야만 의미가 성립"하는가, "각 항목이 독립적으로 읽히"는가
Timeline 날짜, 기간, 마일스톤의 유무. 없으면 시간축이 아니다
Staircase 수준이나 성숙도가 올라가는가, 실행 순서가 옆으로 흐르는가
FlowChart 조건 분기의 유무. 없으면 플로우차트가 아니다
ProblemSolution 문제와 해결이 짝을 이루는가, 단계가 셋 이상 흐르는가
DialogueChat 발화자가 주인가 보조인가

인용을 하나 남긴다. Timeline 에서 Steps 로 넘어간 자리의 근거가 이 문장에 가장 또렷하다.

"세 카드는 시간 축보다 사건의 인과·절차 연결이 핵심이므로, Timeline 라벨로 설명하려면 슬라이드의 주된 관계 구조를 억지로 해석해야 한다."

3.2 난이도 슬롯에 어떻게 배분하나#

설계 4절의 배분은 easy 6, ambiguous 3, boundary 1 이다. 인접이 여섯인데 슬롯이 넷뿐이므로 굵기 순서를 따른다.

슬롯 무엇을 시험하나 근거
boundary 1건 List 와의 경계. 순서 의존성이 있는지 없는지가 한 낱말에 달린 자리 25건 중 14건이 이 축이다
ambiguous 3건 Timeline, Staircase, ProblemSolution 각 1건 다음으로 굵은 축 셋이다
easy 6건 순서 의존성이 문장 의미에서 분명하고 인접 신호가 없는 것

FlowChart, DialogueChat, Versus 축은 파일럿에서 시험하지 않는다. 각 1건이고, Steps 쪽에서 그 셋으로 나간 사례가 하나도 없다. 그 셋은 자기 종의 10건에서 반대 방향으로 시험하는 것이 맞다.


4. ProcessFlow.Steps 실유저 input 75건의 형태#

설계 10절이 이 종을 파일럿으로 고른 이유가 "제작한 input 과 실유저 input 을 나란히 놓고 볼 수 있는 유일한 규모"였다. 그 비교의 기준선이 아래 통계다.

항목 값
글자수 최소, p25, 중위, p75, 최대 8, 46, 78, 119, 484
50자 미만 21 / 75
200자 이상 8 / 75
- 나 * 불릿으로 시작 45 / 75
명시적 순서 표기 (1단계, Stage, ①) 4 / 75
화살표 (→, ⇒) 9 / 75
한국어가 없는 것 2 / 75 (일본어)
마크다운 헤딩 (#) 3 / 75

4.1 제작 input 이 지켜야 할 것#

순서를 표기로 드러내지 않는다. 75건 중 71건이 1단계, ①, STEP 같은 표기를 쓰지 않고, 순서가 문장 의미에만 있다. 정본 형태로 1단계: 를 쓴 것은 한 건뿐이다. 제작 input 이 순서를 표기로 적으면 정답이 표기에서 바로 읽히고, 통과율이 실사용보다 높게 나온다. 설계 5절의 '정답 누설' 게이트가 막아야 하는 것이 이 자리다.

짧게 쓴다. 중위 78자다. 제작 input 을 200자 넘게 쓰면 실사용 상위 11%의 길이가 된다. 길이 배분을 실측 분포에 맞춘다.

불릿 나열을 기본형으로 둔다. 60%가 - 불릿이다. 산문 단락은 소수다.

표기가 거친 것을 한둘 남기되, 판정 불가한 것은 남기지 않는다. 30자 미만이 12건이고 그 안에 *치료계획 확인, (2) 추론하며 듣기 처럼 앞머리가 잘려 절차인지 알 수 없는 조각이 있다. 설계 8절이 "전량 합성"의 약점으로 지적한 "실유저 입력의 지저분함을 잃는다"가 여기서 확인된다. 다만 골든셋의 input 은 정답이 하나로 읽히는 것이어야 한다(설계 4절). 그래서 거칠게 남길 것은 표기이고, 내용이 판정 불가한 조각은 남기지 않는다.


5. 나머지 11종의 오분류 축#

Steps 를 뺀 FAIL 을 라벨별로 묶고, 표본이 3건 이상인 11종의 판정문을 전수로 읽었다. 84건이다. 나머지 44종의 슬롯을 설계할 때 여기서 시작한다.

라벨 FAIL 판정이 지목한 것 판정이 쓴 판별 기준
Bar Chart 10 ListingTable 4 · Gauge/DonutChart 3 · BigNumber 3 막대 형태의 유무
Quote / Testimonial 9 List 6 · HighlightedMessage/Opening 3 인용이 본체인가 도입 장치인가
Comparison Table 9 ListingTable 8 우열이나 선택 판단이 목적인가
List 9 ReasoningStructure 계열 5 · Versus 1 · BigNumber 1 · Quote 1 · ProcessFlow 1 항목이 화살표로 인과 연결되는가
Problem-Solution 6 List 5 · HighlightedMessage 1 해결 쪽이 이름을 갖는가
Gauge 6 BigNumber 6 기준선이나 목표치가 있는가
Profile Card 5 List 5 대상이 사람인가, 기관이나 유형인가
Transformation 5 List 2 · HighlightedMessage 2 · 복합 1 같은 대상의 전후가 대응되는가
Listing Table 5 BigNumber/Gauge 3 · ProcessFlow 1 · ProblemSolution 1 행이 항목이고 열이 부가 정보인가
Timeline 3 List 2 · Steps 1 날짜, 기간, 마일스톤이 있는가
Venn Diagram 3 Versus 3 교집합인가 좌우 대조인가

Enumeration.List 가 여기서도 지배적이다. 11종 중 7종의 지배 축이 List 다. Gauge 는 예외로 BigNumber 하나에만 붙고, Comparison Table 은 ListingTable 하나에만 붙는다. 축이 하나뿐인 종은 boundary 슬롯을 그 하나에 쓰면 되고, 축이 셋인 Bar Chart 나 List 는 ambiguous 셋을 나눠 써야 한다.

5.1 차트 계열의 판정은 이 골든셋에 쓸 수 없다#

Bar Chart FAIL 10건은 전부 시각 형태를 근거로 삼는다. "막대 형태의 시각 요소가 전혀 없고", "반원형 게이지(스피도미터) 두 개로", "도넛형 게이지 차트로 표현하고 있으며" 같은 문장이 판정의 중심이다.

input_text 에는 막대도 게이지도 도넛도 없다. 그러니 이 10건의 판정 기준을 골든셋의 축으로 옮길 수 없다. 골든셋은 다른 기준으로 판정해야 하고, 그 기준은 생성기 프롬프트가 이미 갖고 있다. prompts/system.md 는 데이터의 성격으로 가른다. "속성이 하나뿐이고 그 수치의 크기가 핵심이다" 면 BarChart 이고, "항목이 전체 중의 비중이고 합이 100 에 가깝다" 면 ProportionComposition 계열이다.

차트 계열 11종(BarChart, LineAreaChart, ComboChart, PieChart, DonutChart, StackedBarChart, PictogramChart, TreeMap, RadarChart, BubbleChart, Gauge)의 슬롯 설계는 초안이 아니라 프롬프트의 판단 지침에서 뽑아야 한다. 초안에서 가져올 것은 축의 이름뿐이고 판별 기준은 가져오면 안 된다.

5.2 표 계열에서 판정과 규약이 어긋난다#

Comparison Table FAIL 9건 중 8건이 ListingTable 을 지목하는데, 그 판별 기준이 생성기 프롬프트와 다르다.

무엇이 어떻게 가르나
초안 판정자 목적으로 가른다. "여러 대상을 동일 평가 기준으로 우열이나 선택을 판단" 하는 것이 ComparisonTable 이고, "각 항목의 부가 정보를 정리" 하는 것이 ListingTable 이다
prompts/system.md 형태로 가른다. "대상이 둘 이상이고 같은 속성을 나란히 채운다" 면 ComparisonTable 이고 "속성이 2개 이상" 이어야 한다. "한 대상의 여러 속성을 행으로 늘어놓은 것" 이 ListingTable 이다

같은 슬라이드가 두 기준에서 다르게 나온다. 초안에 그 예가 있다. 물리, 화학, 생물, 지학 네 과목을 행으로 두고 공통 전략, 추가 학습 자료, 심화 학습, 복습이라는 네 열을 채운 표다. 프롬프트 기준으로는 여러 대상에 같은 속성이 둘 이상이므로 ComparisonTable 이다. 판정자는 "핵심 목적은 과목 간 우열이나 차이 판단이 아니" 라는 이유로 ListingTable 이라고 적었다.

골든셋의 정답은 규약을 따라야 한다. 초안 판정을 그대로 축 근거로 쓰면 규약과 싸우는 정답지가 되고, 그 정답지로 잰 수치는 생성기가 규약을 지킬수록 낮아진다. AttributeComparison.ComparisonTable 과 Enumeration.ListingTable 20건은 프롬프트의 형태 기준으로 만들고, 어긋나는 자리를 규약 소유자에게 확인받는다.

5.3 형태 기준이 Enumeration.ListingTable 을 거의 닫는다#

5.2절의 충돌을 프롬프트 쪽에서만 보면 문제가 더 크다. 프롬프트의 형태 기준은 두 줄이다.

"한 대상의 여러 속성을 행으로 늘어놓은 것 → Enumeration.ListingTable" "대상이 둘 이상이고 같은 속성을 나란히 채운다 → AttributeComparison.ComparisonTable. 속성이 2개 이상이어야 한다"

둘을 합치면 ListingTable 은 대상이 하나일 때만 남는다. 대상이 둘이고 속성이 둘이면 그때부터 ComparisonTable 이다. 그런데 실사용 표는 대상이 여럿인 것이 보통이다. 초안에서 Listing Table 이 60건으로 세 번째로 많았던 것이 그 증거다.

제작 input 이 그것을 그대로 보여 준다. Enumeration_ListingTable 슬롯에 만든 열 건 중 대부분이 대상 여럿에 속성 여럿이다. 예를 들어 공정, 설비, 점검 주기 세 열에 절단, 조립, 포장 세 행이 있는 표다. 형태 기준으로는 ComparisonTable 이고 목적 기준으로는 ListingTable 이다.

→ 규약 소유자에게 물을 것이 하나 늘었다. ListingTable 과 ComparisonTable 의 경계를 형태로 둘지 목적으로 둘지다. 지금 프롬프트는 형태로 두고 있고, 그러면 ListingTable 의 실사용 빈도(초안 3위)와 규약상 도달 가능성이 어긋난다.


6. PageNavigation 위험의 데이터 근거#

설계 9절이 PageNavigation 4종의 성립 조건이 미정이라는 것을 위험으로 적었다. 초안 데이터가 그 위험을 이미 보여 준다.

라벨 행 PASS 정답률
Section Divider(간지) 24 10 41.7%
Ending(엔딩) 3 3 100%
Opening(오프닝) 3 2 66.7%
TableOfContents(목차) 0 0 표본 없음

Section Divider 는 어휘 안에서 정답률이 가장 낮다. 표본이 24건이라 우연으로 보기 어렵다.

FAIL 14건의 판정문을 읽어 보면 방향이 하나다. 전부 "실질 콘텐츠가 있으므로 간지가 아니다"이고, 대신 무엇인지는 지목하지 않는다. 즉 라벨이 과잉 적용됐다. *페이지 내 인물 동영상은 샘플 동영상 입니다. 같은 안내 문구와 - 국가 온실가스 감축 목표(NDC) 달성 필요 같은 주장 슬라이드가 둘 다 Section Divider 로 붙어 있었다.

이것이 뜻하는 바는 하나다. PageNavigation 40건은 human_status: pending_spec 으로 두는 것이 맞다(설계 9절). 성립 조건이 정해지기 전에 정답을 확정하면, 규약 소유자가 뒤집었을 때 40건을 다시 만들어야 한다. 초안에서 이미 판정자가 "간지가 무엇인가"에 합의하지 못했다.

↑ 목차로 · 6부 머리로

41장골든셋 제작에서 배운 것 참고프롬프트 작법

원문 reference/golden-set-production-notes.md GitHub 에서 보기 7 KB


title: 골든셋 제작에서 배운 것 description: MOR-2165 로 450행을 만들며 아홉 판을 돌리는 동안 드러난 것. 프롬프트 작법 결함 여덟 종, input 제작에서 반복한 실수, 정본을 먼저 보지 않아 치른 비용. 설계는 golden-set-blueprint.md 가 담고 이 문서는 과정만 담는다.#

골든셋 제작에서 배운 것#

버전: 1.0 · 작성일: 2026-08-26 · 상태: Active · 구현 위치: 없음 (기록 문서)

이 문서의 역할 plan/golden-set-blueprint.md 에서 갈라 나왔다. 그 문서가 942줄로 부풀었고 절반이 설계가 아니라 제작 과정이었다. 설계와 남은 미결은 그쪽에 있고, 여기는 다음에 같은 일을 할 사람이 되풀이하지 않아야 할 것만 담는다.

교훈이 넷이다. 셋은 프롬프트와 input 을 쓰는 법이고, 하나는 그 셋을 다 덜 하게 만드는 것이다.


1. 정본을 먼저 본다#

이것이 가장 크다. 나머지 셋의 상당 부분이 이것 하나로 줄었다.

아홉 판, 450콜 × 다섯 회를 써서 판별 기준을 프롬프트로 역산했다. 그중 상당수가 단어장 정본 시트에 한 줄로 적혀 있었다.

역산으로 알아낸 것 시트에 이미 있던 문장
NestedScope 는 트리와 표현으로만 갈린다 "중심에서 바깥으로 퍼져나가는 동심원 계층 구조… TAM/SAM/SOM"
이웃 종의 신호를 빼야 한다 (4절) 01_분류 기준 탭에 negative signals 열이 있다
PageNavigation 판정 기준 SectionDivider 는 "콘텐츠 없이… 유일한 목적", Ending 은 "별도 콘텐츠 없이"
ListingTable 에 닿기 어렵다 우리 프롬프트가 이 종을 "한 대상의 여러 속성" 으로 좁힌 것이 원인

repo 사본이 정본의 전부가 아니다. 사본 스냅샷은 2026-08-17 이었고 원본 최종 수정은 2026-08-25 08:41 UTC 였다. 여드레 사이에 레이아웃 텍사노미 신규 탭에 신규 정의 26종이 들어왔다. 사본이 언제 뜬 것인지는 사본 머리말에 적혀 있다. 막힌 자리를 보면 그것부터 읽는다.

표 둘의 축이 뒤집혀 있었다. "형태냐 목적이냐" 로 다투던 것의 답이 축이었다.

종 시트의 축
Enumeration.ListingTable 행 = 항목, 열 = 그 항목의 속성. 목록화·인덱싱이 목적
AttributeComparison.ComparisonTable 행 = 지표, 열 = 대상. 같은 지표로 여러 대상을 견주는 것이 목적
AttributeComparison.Versus 중앙 기점 좌우 대칭. 세부 속성보다 대결 구도 자체

둘은 전치 관계이고 규약의 dataset.matrix 가 "첫 열이 범주 축" 이라 그대로 구현된다.

전사할 때 계층은 우리 어휘를 따른다. 시트가 Q&A Card 를 Utterance 대분류 아래 두는데 규약에서는 QnACard 가 별도 대분류다. Utterance.QnACard 라고 적었다가 prompt.test.ts 의 "어휘 밖 카테고리 이름을 지어내지 않는다" 가 잡았다.

2. 프롬프트 작법: 값을 적었다고 모양이 전해지지 않는다#

프롬프트 결함을 여덟 찾았고 셋 중 하나의 유형에 다 든다.

유형 A: 값은 적었고 모양은 안 적었다 (여섯)#

결함 프롬프트에 있던 것 없던 것 모델이 쓴 것
dataset 이중 포장 shape, value 라는 값 한 겹으로 받는다는 것 dataset: { measure: {…} }
thresholds 원소 thresholds 라는 키 이름 원소가 { at, label?, state? } 객체라는 것 thresholds: [90]
relations 키 이름 intersects, compares 라는 값 키 이름이 type·desc 라는 것 키를 지어냈다
PortraitProfile.Profile — 어휘의 정확한 이름 (ProfileCard) 어휘 밖 이름
dataset 의 자리 dataset: {…} 조각 그것이 structure 의 키라는 것 structure 의 형제로 적었다
emphasis 의 타입 emphasis(강조) 라는 이름 참/거짓이라는 것 emphasis: strong

뒤의 둘은 자기 자리가 정해져 있다. ## entity 와 ## dataset 과 ## relations 가 나란한 제목이라 셋이 같은 층으로 읽히는데 스키마에서 dataset 은 structure 의 키다. 프롬프트의 모든 예시가 structure: 아래에 entities: 만 보여 주고 ## dataset 절은 부모 없는 조각만 보여 줬다. 그래서 어긋난 자리가 정확히 Gauge 와 BigNumber 였다. 우연이 아니다.

emphasis 는 entity 필드를 한 줄에 나열한 자리에 있었다. 그 줄의 다른 넷은 문자열이고 weight 는 수, emphasis 는 참/거짓이다. 타입이 셋인데 한 줄에 나열했으니 모델이 낱말을 넣었다.

유형 B: 구조를 서술 순서로 적었다 (하나)#

프롬프트가 ScopeCoverage.NestedScope 를 "안쪽에서 바깥쪽으로 넓어진다" 로 적었다. 그런데 "A 안에 B, 그 안에 C" 와 "C 를 B 가 감싸고 그것을 A 가 감싼다" 는 같은 구조이고 적는 순서만 다르다. 실패 5건 중 4건이 바깥에서 안으로 적힌 글이었고 통과 5건 중 4건이 안에서 바깥으로 적힌 글이었다.

같이 빠져 있던 것이 진짜 배타 조건이다. HierarchicalTree 는 한 상위가 하위 여럿을 거느려 가지가 갈라지고 NestedScope 는 단일 사슬이다. 순서 대신 갈라짐으로 판별하게 고쳤다.

유형 C: 규칙을 한 자리에만 걸었다 (하나)#

규칙은 있었다. "값에 콜론이 있으면 값 전체를 겹따옴표로 감싼다". 그런데 뒤에 "제목에서 자주 틀린다" 가 붙고 예시가 title 뿐이었다. 모델은 items 원소에 적용하지 않아 - 예: 상담 연결 을 썼고 그것은 객체 {예: "상담 연결"} 로 파싱된다. 목록 원소 예시를 보탰다.

여덟 건 전부 prompt.test.ts 의 REQUIRED_RULES 에 못박았다. 그 목록의 취지가 "실측에서 실패로 확인된 규칙" 이다.

일부러 하지 않은 것 하나. entitySchema 의 필드 이름 전량이 프롬프트에 나오는지 코드가 대조하는 테스트를 만들면 이 결함 종류가 자동으로 잡힌다. 그러려면 z.lazy 를 벗겨 _def.getter().shape 에 닿아야 하는데, zod 내부에 기대는 테스트는 zod 판이 오르면 깨진다. 여덟을 문자열로 못박는 값이 그 취약함보다 크다고 봤다.

3. 긍정 정의를 배타 규칙으로 옮기지 않는다#

같은 실수를 두 번 했다.

언제 무엇 대가
첫 번째 정본에 없는 조건을 지어냈다 — Pyramid 에 "층마다 비중이 있고" 3 → 0
두 번째 정본의 긍정 정의를 배타 규칙으로 바꿨다 — Calendar 에 "진행 순서로 이어지면 Timeline 이다" 9 → 4, 총합 384 → 370

시트의 Calendar 문구는 "날짜를 격자 형태로 배치하고… 특정 기간의 일정 분포를 한눈에" 였다. 여기까지 옮기는 것은 맞았다. 내가 한 문장을 더 붙인 것이 문제였고, 콘텐츠 캘린더든 생산 일정이든 실제 월간 일정에는 대개 진행 순서가 있어서 그 문장이 거의 모든 날짜 목록을 Timeline 으로 밀어냈다.

규칙. 정본이 "이것이다" 라고 말한 것을 "저것이 아니다" 로 옮기지 않는다. 긍정 정의는 그 종이 성립하는 조건이고 배타 규칙은 이웃 종이 성립하지 않는 조건이다. 이웃을 배제하려면 정본이 그 배제를 적어 둔 자리를 찾는다. 시트의 SectionDivider 가 "TableOfContents 와 달리 전체 구조가 아닌 단일 섹션만" 이라고 적은 것이 그런 자리이고, 그것은 옮겨도 된다.

되돌릴 때도 시트 문구는 남겼다. 격자와 분포는 정본의 말이므로 지울 이유가 없고 내가 붙인 배타 조건만 뺐다.

판단 지침을 더하는 일에는 딸린 값이 있다. 지침에 걸리는 input 이 함께 드러난다. Timeline 세 건이 지침이 없던 동안 통과하고 있었지만 그것은 input 이 옳아서가 아니라 가를 기준이 없어서였다.

4. input 제작: 겨냥한 신호를 넣고 이웃의 신호를 뺀다#

실패를 볼 때 생성기를 먼저 의심하면 안 된다. crafted 실패 17건을 정독했더니 13건이 내 input 의 문제였고 생성기 판단이 옳았다.

앞의 것(겨냥한 신호를 넣는 것)만 신경 쓰다 뒤의 것을 놓쳤고, 내가 넣은 낱말이 그대로 되돌아왔다.

행 내가 넣은 것 나온 것
CombinedResult-06 "세 가지가 겹쳐야" VennDiagram. 겹침은 교집합의 낱말이다
HighlightedMessage-09 "자정에서 여섯 시로 바뀝니다" Transformation. 전후 변화를 넣었다
Versus-07 "구형 공정 / 개선 공정" Transformation. 낱말 자체가 전후다
Steps-09 "오류가 보고되면" FlowChart. v7 이 분기를 이 종의 핵심이라 한다
ProblemSolution-07 "종이 계약서를 전자서명으로 바꿔" Transformation 의 정의가 "동일 대상의 개선 전후" 다
ComboChart-08 "재료비 42%, 인건비 33억원" 서로 다른 두 대상이라 표다
Transformation-02 "62%에서 91%로 올랐다" 한 문장에 수치 하나라 BigNumber 다

쌍둥이 행을 세 번 지나쳤다. Transformation 의 "지표 둘에 전후를 붙이면 표가 된다" 를 -02 에서 배웠는데 -06, -10, -07, -08 을 네 판에 걸쳐 하나씩 발견했다. 한 행의 원인을 알았으면 같은 종의 나머지 아홉을 그 자리에서 함께 봐야 한다.

LongFormTextLayout 을 세 판에 걸쳐 세 번 다르게 진단했다.

판 진단 결과
2차 길이만 옮기고 "하나의 주제" 를 빠뜨렸다 7 중 2
3차 주장하는 글을 썼다. 서술로 바꾼다 5 중 1
4차 이 종은 정의 자체가 표현 형식이다 맞았다

통과와 실패를 맞대 보면 답이 데이터에 있었다. 통과한 행이 "따로따로 적기보다 한 번에 읽히는 글로" 로 형식을 스스로 말하고, 실패한 7건은 전부 형식 언급이 없는 정보 전달 글이었다. v7 정본이 이유를 말한다. "정보 단위를 독립 항목으로 분리해 병렬 나열하기보다, 장문의 글로 시각화한 것이 핵심". 이 정의는 내용이 아니라 형식을 말한다.

5. 도구에서 겪은 것#

같은 종류의 사고를 세 번 냈다. 셋 다 "유도되는 값을 이어받거나 걸러야 하는 자리에 그 조건이 없었다" 는 한 모양이다.

무엇 대가
--init 이 요약을 지웠다 요약을 다시 채웠다
초안 입력을 거둘 때 P/F 를 빠뜨렸다 거부된 라벨 42행이 들어왔고 그중 4행만 통과했다
--init 이 정답을 지웠다 444행을 다시 태웠다. 한 판에 450콜을 두 번

부분 재생성이 측정을 가능하게 했다. --init 이 input_text 와 프롬프트 해시가 모두 그대로일 때만 정답을 이어받게 고치자 판을 읽을 수 있게 됐다.

판 다시 만든 행 통과 읽을 수 있나
3~5차 450 355 → 365 → 364 아니오
6차 8 370 예. 8건 중 6건이 들었다
7차 1 372 예

생성기에 temperature 나 seed 설정이 없다. API 기본값이라 비결정적이고, 450행을 매번 새로 만들면 내 수정과 생성기의 잡음이 섞인다. 3~5차의 355 → 365 → 364 가 그것이다. 종 단위의 큰 변화는 신호였지만 총합 ±10 은 판마다 그냥 흔들린다. 채점기 설계가 이것을 알고 있어야 한다 (blueprint 9절).

행 단위로 고치는 것이 종 단위로 다시 만드는 것보다 낫다. 종 단위로 input 을 다시 만들면 좋아지는 종과 나빠지는 종이 함께 나온다. CombinedResult 는 0에서 8로 올랐지만 LongFormTextLayout 은 5, 3, 0 으로 내려갔다. 열 건 중 여덟이 이미 통과한 종을 다시 만들면 통과한 것까지 바뀐다.

제작 스크립트는 지금 없다. scripts/visual-summary-golden-set.ts 와 generator/golden-sets/ 는 MOR-2079 에서 걷어냈다. 이 절은 다시 만들 사람을 위한 기록이다. 판정 로직만 generator/lint.ts 로 남았다.

Checklist#

  • 프롬프트 결함 여덟 종을 prompt.test.ts 의 REQUIRED_RULES 에 못박음 (2026-08-25)
  • 정본 시트 문구를 프롬프트 판단 지침과 repo 사본 v7 에 반영 (2026-08-25)
  • 누설 판정의 DIRECTIVE_TERMS 보강. PictogramChart 의 앞선 통과가 잡히지 않은 표현 지시에 기댔다. "낱개", "칸", "격자" 계열을 더할지는 그 낱말이 일상 한국어에서 얼마나 흔한지를 재고 정한다

Changelog#

날짜 버전 변경 근거
2026-08-26 1.0 golden-set-blueprint.md 9.3~9.13 을 갈라 옮기고 교훈 다섯으로 묶음. 설계와 과정이 한 문서에 섞여 942줄이 됐고 절반이 과정이었다 황현진: "문서를 좀 짧게 해야해 너무 길어"

↑ 목차로 · 6부 머리로

42장iui.comp_frames 10만 건 IIS 재적재 참고반영 누락과 복구 기록

원문 reference/iui-comp-frames-100k-iis-pool-reload.md GitHub 에서 보기 5 KB 원문 제목 「iui.comp_frames 10만 건 IIS pool 반영 누락과 재적재」

오너 정원석 · 작성일 2026-09-13 · 최종 갱신 2026-09-13

요약#

2026-09-13, 허재영이 I.I.S 10만 건 추출을 완료하고 iui.text_capacity에 100,025개 design_object를 upsert했다(Slack #mordor스쿼드(C091C6JFYH0), 2026-09-13 스레드). 그러나 IUI production의 /comp-frame 검색이 실제로 읽는 테이블은 iui.text_capacity가 아니라 iui.comp_frames이고, 이 테이블은 별도의 오프라인 빌드 스크립트(scripts/build-comp-frames.ts, miridih/miricanvas-iui 저장소)로만 갱신된다. 확인 시점 기준 iui.comp_frames에는 기존 10,058개 design_object만 있었고, 신규 90,000여 개는 반영되지 않은 상태였다.

scripts/build-comp-frames.ts --iis --fresh를 production에 대해 실행해 iui.comp_frames를 100,025개 design_object 전량으로 재구성했다. 이어서 scripts/comp-frame/backfill-gc-passed.ts를 재실행해 GC 게이트 통과 플래그(gc_passed)를 복원했다.

배경#

  • iui.text_capacity는 텍스트 용량(글자 수, 줄 수, 폰트 크기 등) 모델의 원천 테이블이다. IIS 추출본이 upsert되는 곳이 이 테이블이다.
  • iui.comp_frames는 /comp-frame 검색이 실제로 조회하는 후보 pool이다. scripts/build-comp-frames.ts가 public.iis_semantic_component와 iui.text_capacity를 조인해서 만든다.
  • 두 테이블은 독립적이다. iui.text_capacity에 새 design_object가 들어와도 iui.comp_frames는 자동으로 갱신되지 않는다. scripts/build-comp-frames.ts --iis(신규 반영) 또는 --iis --fresh(전량 재구성)를 별도로 실행해야 한다.
  • 2026-09-13 확인 시점, public.iis_semantic_component와 iui.text_capacity에는 100,025개 design_object가 있었으나 iui.comp_frames에는 10,058개만 있었다. --from-existing 모드(기존 저장된 design_object만 재빌드)만 최근 실행됐고, 신규 design_object를 추가하는 --iis 전체 실행은 없었던 것으로 보인다.

무엇을 했나#

1. 백업#

production iui.comp_frames(기존 11,663 rows)를 --fresh 실행 전 iui.comp_frames_bak_20260913_pre_iis_fresh로 복제했다. --fresh는 트렁케이트 후 재적재하므로, 실패 시 복구 경로로 남겨 두었다.

이 백업 테이블은 지금 지우지 않는다. 재적재 결과가 일정 기간 안정적으로 쓰인 것이 확인된 뒤 삭제 여부를 판단한다. 삭제하기 전에는 이 문서의 "재적재 결과" 절과 대조해 row 수, gc_passed 분포가 어긋나지 않는지 먼저 확인한다.

2. --iis --fresh 실행#

tsx scripts/build-comp-frames.ts --iis --fresh
  • 2026-09-13 20:57 시작, 21:24 완료. 소요 1,606,619ms(약 26.8분).
  • 결과: iui.comp_frames 112,227 rows, distinct design_object_id 100,025개.
  • font-hierarchy(경계 안 Text 노드 중 iui.text_capacity에서 폰트 크기를 찾은 비율) 106,942/112,227(95.3%) measurable. 정상 범위로 판단했다.

3. 속도 개선을 위한 임시 코드 수정 (커밋하지 않음, 실행 후 원복)#

원격(로컬 환경에서 production DB로 네트워크 접속)에서 실행할 때는 각 페이지의 왕복 지연이 누적되어, 로컬 벤치마크(2026-08-10, 46.7초/11,284행)보다 훨씬 오래 걸렸다. 왕복 횟수를 줄이기 위해 miricanvas-iui 저장소의 청크 크기 상수 넷을 실행 시간 동안만 올렸다.

파일 상수 원래값 임시값
scripts/build-comp-frames.ts DO_PAGE_SIZE 200 1000
scripts/build-comp-frames.ts UPSERT_BATCH_SIZE 100 300
scripts/comp-frame/lib/designObjects.ts fetchDesignObjects chunkSize 50 150
scripts/comp-frame/lib/textCapacity.ts fetchTextCapacityByIds chunkSize 50 300

실행 완료 후 네 값을 전부 원래대로 되돌렸다. 저장소에 커밋된 적이 없으므로 git diff에 흔적이 남지 않는다. production을 이 저장소가 아닌 환경(예: 해당 EC2 로컬)에서 다시 실행한다면, 왕복 지연이 없으므로 이 수정은 필요하지 않다.

4. gc_passed 재적재#

--fresh는 iui.comp_frames를 트렁케이트하므로 gc_passed 컬럼이 전부 false로 리셋된다. scripts/build-comp-frames.ts의 빌드 로직은 이 컬럼을 채우지 않는다. scripts/comp-frame/backfill-gc-passed.ts 주석이 "--fresh 이후 반드시 재실행"이라고 이미 명시하고 있다.

tsx scripts/comp-frame/backfill-gc-passed.ts --table comp_frames
  • CSV scripts/comp-frame/data/gc_passed_do_ids_dv2.4_GCv1.0.csv(203,024개 design_object_id, GC v1.0 게이트를 통과한 목록)를 읽어 해당 id만 gc_passed=true로 갱신한다.
  • 실행 결과, gc_passed=true 81,479개 design_object(100,025개 중 81.5%). 이전(--fresh 실행 전) 기준 9,291개에서 크게 늘었다.
  • 나머지 18,546개는 이 CSV에 없는 design_object다. GC v1.0 게이트가 아직 채점하지 않았거나 통과하지 못한 것으로 보인다. 이 CSV를 언제 다시 갱신하는지는 확인하지 못했다.

재적재 결과 (검증)#

항목 값
iui.comp_frames distinct design_object_id 100,025
iui.comp_frames rows 112,227
gc_passed = true 81,479 design_object(81.5%)
gc_passed = false 18,546 design_object(18.5%)
SWOT 프리셋(RelationalComparison_SWOT) 검색 후보 131 rows (재적재 전 0)

재적재 과정에서 발견한 것, 이번 작업 범위 밖#

이번 작업 도중 production 환경 접근 과정에서 이번 작업과 무관한 문제 둘을 발견했다. 확인만 했고 조치는 하지 않았다.

  1. production Postgres(10.122.9.74:54322)에 기본 계정과 기본 비밀번호로 외부에서 직접 접속된다.
  2. 이 인스턴스의 JWT_SECRET이 Supabase CLI 기본값(super-secret-jwt-token-with-at-least-32-characters-long)이다. 공개된 값이므로 이 값을 아는 사람은 누구나 유효한 service_role JWT를 만들 수 있다.

두 항목 모두 담당자 확인과 별도 조치가 필요하다.

뒷정리 상태#

  • .env.local(로컬 작업용, production URL과 service_role JWT 포함) 삭제함.
  • 위 표의 임시 청크 크기 수정 네 곳 전부 원복함.
  • iui.comp_frames_bak_20260913_pre_iis_fresh는 유지한다. 삭제 판단 기준은 위 "백업" 절 참고.

참고#

↑ 목차로 · 6부 머리로

43장L1/L2 전시 정책 · 결정 vs 구현 갭 참고2026-09-25 조사

원문 reference/iui-l1-l2-display-decision-gap-2026-09-25.md GitHub 에서 보기 4 KB 원문 제목 「iui — L1/L2 전시 정책, 결정 vs 구현 갭 조사 (2026-09-25)」

오너 정원석 · 작성일 2026-09-25 · 최종 갱신 2026-09-25

miricanvas-iui(PoC 검증 사이트) 저장소의 실물(PR diff·Jira 코멘트·배포 이력)을 근거로 "L1/L2 결과를 순수 코스트 순으로 섞어 보여준다"는 결정이 실제로 어디까지 구현됐는지를 코드 레벨에서 확인한 조사 기록이다. 결론은 ../plan/l1-l2-result-display-blueprint.md (v0.5, L2 조합 펼치기)로 이어졌다 — 이 문서는 그 결론에 이르기까지의 근거만 담고, 실행 계획은 담지 않는다.

0. 결론 먼저#

2026-09-22 Slack 결정(C09N92LC7S9/p1790057360079629) 중 정렬 순서와 L2 점수 계산식 두 가지는 MOR-2519로 티켓화되어 구현·배포까지 끝났다(Done, miricanvas-iui PR #1183). 그러나 L2 문서 하나가 후보 조합 1개(슬롯별 top-1)로만 랭킹에 들어가는 규칙 (TS documentSearch.ts:519-520, Rust document_merge.rs:452)은 바뀌지 않았다. 그래서 "L1, L2 상관없이 코스트 순"이 화면에서 성립하지 않는다. 2026-09-25 실측에서 L2 후보가 3,253개였는데 목록에 L2는 1등 1개뿐이었다. 카드 레이아웃(groups=slide/ expand)은 원인이 아니다.

1. 결정 원문#

최예지A(2026-09-22 15:09, C09N92LC7S9 스레드, thread_ts=1789696955.651419):

L1,L2 전시 방식 1번으로 결정해서 전달드립니다. (이미지 속 빨간 박스)

  • L1, L2 결과 상관없이 코스트가 낮은 순서대로 상위에 나열해서 보여준다.
  • L2 점수 계산 기준은 **'평균'**으로 적용한다. (시안에도 해당 내용 수정 반영해두었습니다.)

이 결정문은 "L2를 상단에 고정할지, L1과 코스트 하나로 섞어 정렬할지"(1안 vs 2안)에 대한 답이다. "L1, L2 결과 상관없이 코스트 순"을 문자 그대로 적용하면, 평균 cost가 낮은 L2 조합은 여러 개가 L1 사이에 섞여야 한다. 현재 구현은 L2 조합을 1개만 내보내므로 이 문장을 충족하지 못한다.

같은 스레드 안에 이 별개 질문이 실제로 제기된 적은 있다:

김수현: "L2 서머리도 하나, L2에 대한 검색 결과도 하나만 나오는 걸까요?" 홍지혜: "하나의 사용자입력에 대해서 L2 Visual Summary는 하나, L2의 검색 후보들을 조합한 대표 묶음을 하나만 노출한다가 정확합니다." 김수현: "그러면 ... L2 후보 조합 세트를 여러개 노출한다로 정책을 바꿀 수도 있는거죠?" 홍지혜: "네 그럴수도 있습니다"

이 질문("대표 묶음 1개" vs "조합 여러 개")에 대한 명시적 답은 스레드에 없다. 다만 위 "1번" 결정문과 함께 읽으면 조합 여러 개 쪽이다. 조합 개수와 유사 조합 처리는 blueprint §4.1·§4.3에서 다룬다.

2. 실제 구현 범위 (코드로 확인)#

git diff $(git merge-base 523201c1 d2d07518) d2d07518(miricanvas-iui PR #1183 전체 diff)에서 카드 레이아웃 관련 변경은 FrameGroupCard.tsx 한 곳뿐이고, 내용은 이것뿐이다:

diff
- groupScoreMode?: GroupScoreMode;   // 'sum'|'mean'|'max' 선택지 제거
- GROUP_SCORE_TOOLTIP: Record<GroupScoreMode, string> = { sum: ..., mean: ..., max: ... }
+ GROUP_SCORE_TOOLTIP = '페이지 점수 — 지금 고른 슬롯들의 벌점 평균...'  // mean 고정

.groupCard { grid-column: 1 / -1; }(펼침 레이아웃이 전체 폭을 차지하는 규칙, CompFrame.module.css:1207)는 이 PR에서 한 줄도 안 바뀌었다. 이 규칙 자체는 2026-09-09(§13-22 item 11)에 만들어진 것으로, 이번 결정보다 5일 전이다 — 즉 "상단 고정 vs 코스트 순 혼합"을 논쟁하기도 전부터 있던 오래된 코드다.

groups=slide(자동 슬라이드) 레이아웃은 이미 "L1과 같은 폭의 그리드 칸 하나"를 차지하도록 되어 있다(FrameGroupCard.tsx 모듈 doc: "one ordinary results-grid cell, the same outer size as a single FrameResultCard") — 이것도 2026-09-09에 만들어진 것으로 이번 결정과 무관하다. 카드 모양은 원인이 아니다. 원인은 카드가 몇 장 나오느냐, 곧 L2 문서당 GroupEntry가 1개로 고정된 규칙이다(§0).

3. MOR-2519 QA는 이 갭을 볼 수 있는 방법이 아니었다#

티켓 완료 코멘트(이석, 2026-09-23):

/comp-frame에서 실제 라이브로 확인 — groups=slide(L1+L2 혼합) 프리셋 검색 시 서버가 L1: {...} · L2: {...} · 검색 3회 · 항목 11개 형태로 병합·mean scoring된 결과를 정상 반환하는 것을 직접 확인함.

검증 대상이 결과 메타 텍스트 줄의 숫자(검색 횟수, 항목 수)였다 — 정렬 순서와 평균값이 맞게 나오는지를 확인하는 방법으로는 정확하지만, 목록에 L2 항목이 몇 개, 몇 등에 섞여 있는지는 애초에 그 방법으로 볼 수 없다. 메타 줄에 L2: {후보 3253}이 찍혀도 목록에는 L2가 1개만 나오는 상태가 이 QA를 통과했다.

4. 정본(mordor-wiki) 자체 확인#

projects/mordor/visual-summary/spec/schema-v0.5.md·TODO.md·DASHBOARD.md에서 L1/L2/L3는 전부 생성 시 복잡도 레벨(complexity level, 8.8절) 개념으로만 다뤄진다 — "검색 결과 화면에서 L2를 몇 장으로/어떤 카드 크기로 보여줄 것인가"에 대한 조항이 없다. 즉 이 UI 전시 방식은 애초에 정본 스펙 영역이 아니라 PoC 화면(iui) 자체의 UX 결정 사항이고, 그 결정은 Slack/Jira에만 남아 있었다 — 이번에 겪은 "결정문을 찾으려면 Slack 스레드를 처음부터 다 읽어야 하는" 어려움 자체가, 이 문서와 ../plan/l1-l2-result-display-blueprint.md를 정본에 남기는 이유다.

관련 자료#

↑ 목차로 · 6부 머리로

44장호출당 비용 실측 기록 참고2026 상반기

원문 reference/call-cost-history-2026.md GitHub 에서 보기 4 KB 원문 제목 「내용시각화 호출당 비용: 과거 실측 기록 (2026 상반기)」

이 문서의 역할 "내용시각화 한 번 호출당 얼마 드나"에 대한 팀 내 실측·추정 발화를 Slack에서 모아 시점별로 정리한다. 전량 과거 시점 데이터다. 모델 교체(Gemini 3.1 Flash-Lite → 3.5 Flash, Sonnet 버전 변경 등)와 프롬프트/파이프라인 변경이 이후에도 계속 있었으므로, 여기 숫자를 현재 원가로 그대로 쓰면 안 된다 — 참고용 이력이다. 지금 시점 정확한 원가가 필요하면 Langfuse(miricanvas-content-visualization organization, #dx개발_플랫폼_ae파트 황현진님 2026-08-12 공지) 대시보드를 직접 봐야 한다.

1절 기준 포맷 주의 1절 "Summary 생성 API" 수치는 전부 현행 Markdown Summary(../README.md 용어표의 Markdown Summary — 오너 허재영·황현진) 생성 비용이다. 이 폴더가 다루는 Visual Summary(YAML, schema v0.5)는 아직 실서비스 생성기가 없어 호출당 비용 실측 자체가 없다. Markdown→YAML 전환은 4단계(README.md 로드맵)에서 이뤄지므로, 그 전까지는 Markdown Summary 수치가 유일한 실측 참고선이지만 YAML 산출물의 토큰 길이·비용에 그대로 대입하면 안 된다 (YAML이 JSON보다 토큰이 적다는 건 확인됐지만 Markdown 대비는 별도 실측이 없다).

오너 정원석 (MORDOR) · 작성 2026-08-29


1. Markdown Summary 생성 API (현행 프로덕션, 사용자가 실제로 호출하는 경로)#

2026-03-16, 김수현님 (#프레젠테이션트라이브_내용시각화_전체, permalink). 당시 파이프라인(시멘틱 쿼리 + Summary 분리 구조) 기준 300개 테스트 평균:

항목 비용 토큰(입력/캐시/출력)
시멘틱 쿼리 1회 $0.001137 (1.68원) 1,981.63 / 344.32 / 359.58
서머리 생성 1회 $0.00232 (3.43원), Low $0.00128(1.89원) ~ High $0.00424(6.28원) —
1 서머리 + 1 시멘틱 총합 약 3.6원 ~ 8.0원 (평균 5.1원+α) —

서머리 하나에서 여러 시멘틱 쿼리가 나올 수 있어 실제 총비용은 이보다 커질 수 있다고 원문에 명시.

2026-01-07, 박지현B님 (같은 채널, permalink). 파이프라인 변경 이전 버전 테스트, 957건 raw text input 기준 Summary 중앙값:

  • 추론 속도 약 1.7초
  • 비용 $0.000559 (약 0.83원)
  • 토큰(입력/출력): 178/132

2. 내용/색상 대치 평가 (판정 모델, Gemini)#

2026-07-07, 조성우님 (#내용시각화스쿼드_스마트블록_코어, permalink). Gemini 3.1 Flash-Lite가 5월 말 deprecated되어 Gemini 3.5 Flash로 교체된 뒤 수치:

평가 항목 기존 (3.1 Flash-Lite) 현재 (3.5 Flash, 2026-07-07 시점)
내용 대치 평가 약 $0.015~0.2 약 $0.02~0.025
색상 대치 평가 — 약 $0.025~$0.03

3. AI 결과 검증 (Bad/Acceptable/Perfect 판정)#

2026-06-08, 조성우님 (#mordor스쿼드, permalink). 강창석님 질문에 대한 답변, 썸네일 1개 기준:

  • 처음 답변 $0.04(약 62원) → 정정: $0.004(약 6.2원)

4. 시멘틱 태깅 (검색 색인 구축용 배치, 실시간 호출 아님)#

2026-07-20, 황현진님 (#프레젠테이션트라이브_내용시각화_전체, permalink). 태깅 대상 변경 후 재계산:

  • Claude Sonnet 기준 건당 약 36원, 페이지 단위로 약 430만원, 컴포넌트 단위 리스트 60%가 추가로 빠지면 약 120만원 → 총 약 550만원 예상

2026-06-11, 황현진님 (같은 채널). 페이지 500개 기준 모델별 장당 가격 비교:

모델 장당 가격
Opus4.8 60원
Sonnet4.6 36원
Haiku4.5 12원
GPT5.4 36원
GPT5.4-mini 11원

2026-06-10, 김수현님 (같은 채널). 표지 뺀 페이지 전부 태깅 시 약 62,000페이지, 대략 300~400만원 예상.

2026-06-12 / 07-20, 황현진님 (#dx개발_플랫폼_ae파트). 컴포넌트 태깅 PoC로 Claude API 비용 ~$300 (6/12), 전체 태깅으로 Sonnet-5 사용 시 $700 예상 23일 소요 (7/20).

2026-07-28, 황현진님 (같은 채널). 컴포넌트 8만개 추가 태깅, 비용 ~$400 예상 (화와 수 이틀).

5. 타입 분류 + HTML 생성#

2026-06-25, 권경태님 (#프레젠테이션트라이브_스마트다이어그램, permalink). 306장 기준 타입 분류 + HTML 생성까지 41,137원 (부가세 10% 별도) → 장당 약 134원.

6. 참고: 이 이력을 볼 때 유의할 점#

  • 모델이 계속 바뀐다. Gemini 3.1 Flash-Lite → 3.5 Flash, Sonnet 버전(4.5 → 4.6 → 5) 등 교체가 2026년 내내 있었고 그때마다 단가가 달라졌다. 위 표의 "현재"는 그 시점의 현재일 뿐이다.
  • 1~3절은 실시간 호출 경로(사용자 요청 1건당 발생), 4~5절은 배치/색인 구축 비용(전체 컴포넌트 대상 1회성 또는 주기적 작업)이라 성격이 다르다. "한 번 호출당 얼마냐"는 질문에는 13절이 답이고, 45절은 총 예산 문의에 답이다.
  • Langfuse organization miricanvas-content-visualization (2026-08-12 신설, permalink)이 mordor 과 내용시각화 스쿼드 간 프롬프트 전달과 골든셋 공유 용도로 만들어졌으니, 지금 시점 정확한 원가는 여기서 trace 단위로 조회하는 편이 이 문서보다 신뢰도가 높다.

↑ 목차로 · 6부 머리로

45장이종중첩 컴포넌트 수량 산출 참고1,000건 표본 실측 재현 묶음

원문 reference/2026-08-13-이종중첩-수량-산출/README.md GitHub 에서 보기 5 KB 원문 제목 「이종중첩 컴포넌트 수량 산출 (2026-08-13)」

이 문서의 역할 TODO.md D-7 이 검색 풀 제외를 결정하며 남긴 후속 액션 "수량 파악"의 결과다. 비율·신뢰구간과 함께 어떻게 쟀는지, 비용이 얼마였는지를 적는다. 오너 황현진 (MORDOR) · 상태 확정 · 최종 갱신 2026-08-13 관련 MOR-2048 실행 · MOR-2039 상위 · ../../spec/schema-v0.5.md 8.7절 정의 · ../../TODO.md D-7

이 문서 훑기

절 무엇에 답하나
1 이종중첩이 몇 개인가
2 어떤 컴포넌트를 이종으로 판정했나
3 어떻게 뽑고 어떻게 쟀나
4 얼마가 들었나
5 이 숫자를 얼마나 믿을 수 있나
6 무엇을 재지 않았나

1. 결과#

전체 183,052개 컴포넌트에서 1,000개를 뽑아 이종 여부를 판정했다.

항목 값
표본 1,000개
표본 내 이종중첩 11개
관측 비율 1.10%
95% 신뢰구간 (Wilson) 0.62% ~ 1.96%
전체 환산 약 2,014개
환산 신뢰구간 약 1,126 ~ 3,586개

수량이 작다. 신뢰구간 상한에서도 3,586개이고, 이는 전체의 1.96% 다. D-7 이 "대부분 조합 Fallback 으로 해결되므로 검색 풀에서 제외한다"고 판단한 근거와 어긋나지 않는다. 제외로 잃는 양이 이 범위라면 대안 레버('순수 단일 type 우선 검색')를 꺼내지 않아도 결정이 성립한다.

컴포넌트당 type 개수 분포#

type 개수 건수 비율
0개 (단일 원자 항목이거나 해당 분류 없음) 70 7.0%
1개 (동종) 919 91.9%
2개 이상 (이종중첩) 11 1.1%

3개 이상은 관측되지 않았다. 다만 판정을 이진분류로 한정했으므로 종수의 상한은 이 측정으로 말할 수 없다 (6절).


2. 무엇을 셌나#

이종중첩 = 하나의 컴포넌트 바운더리 안에 서로 다른 type 이 동시에 있는 경우. type 기준은 semantic category v7 단어장 49종이다.

Slack 에서 처음 공유된 예시가 이 경우다. 두 원(VennDiagram) 양옆에 캡슐 항목이 나열된(List) 컴포넌트이며, 표본에서도 같은 조합이 잡혔다.

이종 11건의 조합#

조합 건수
BigNumber + List 4
HighlightedMessage + List 2
List + ListingTable 1
List + ProfileCard 1
List + Quadrant 1
List + VennDiagram 1
List + Steps 1

11건 모두 List 를 한쪽에 낀다. "나열 블록 + 성격이 다른 별개 블록"이 한 컴포넌트에 나란히 놓인 형태다. 건수가 적어 조합 간 많고 적음은 이 표로 판정할 수 없고, 어떤 유형이 존재하는지까지만 읽을 수 있다.


3. 방법#

항목 내용
모집단 layouts.data_version = '2.3' + v3.5 유효 컴포넌트 + layout 의 linted_rlsc_id 일치 = 183,052개
표집 layout 1,000개 무작위(seed 42) → 각 layout 에서 컴포넌트 1개 무작위
판정 기존 시멘틱 태깅 프롬프트(semantic-tag-prompt.txt)를 무수정 적용. type 이 2개 이상이면 이종
실행 Claude Code 서브에이전트 50개 (청크당 20건). 지시문은 agent-instructions.md
이미지 원본 webp 알파 마스크를 흰 배경으로 합성한 뒤 판정
통계 Wilson score 구간

표본은 앞 500건을 먼저 돌리고 1,000건으로 확장했다. 표본을 셔플해 저장했으므로 앞 500건 자체가 무작위 표본이고, 뒤 500건을 이어붙이면 표본이 그대로 커진다. 확장 전후로 점추정이 서로의 신뢰구간 안에 머물렀다 (500건 1.5%, 1,000건 1.10%).

layout 유일 추출#

같은 layout 의 컴포넌트는 구조가 닮아 클러스터를 이룬다. 컴포넌트 단위로 균등 추출하면 컴포넌트가 많이 달린 layout 을 과대 대표하고, 클러스터가 섞이면 설계효과 1.2~1.5 로 1,000건이 실질 700건대가 된다. layout 을 먼저 뽑고 각 layout 에서 1개만 취해 이를 피했다.

layout 당 컴포넌트 수로 가중하면 1.2%(약 2,281개)가 된다. 위 1.10% 는 layout 대표 컴포넌트 기준이고 가중값은 컴포넌트 단위 기준이며, 둘 다 신뢰구간이 겹친다.

프롬프트를 고치지 않은 이유#

기존 시멘틱 태깅 프롬프트의 ## Output 절이 이 판정 기준 그대로다. "2번째 이상의 태그는 1번째와 독립된 별개의 시각 구조가 한 컴포넌트 안에 실제로 함께 있을 때만 추가한다"이고, "2번째가 1번째의 배치, 구성, 변형에 불과하면 금지"한다. 태그가 2개 이상이면 곧 이종중첩이므로 판정 로직을 새로 만들 필요가 없었다. 프롬프트를 손대면 기존 태깅 결과와의 정합성이 깨지고 v7 갱신 시 sync 대상이 늘어난다.

판정 기준을 서브에이전트 지시문에 요약해 넣지도 않았다. 지시문은 무엇을 읽고 어디에 쓸지만 정하고, 에이전트가 프롬프트 원본을 직접 읽어 그것을 기준으로 삼는다. 기준을 요약하면 원본과 두 벌이 되어 한쪽만 낡을 수 있고, 특히 ## Output 절은 제약(padding 금지)과 허용(종류가 다른 구조가 여럿이면 각각 태그)이 함께 적혀 있어 요약하면 한쪽으로 기울기 쉽다.


4. 비용#

0원이다. 판정 전량을 Claude Code 서브에이전트(구독 풀)로 수행했고 Anthropic API 를 호출하지 않았다.

batch API 로 같은 일을 하면 다음과 같다. 실측 토큰(시스템 프롬프트 13,163 캐시 + 요청당 537, 출력 약 110)에 sonnet-5 배치 단가를 적용한 값이다.

대상 비용
1,000건 약 $4
전수 183,052건 약 $690 (105만 원)

5. 품질 검증#

프롬프트 성능을 별도 테스트셋으로 검증하는 절차는 생략했고, 대신 판정 결과를 육안으로 확인했다.

  • 이종 판정 전수 확인 → 위양성 0건. 11건 모두 두 구조가 시각적으로 분리돼 있었다.
    • 좌측 원 안 대형 수치 + 우측 3개 항목 나열. Slack 예시와 구조가 같다
    • 상단 라벨-값 4개 나열 + 하단 헤더 행과 격자선이 있는 표. List 와 ListingTable 의 경계 규칙을 그대로 적용했다
    • 3열 각각의 큰 숫자 + 하단 상세정보 박스. 숫자 반복은 BigNumber 하나로 접고 별개 축인 상세 박스만 두 번째 태그로 냈다
  • 비이종 판정 중 무작위 30건 확인: 놓친 것이 눈에 띄지 않았다
  • 린터 drop 0건: 판정 라벨이 전부 Taxonomy v7 49종 안에 있었고 표기 교정도 0건이었다
  • 중복 0: design_object_id, layout_id 모두 중복이 없어 layout 유일 추출이 지켜졌다

분류기가 규약의 padding 금지선을 지키면서 이종만 잡았다. 위양성 쪽 편향은 관측되지 않았다.


6. 한계#

  1. 판정은 이진분류만 했다. 섞인 type 이 몇 종인지는 범위 밖이다. 3종 이상이 관측되지 않았으나 프롬프트가 종수를 세도록 설계되지 않았으므로, 종수 상한은 별도 측정이 필요하다.
  2. 조합 분포는 건수가 적다. 11건 중 7개 조합이 각 1~4건이라 조합 간 빈도 비교는 성립하지 않는다.
  3. type 이 0개인 70건(7.0%)은 단일 원자 항목이므로 정의상 이종일 수 없어 동종으로 처리했다.

산출물#

판정에 쓴 프롬프트와 실행 지시와 판정 원본을 이 폴더에 하나의 재현 단위로 묶어 둔다.

판정에 쓴 프롬프트#

파일 내용
semantic-tag-prompt.txt 판정 기준의 전부. 컴포넌트 판단 규칙 · 경계 규칙 · Taxonomy 49종 · 태그 개수를 정하는 ## Output 절
agent-instructions.md 서브에이전트에게 준 지시문. 판정 기준을 담지 않고 위 파일을 읽게 한다

판정프롬프트는 내용시각화-태깅 레포의 batch/component용/prompts/semantic-tag-prompt.txt 를 2026-08-12 에 무수정 복사한 것이다 (sha256 9f8eef7a3461eb49, 29,180자). v7 단어장이 갱신되면 원본이 바뀌므로, 이 사본으로 재현하거나 결과를 다른 태깅과 비교할 때는 해시부터 대조해야 한다.

판정 결과#

파일 내용
results-1000.csv 표본 1,000건 판정 결과
results-1000.html 이종 11건의 썸네일과 판정근거
CSV 컬럼#
컬럼 내용
레이아웃ID · 컴포넌트ID layouts.id · design_objects.id
썸네일URL 판정에 쓴 원본 썸네일
레이아웃내_컴포넌트수 그 레이아웃에 달린 컴포넌트 수. layout_id 로 groupby 해서 센 값이고 판정이 개입하지 않는다
컴포넌트_타입 판정된 type 배열
판정근거 그렇게 판단한 시각 요소
이종중첩 컴포넌트_타입 이 2개 이상이면 True

측정 코드와 서브에이전트 판정 원본(agent_tags/chunk_NN.jsonl 50개)은 별도 작업 폴더(이종중첩비율산출)에 있다.

↑ 목차로 · 6부 머리로

46장이종중첩 산출 · 서브에이전트 지시 프롬프트 참고판정 규약 원문

원문 reference/2026-08-13-이종중첩-수량-산출/agent-instructions.md GitHub 에서 보기 2 KB 원문 제목 「서브에이전트 지시 프롬프트」

판정은 Anthropic API 가 아니라 Claude Code 서브에이전트가 한다. 청크 하나(20건)에 에이전트 하나를 붙이고, 아래 지시의 {NN} 자리에 담당 청크 번호를 넣어 보낸다. 표본 2,000건이면 청크가 100개이므로 에이전트도 100개다.

이 파일이 지시문의 SSoT다. 에이전트를 띄울 때는 아래 블록을 그대로 복사해 쓴다.

두 층으로 나뉜다. 이 지시는 "무엇을 읽고 어디에 쓸지"만 정하고, 판정 기준은 지시 안에 두지 않는다. 에이전트가 1번 단계에서 semantic-tag-prompt.txt(29,180자, 무수정 사본)를 직접 읽어 그것을 기준으로 삼는다.


지시문#

너는 시멘틱 태깅 분류기다.

## 1. 판정 규약
`/Users/miridih/miridih/이종중첩비율산출/prompts/semantic-tag-prompt.txt` 를 **전문 읽어라.**
이 파일이 판정 기준의 전부다. 컴포넌트 판단 규칙·경계 규칙·Taxonomy 49종·Output 절을 그대로
적용한다.

**규약을 요약하거나 네 상식으로 대체하지 마라.** 특히 `## Output` 절은 태그를 몇 개 달지,
언제 늘리고 언제 하나로 접을지를 정하는 곳이다. 그 절의 문장을 빠짐없이 적용한다 —
제약(padding 금지, 같은 종류의 반복은 1개)과 허용(종류가 다른 구조가 뚜렷이 여럿이면 각각
태그, 필요 시 최대 3~4개) 양쪽 다.

## 2. 작업 목록
`/Users/miridih/miridih/이종중첩비율산출/output/chunks/chunk_{NN}.json` — `items` 각 원소가
컴포넌트 하나, 총 20건.

## 3. 판정
각 항목의 `image` 절대경로를 **Read 툴로 열어 그림을 실제로 보고** 판정한다. 경로나 파일명으로
추측 금지.
이미지는 800×450 캔버스에 컴포넌트 하나를 렌더한 것이다. 흰 여백이 많은 건 정상이며
**위치·여백·정렬이 아니라 그려진 요소의 내부 구조만** 본다.

## 4. 기록
`/Users/miridih/miridih/이종중첩비율산출/output/agent_tags/chunk_{NN}.jsonl` 에 JSONL 한 줄에
하나씩 20건 전부:

{"design_object_id": 7605088, "minor": ["List"], "visual_evidence": "근거 한 문장"}

5건쯤마다 append 하라. 누락 금지.

## 5. 보고
`처리 N건 / 이종(minor 2개 이상) M건` 한 줄만 반환. 개별 내역은 반환하지 마라.

설계 의도#

규약을 지시문에 복사하지 않는다. 판정 기준을 지시문에 요약해 넣으면 원본과 두 벌이 되고, 한쪽만 낡거나 한쪽으로 기울 수 있다. 특히 ## Output 절은 제약과 허용이 함께 적혀 있어, 요약하면 한쪽만 옮기기 쉽다. 에이전트가 원본을 직접 읽게 두면 사본이 하나로 유지된다.

결과를 반환값이 아니라 파일로 쓰게 한다. 에이전트가 중간에 끊겨도 그때까지의 판정이 남고, 재실행할 때 남은 것만 처리하면 된다. 반환은 처리 N건 / 이종 M건 한 줄로 제한해서, 개별 판정을 되받느라 컨텍스트를 쓰지 않게 했다.

이미지를 Read 툴로 열게 명시한다. 경로나 파일명으로 추측하면 그림을 안 보고 판정하는 일이 생긴다. 판정 근거가 시각 요소여야 하므로 이 지시가 필요하다.

흰 여백을 판단에서 빼라고 적는다. 썸네일은 800×450 페이지 캔버스에 컴포넌트 하나만 렌더한 것이라 여백이 크다. 이를 배치 정보로 읽으면 규약의 컴포넌트 판단 규칙 1번("위치와 여백은 무시한다")과 어긋난다.

↑ 목차로 · 6부 머리로

47장출처 인덱스 참고Confluence·Jira·Drive·Slack·코드

원문 reference/sources.md GitHub 에서 보기 12 KB

이 문서의 역할 이 폴더의 문서들이 근거로 삼은 외부 자료를 한곳에 모은다. 같은 것을 다시 검색하지 않기 위한 색인이다. 각 행에는 무엇을 알아내려고 볼 자료인지를 적어 두었다.

오너 정원석 (MORDOR) · 최종 갱신 2026-08-01


1. Confluence#

SSoT가 Confluence 인 것과, 이관 후 보존만 하는 것이 섞여 있다.

페이지 ID 무엇을 볼 때 상태
고객 인풋에서 시멘틱 쿼리v2 추출 LLM 모듈 2713223553 단어장 전량의 실제 카탈로그. 3.4절 일반 29종 · 3.5절 표/차트 23종 프롬프트에 라벨과 선택 기준이 박혀 있다. 5.1절 정규화 규칙 운영 중 (황현진)
2026.07.24 모르도르 추가 논의 2723446785 N/N+α/N+β 정의, 장식 처리 방침, 폴백 카테고리 논의, '리스트는 포기 불가·빅넘버는 포기 가능', 로드맵 5단계 재정의 회의록 (정원석)
Semantic Tagging 절차 명세 — MORDOR(RAT pod) 측 2693858408 태깅 대상 테이블 수치(119,420 페이지 / 83,771 컴포넌트), 태깅 정책 7조, P1~P6 절차, 수신 규격 류민
시멘틱 검색v2 작업 목록/결정사항/타임라인 2692711514 정책·타임라인 SSoT 김수현
내용시각화 Summary 고도화 허브 2704247075 이 폴더 README.md 의 원본 보존 전용. 2026-08-01 부터 repo 가 SSoT
Summary·RLSC 공용 스키마 규약 v0.4 2723021085 schema-v0.4.md 의 원본 보존 전용
Semantic Tags 저장 설계 초안 2692908248 버전 원장 + 현재값 뷰 설계 (RAT pod 제안) 류민
(PRD) 2026q3 내용시각화 - 시멘틱 강화 2468807247 Layer 구분(Semantic Layout = 컴포넌트 레벨 / Content Archetype = 페이지 레벨), v6 정답률 83.8% PRD
내용시각화 시멘틱 강화 분류/검색 체계 정립 2485977598 canonical tag 중간 계층 설계 배경 배경
시멘틱 검색v2 킥오프 회의록 2675671367 v7 확정 경위, 'middle category 가 가장 중요' 회의록
7/21 체크인 2707851016 앞단은 기존 RLSC 방식으로 간다는 결정 회의록
Visual Summary 스펙 인덱스 · v0.2 · v0.3 · 요구사항 원문 규약 v0.4 의 전신. v0.2 에서 'YAML 과 힌트형으로 확정' 보존 전용

cloudId 604308f5-a532-417c-b3de-1d9ed01845cc (MCP 도구 인자로 필요)


2. Jira#

티켓 제목 무엇을 볼 때 상태
MOR-1773 Summary·RLSC 공용 스키마 규약 정의 규약 확정의 상위 티켓 진행 중
MOR-1774 · MOR-1775 · MOR-1776 2·3·4단계 마일스톤 로드맵 단계별 범위 1775 는 실행 안 함
MOR-1755 semantic tag 를 받아 BE 로 전달하는 프로세스 문서 layouts(page) 태깅을 BE 에 전달하지 않는다는 결론. 본문 '확인 필요' 절 진행 중 (박지우)
MOR-1856 [RAT] 반복 단위 순서 라벨링 (order-tagger) category 별 속성의 첫 사례. sequence·rank·attach·span, 달성 수치와 남은 문제 셋 Draft (신영빈)
MOR-1814 유효컴포넌트 v3.5 증량분과 semantic tag 확장 전량 재전달 (data version 2.2) 증량분 재전달 현황 2026-07-31 진행
MOR-1741 · MOR-1744 · MOR-1745 상위 Semantic Tagging / DB 적재 / BE 전달 태깅 파이프라인 1744 는 7/21 배포 완료
MOR-1789 · MOR-1781 ST-05a 판정 규칙 / ST-03c 대치 SSoT v1 Steps 판정과 대치 기준
MOR-1425 · MOR-1448 · MOR-1426 · MOR-1427 I.I.S. 계열 4단계 2026년 6월 중순부터 정지
UNICORN-77316 · UNICORN-77328 파이프라인에 태깅 정보 적재 / 시그니처v2에 태그 검색 적용 BE 쪽 실물 티켓 최운식
UNICORN-77318 · UNICORN-77432 컴포넌트 태깅 / 복수 태그 max 태깅 품질·복수 태그
UNICORN-77437 없음 존재하지 않는 키. 여러 문서가 BE contract 협의 티켓으로 걸어 두었으나 7/15 번호 구간에 구멍만 있다 정리 필요 (TODO.md A-6)

3. Google Drive#

폴더 내용시각화 시멘틱 (1O9vhqaccRoCvltggVhsGjGMjRZqa2s5n) 에 셋이 들어 있다.

파일 ID 무엇을 볼 때 최종 수정
2026q3_내용시각화_시멘틱 분류_v7.xlsx 1SGIvT7pRqVM2JmfpZvAUNPCjFGjxhJLN 단어장 원본. 시트 6종: 01_분류 기준(정의·판별 기준·경계·positive/negative signals) · 02_추론 운영 · 03_검색 설계 · 04_평가 개선 · 05_분류-레이아웃 매핑. 라벨 목록 탭은 gid=2067734549 2026-08-01 02:56
2026724_내용시각화_컴포넌트시멘틱태깅_1차 1Dj29Ysgh0h8PFAe-kGFaOK4PQSPgl2_KxtPWX0HzTa0 태깅 대상 원장 (tagging_targets_rows 119,420) 2026-08-01 03:28
summary 706_712 5000개_split_시멘틱v2검증용 1wRBwx5ab1bYJOPNzkCTySOcpY1dASDPbddT0aZiP6Ro 실사용 입력·Summary·라벨. 아래 3.1절 참고 2026-07-30

3.1 검증 시트의 구성 (파일 하나에 시트 넷)#

이 파일에서 markdown-summary-samples.md 를 뽑았다. 12MB 넘는 파일이라 통째로 읽으면 잘리므로, 필요한 시트를 지목해 읽는다.

시트 내용 규모
part1~part3 input_text · summary 원본 로그 7,208 block (5,371 입력을 --- 로 분할)
시멘틱v2 검증 id · input_text · summary · layout_description · routing · taxonomy_major · taxonomy_minor · 판단근거 · 점수 1,051 block
분포표 순위 · 데이터용 여부 · Page 여부 · taxonomy_minor · 절대수 · 비중 · 우선순위(P0/P1) · 누적 46행
골든셋 case_id(GC-nnnn) · 구현우선순위 · summary · routing · taxonomy_major · taxonomy_minor 90건 (P0 50 · P1 40, 17 중분류)

원 쿼리

sql
SELECT * FROM bronze.miridih_miricanvas_designcontent_template.component_search_request_log
WHERE team_idx NOT IN (4054, 4057)          -- 내부 유저 제외
  AND created_date >= '2026-07-06' AND created_date <= '2026-07-12'
  AND input_text IS NOT NULL
QUALIFY ROW_NUMBER() OVER (PARTITION BY input_text ORDER BY created_date DESC) = 1;

4. 코드 저장소#

저장소 로컬 경로 무엇을 볼 때
miridih/miricanvas-iui ~/Projects/miricanvas-iui 이석님 PoC. 아래 4.1절
miridih/aippt-prisonbreak 없음 MORDOR 파이프라인 본체. RLSC 타입 src/app/pipeline/common/type/layoutStructure.ts, Role 어휘 packages/structured-layout/src/role-v2.ts
miricanvas-web-2 없음 엔진. Smart Object / DataViz packages/engine/editor/src/node/item/

4.1 miricanvas-iui 에서 볼 곳#

packages/comp-sign v4.3.8 가 컴포넌트 매칭 파이프라인 전체를 담는다. src/comp-match 는 이를 소비하는 PoC UI 이다.

파일 무엇을 볼 때
packages/comp-sign/README.md 파이프라인 9단계 표(클라이언트/서버/라이브러리 책임 경계), API 전체
packages/comp-sign/src/build-comp-signatures.ts 색인: 컴포넌트 → structure_hash · text_map · text_node_roles · 채점 feature
packages/comp-sign/src/build-search-hashes.ts 검색: markdown → 요약 트리 + {hash, variant, demand}[]
packages/comp-sign/src/rank-candidates.ts 랭킹. CompCandidate 타입, 태그 군 적용부(102행 주석, 163~177행 정렬), dedupe
packages/comp-sign/src/build-replacement.ts 내용 대치. 입력 구성, box 보정 옵션 4종
packages/comp-sign/src/comp-match/semanticTags.ts semantic tag 소비 방식의 SSoT. 태그 군 3단계 정의(1944행), 목표치 기본값과 TagTier(156168행), 저장 키 형식, 자리 분배
packages/comp-sign/src/comp-match/semanticTaxonomy.gen.ts 분류 생성물 (major 23 → minor 49)
packages/comp-sign/src/comp-match/scoringWeights.ts 채점 가중치 기본값 20여 종
packages/comp-sign/src/node-labeler/ 노드 라벨링
scripts/build-semantic-taxonomy.ts 분류 시트 CSV → semanticTaxonomy.gen.ts 생성기
scripts/build-comp-signs.sql semantic_tags denormalize 백필 (멱등)

5. Slack#

채널 #mordor스쿼드 = C091C6JFYH0. permalink 는 https://miridih.slack.com/archives/C091C6JFYH0/p{ts} 형식이고, {ts} 는 message_ts 에서 점을 뺀 값이다.

날짜 · 사람 permalink 무엇을 볼 때
2026-07-15 20:07 류민 p1784113645611729 태깅 정보 저장 스키마 초안. 버전 원장 분리, SSoT(layouts·design_objects) 불변, 소비층 분리(BE 는 현재값 뷰만 본다), design_object id 재발급 대비 layout_id+content_signature 병기
2026-07-16 10:41 정원석 p1784166091457939 태깅 데이터 흐름 5단계. 류민 별도 table → 황현진 배치처리 → insert → 박지우가 layouts·design_objects 로 복사 적재 → 박지우 S3 upload 시 함께 전달
2026-07-24 13:33 정원석 p1784867588470239 복수 category 콤마 결함. ["A","B"] 로 들어가야 할 값이 ["A,B"] 로 적재된 65건. distinct category 가 64로 부풀었던 원인이고, 태그 배열 합의(MOR-1745)와 어긋난 지점
2026-07-24 13:42 정원석 p1784868161303399 위 결함 65건의 design_object_id 전수 목록(조합별 분류). scripts/output/semantic-tag-comma-defects.csv
2026-07-27 09:38 정원석 p1785112693366229 BAD 원인 분석. BAD 의 85% 가 컴포넌트와 내용이 안 맞는 문제라는 근거. 집계 방법과 세부 원인은 content-binding-contract.md 머리에 있다
2026-07-27 11:33 류민 p1785119593760279 desc/deco 노드 분류 규칙 설계, core-C 최소 정의(core vs desc 경계)
2026-07-27 18:00 정원석 p1785142810027419 페이지 역할 태그 처리 결정. 네 종은 Page Role 을 승계하고 logic 으로 태깅한다. 상세는 TODO.md C-4
2026-07-31 09:50 신영빈 p1785459037587589 CORE/DESC/DECO/OUT 노드 태깅, MOR-1856 순서 추론, Steps Capacity 확장 PoC, 꾸밈·설명 단계적 증감 구상
2026-07-31 정원석 p1785477152714629 문서를 Confluence 에서 repo 로 이관한다는 공지
날짜 미상 p1784594088864429 layouts 태깅을 검색에 쓰지 않는다는 원 근거. MOR-1755 본문이 인용

Slack 검색은 결과가 커서 자주 잘린다. include_context: false, response_format: "concise", limit: 12 로 시작하고 필요한 것만 slack_read_thread 로 확대한다. permalink 가 필요하면 response_format: "detailed" 로 좁게 검색한다.


6. 이 폴더의 문서와 근거의 대응#

문서 주요 근거
README.md Confluence 2704247075(원본) · 2723446785(7/24) · DB design_object_semantic_tags 2026-07-22 적재
schema-v0.4.md Confluence 2723021085(원본) · 2723446785(N/N+α/N+β) · aippt-prisonbreak RLSC 타입 · miricanvas-web-2 DataViz
semantic-taxonomy-v7.md Confluence 2713223553(카탈로그) · Drive 1SGIvT7...(원본 시트) · comp-sign semanticTags.ts(id 형식)
markdown-summary-samples.md Drive 1wRBwx5... 전량
pipeline-flow.md 위 전부 + ../comp-signature.md
content-binding-contract.md comp-sign v4.3.8 실물 코드 · MOR-1856 · Slack 7/27·7/31
TODO.md 위 전부
call-cost-history-2026.md Slack #프레젠테이션트라이브_내용시각화_전체·#내용시각화스쿼드_스마트블록_코어·#mordor스쿼드·#dx개발_플랫폼_ae파트 발화 다수 (2026-01~08). 전량 과거 시점 데이터

↑ 목차로 · 6부 머리로

7부

통합 보고서 — report/

PDF 보고서의 직전 원고 여섯 편. 원문의 스냅샷이고 SSoT 가 아니다 — 원문과 다르면 원문이 맞다.

48장통합 PDF 보고서 만드는 법 참고구성과 재생성

원문 report/README.md GitHub 에서 보기 5 KB 원문 제목 「Visual Summary 통합 PDF 보고서」

이 문서의 역할 이 폴더가 무엇이고 왜 있는지, 최종 PDF 를 어떻게 다시 만드는지를 담는다. 오너 정원석 (MORDOR) · 생성일 2026-08-23 · 최종 갱신 2026-09-07 · 파생물이고 SSoT 아님

찾기 (한 줄)#

무엇을 찾나 어디인가
완성된 PDF pdf/visual-summary-overview.pdf
PDF 생성 직전 원고 sections/01-overview.md, 02-schema.md, 03-status.md, 04-implementation.md, 05-reference.md, 06-examples.md
다시 만드는 법 아래 4절. python3 build/build.py ../sections ../pdf/visual-summary-overview.pdf
원문 SSoT (이 PDF가 요약하는 것) ../README.md, ../TODO.md, ../spec/, ../design/, ../plan/

PDF만 고치고 싶으면 sections/*.md를 고친다. 원문이 바뀌었으면 원문을 먼저 고치고, 그다음 sections/ 요약을 맞춘 뒤 PDF를 다시 빌드한다. sections/만 고치고 원문을 그대로 두면 안 된다.

0. 이게 뭔가#

README.md, TODO.md, spec/, design/, plan/ 등 이 프로젝트 폴더 곳곳에 흩어진 문서를 한 사람이 처음부터 끝까지 통독할 수 있도록 개념, 일정, 작업 상태, 구현물 로직 순서로 재구성해 하나의 PDF 로 묶은 것이다. 이 폴더의 원문 문서를 대체하지 않는다. SSoT 는 언제나 각 원문 파일(../README.md, ../spec/schema-v0.5.md 등)이고, 이 PDF 는 그것들의 스냅샷이다.

최종 산출물: pdf/visual-summary-overview.pdf (95쪽, 2026-09-07 재생성)

1. 구성#

report/
├── README.md            이 문서
├── pdf/
│   └── visual-summary-overview.pdf   최종 산출물
├── sections/             PDF 본문 원고 (한국어 Markdown, 번호순으로 이어붙인다)
│   ├── 01-overview.md    개요: 배경, 로드맵, 용어 사전, 소유 경계
│   ├── 02-schema.md      규약: 스키마 v0.5, 필드 전체, fallback 사다리, 복합 구조 fallback, projection 규칙, 꾸밈구조
│   ├── 03-status.md      일정과 작업 상태: 로드맵 진행, Jira 구조, TODO 절 구조
│   ├── 04-implementation.md   구현물 로직: PoC 파이프라인, comp-sign 실물, 결함 16종, 대치 계층, 평가 pool, 코드 위치
│   ├── 05-reference.md   부록 A~K: 카테고리 45종, params 27종, structure 필드, fallback 순서 45종과 엣지 130개,
│   │                     열린 항목(2026-09-07), 자기정합 검사 14가지, 꾸밈구조 형 정의
│   ├── 06-examples.md    부록: 45종 YAML 예시 전량, 병치와 포함 이종중첩, 복합 구조 fallback 단계별 예시
│   └── img/              본문에 삽입하는 다이어그램 PNG (diagrams/ 의 렌더 결과물)
├── diagrams/              Mermaid 다이어그램 소스 (.mmd) — 수정은 여기서 한다
│   ├── mermaid-config.json    색상·폰트 테마 (저장소 톤에 맞춘 slate 팔레트)
│   ├── puppeteer-config.json  헤드리스 크롬 --no-sandbox 옵션
│   ├── roadmap.mmd       5단계 로드맵 흐름도
│   ├── pipeline.mmd      PoC 8단계 파이프라인
│   ├── ownership.mmd     소유 경계도 (누가 실서비스, 누가 PoC 대행)
│   ├── codemap.mmd       코드 모듈 지도 (lib/api/app 세 층)
│   ├── fallback.mmd      fallback 전 과정 (단일 노드 사다리 + 복합 구조 N1~N5 + projection)
│   ├── search-tiers.mmd  검색 fallback 군 넷과 군 안의 채점 넷
│   └── timeline.mmd      일정 간트차트
└── build/                 PDF 조립 도구
    ├── build.py           sections/*.md 를 합쳐 HTML → PDF 로 변환하는 스크립트
    ├── style.css          인쇄용 스타일시트 (A4, 표지, 목차, 다이어그램 크기 등)
    └── template.html      pandoc HTML 템플릿 (표지 + 목차 + 본문)

2. 다시 만드는 법 (내용을 고친 뒤)#

2.1 원고만 고친 경우 (다이어그램은 그대로)#

sections/*.md 를 고친 뒤 바로 4절의 빌드 명령만 실행하면 된다.

2.2 다이어그램을 고친 경우#

  1. diagrams/*.mmd 를 고친다.

  2. 렌더링한다 (Node.js 와 npx 가 있으면 별도 설치 없이 실행된다).

    bash
    cd diagrams
    for f in roadmap pipeline ownership codemap nesting-containment fallback search-tiers; do
      npx -y @mermaid-js/mermaid-cli -i "$f.mmd" -o "../sections/img/$f.png" \
        -b white -s 3 -c mermaid-config.json -p puppeteer-config.json
    done
    # timeline(간트차트)은 가로로 넓어서 -w/-H 를 따로 준다
    npx -y @mermaid-js/mermaid-cli -i timeline.mmd -o ../sections/img/timeline.png \
      -b white -s 3 -w 1400 -H 500 -c mermaid-config.json -p puppeteer-config.json
  3. 실측 컴포넌트 위에 덧그린 그림(sections/img/real-*.png)은 mermaid 가 아니라 파이썬 스크립트로 만든다. 원본 썸네일은 2026-08-13 수량 산출의 URL 에서 받아 오므로 사내망이 필요하다.

    bash
    cd diagrams
    python3 annotate-real-components.py ../sections/img   # pillow 필요
  4. 다이어그램은 세로로 길게 그리지 않는다. style.css 가 이미지를 max-width: 148mm, max-height: 105mm 로 제한하는데, 다이어그램 자체의 가로세로 비율이 1.5~3.0 사이가 아니면 너무 얇은 배너가 되거나(비율이 크면) 페이지를 거의 다 채우는 그림이 된다(비율이 작으면, 특히 세로로 긴 flowchart TD). 단계가 여러 개인 흐름도는 roadmap.mmd/pipeline.mmd처럼 flowchart TB 바깥 틀 안에 direction LR인 하위 subgraph를 여러 단으로 쌓는 패턴을 쓴다. subgraph 안 노드끼리 화살표로 연결해 둬야 direction LR이 실제로 적용된다(연결이 없으면 mermaid가 세로로 쌓아 버린다).

3. 렌더 결과를 눈으로 확인하는 법#

PDF 뷰어가 없는 환경에서는 PyMuPDF 로 페이지를 PNG 로 뽑아 확인한다.

bash
pip install pymupdf
python3 -c "
import fitz
doc = fitz.open('pdf/visual-summary-overview.pdf')
print('pages', len(doc))
doc[0].get_pixmap(dpi=95).save('page0.png')
"

4. PDF 빌드 명령#

요구 사항: pandoc(HTML 변환), weasyprint(HTML → PDF, pip install weasyprint). macOS 에서 Apple SD Gothic Neo 폰트가 시스템에 있으면 한글이 정상 출력된다. 다른 OS 에서는 build/style.css 의 font-family 를 그 환경의 한글 폰트로 바꾼다.

bash
cd build
python3 build.py ../sections ../pdf/visual-summary-overview.pdf

build.py 는 sections/*.md 를 파일명 순서대로(01 부터 06 까지) 이어붙이고 template.html 로 표지와 목차를 씌운 뒤 weasyprint 로 PDF 를 낸다. 밑줄로 시작하는 파일은 이어붙일 때 건너뛴다. 중간 산출물(sections/_merged.md, sections/_merged.html)이 같은 폴더에 떨어지므로, 이 규칙이 없으면 두 번째 빌드에서 본문이 두 벌 들어간다. 중간 산출물은 커밋하지 않는다 (.gitignore 필요 없이 매번 덮어써지므로 리뷰 전 수동으로 지워도 된다).

5. 내용을 갱신할 때 지켜야 할 것#

  • 원문이 SSoT다. sections/*.md 는 이 폴더의 원문 문서(../README.md, ../spec/, ../design/, ../plan/, ../TODO.md, ../DASHBOARD.md)에서 가져온 요약이다. 원문이 바뀌면 이 요약도 같이 갱신한다. 반대로 이 요약만 고치고 원문을 그대로 두면 안 된다.
  • 수치와 날짜는 원문에서 확인된 값만 쓴다. 추정치를 채우지 않는다.
  • 저장소 문서 작성 규칙을 따른다. em dash(—)와 가운뎃점(와) 금지, 제목은 명사구, 인용은 자기완결 링크. 상세는 docs/WRITING.md.
  • PDF 를 갱신하면 이 README 의 생성일과 쪽수도 같이 고친다.

↑ 목차로 · 7부 머리로

49장보고서 1 · 개요 파생파생물

원문 report/sections/01-overview.md GitHub 에서 보기 9 KB

개요: 이 문서가 무엇이고 Visual Summary 가 무엇인가#

이 문서의 목적과 한계#

이 문서는 미리캔버스 내용시각화(사용자가 쓴 글을 슬라이드 디자인으로 바꿔 주는 기능)의 하위 트랙인 Visual Summary(내용시각화 Summary 고도화) 를 한 번에 통독하기 위해 만든 조합 문서다. 개념, 일정, 작업 상태, 구현물의 로직을 한 흐름으로 붙였다.

이 문서는 SSoT(단일 진실 공급원)가 아니다. 원문은 miridih/mordor-wiki 저장소의 docs/projects/mordor/visual-summary/ 폴더이고, 이 문서는 그 폴더의 문서들과 miridih/aippt-prisonbreak 저장소의 실제 구현 코드를 읽어 조합한 파생물이다. 값이 어긋나면 원문이 맞다. 원문 폴더 안의 역할 분담은 다음과 같다.

원문 위치 그 문서가 갖는 권한
visual-summary/README.md 프로젝트 허브. 로드맵, 용어 고정, Semantic Component Category 분포
visual-summary/TODO.md 할 일과 정할 것의 SSoT
visual-summary/spec/schema-v0.5.md 규약(스키마) SSoT. 2026-08-02 확정
visual-summary/spec/capability-table.md 카테고리 45종의 성립 조건과 fallback 순서
visual-summary/design/ 왜 그렇게 설계했는가
visual-summary/plan/poc-guide.md 처음 보는 사람의 진입점
visual-summary/DASHBOARD.md 진행 현황 요약. 파생물이고 SSoT 아님

오너는 정원석(MORDOR) 이고, 원문 허브의 최종 갱신은 2026-09-05 이다. 규약 현행 값은 0.5.4 이다. Confluence 의 원래 허브 페이지(2704247075)는 2026-08-01 부터 갱신을 멈추고 보존만 한다. 옮긴 이유는 둘이다. Confluence 는 MCP 로 갱신할 때 결과가 불확실하고, AI 에게 SSoT 를 컨텍스트로 넣기에도 저장소 쪽이 낫다는 판단이다 (2026-07-31 #mordor스쿼드 공지).

배경: 왜 Summary 를 고도화하는가#

먼저 오늘 무엇이 돌고 있는지부터 본다. 사용자가 발표 주제나 본문 텍스트를 넣으면, LLM 이 그것을 Markdown Summary(마크다운 형식의 중간 요약문)로 바꾼다. 그 다음 시스템은 이 마크다운을 임베딩(문장을 숫자 벡터로 바꾼 표현)으로 만들어, 이미 만들어져 있는 디자인 컴포넌트들의 임베딩과 유사도를 비교해 비슷한 것을 찾는다. 찾은 컴포넌트의 텍스트 자리에 사용자 내용을 넣으면 슬라이드가 나온다.

이 방식의 문제는 하나다. 마크다운은 "무엇을 말하는가"만 담고 "어떤 구조로 보여야 하는가"를 담지 못한다. 사용자가 "매출이 3년간 늘어난 추이"라고 썼을 때 그것이 시간 순서를 가진 타임라인인지, 세 값을 비교하는 막대 차트인지, 단순 나열인지가 마크다운 텍스트에는 구조로 적히지 않는다. 임베딩 유사도는 단어가 비슷한 컴포넌트를 찾을 뿐이라, 구조가 맞는 컴포넌트를 골라 주지 못한다.

Visual Summary 는 이 잃어버린 구조를 명시적으로 적는 중간 표현이다. 사용자 입력을 "컴포넌트 몇 개이고, 각 컴포넌트는 어떤 의미 유형(리스트, 단계, 비교표 등)이며, 그 안에 항목이 몇 개이고 항목끼리 어떤 관계인가"로 구조화해서 적는다. 형식은 YAML 로 확정했다 (2026-08-01 초안, 2026-08-02 v0.5 확정).

한눈에 보기#

질문 짧은 답
무엇을 바꾸나 Summary 형식(Markdown → YAML), 검색 방식(임베딩 → semantic tag + 구조), 태그를 다는 범위(안 달다가 컴포넌트 단위, 최종적으로 node 단위)
지금 어느 단계인가 1단계(현행)가 운영 중이고, 2단계와 3단계는 건너뛰기로 정해졌다. 실제 목표는 4단계다
4단계 목표 시점 2026년 9월 말 dev/stg 반영, 10월 중순 production 판정 (2026-08-04 확정)
규약은 확정됐나 확정됐다. v0.5, 2026-08-02. 현행 값은 0.5.4. 원래 마감 8/7 보다 닷새 앞섰다
실물이 도는가 Eregion 화면(/visual-summary)에서 질의 갈래 전체가 돈다. 2026-09-05 주에 자유 입력부터 렌더까지 Week 27 PoC(K절)가 로컬 인덱스 12,348건 위에서 한 번에 돈다. 실서비스 코드는 아니고 PoC 다
가장 큰 미해결 규약 열린 결정(B절)은 2026-09-05 에 전량 닫혔다. 남은 병목은 내용 대치 contract(I절), 노드 검색 정확도 판정, 프로덕션 I.I.S. 추출이다
데이터 쪽 가장 큰 제약 검색 대상 컴포넌트의 Semantic Component Category 가 List 한 종류에 쏠려 있다. README 3절의 83,771건·81.1% 는 2026-07-22 스냅샷이다. 2026-09-05 프로덕션 실측은 design_object_semantic_tags version 2.2 가 178,095건이고 전량이 검색 후보 조건을 채운다

5단계 로드맵 요약#

단계마다 바뀌는 것은 네 가지다. Summary 형식, 검색 방식, 태그를 다는 범위, 그리고 그 태그가 무엇을 가리키는가 이다.

단계 Summary 형식 검색 방식 태그 범위 상태
1단계 (현행) Markdown 임베딩 유사도 태그 없음 운영 중
2단계 Markdown semantic tag 컴포넌트 하나에 태그 사실상 스킵 (2026-08-07 확정)
3단계 YAML semantic tag + 구조 질의 컴포넌트 하나에 태그 실행하지 않음, Jira 상 Cancelled (2026-08-06)
4단계 YAML semantic tag + RLSC 내부 node 태그 컴포넌트 안 node 하나하나 진행 중. 9월 말 dev/stg 목표
5단계 YAML 4단계와 같음 node 하나하나 + properties(카테고리 고유 속성) + 구조 대응 미정

[그림: 로드맵 5단계와 스킵 경로] ↗

5단계 추출기는 두 벌을 함께 낸다. 한 번의 호출로 둘이 나온다(2026-08-23 확정, plan/node-tag-and-property-poc-blueprint.md 1.0.1절).

산출물 무엇인가 어휘는 어디서 오나 없으면 무엇을 모르나
properties (카테고리 고유 속성) 피라미드의 꼭대기가 어느 쪽인가, 순서가 어느 방향인가처럼 그 카테고리에서만 뜻이 있는 표현 값 capability-table.md 2절 "선택 속성" 열(reads.params). 질의 쪽 params 와 같은 어휘 이 컴포넌트를 어떻게 그리는가
구조 대응 (structure map) 경계 안의 어느 노드가 label 인지 items 인지 value 인지 period 인지의 대응표 capability.ts 의 reads.content 에 label 과 items 를 더한 것 사용자 내용의 어느 조각을 어느 노드에 넣는가

둘 다 iis_semantic_component 테이블의 칸(properties, structure)으로 들어가고 RLSC 스키마 자체는 건드리지 않는다.

property(속성)라는 말이 두 뜻으로 쓰인다. 이 문서는 아래로 고정한다 (원문 SSoT 는 허브 README.md 2절 표기 규칙).

무엇 질의 쪽 (생성기가 적는다) 색인 쪽 (추출기가 뽑는다)
카테고리 고유 속성 params properties
구조 structure. 내용 자체 구조 대응(structure map). 노드와 필드의 대응표

params 와 properties 는 같은 어휘를 쓰지만 같은 것이 아니다. 한쪽은 요구("이렇게 그려 달라")이고 다른 쪽은 실측("이 컴포넌트는 이렇게 그려져 있다")이다. 둘을 맞대는 것이 5단계 T-8 이다. 맨 이름 '속성'이나 'property' 를 단독으로 쓰지 않는다. 넓은 뜻은 한 자리에만 있다. 5단계 국면 이름 I.I.S. (Semantic Component Property) 의 'Property' 가 위 오른쪽 열 두 칸을 합쳐 부르는 이름이고, 그래서 이 이름은 줄여 쓰지 않는다.

단계를 나누는 진짜 이유는 태그의 모호성이다. 태그를 컴포넌트 단위로만 달면, 그 태그가 컴포넌트의 최상위(root) 자체가 그 유형이어서 붙은 것인지, 하위 어딘가에 그 유형의 노드가 있어서 컴포넌트 전체가 받은 것인지 구분할 수 없다. 실데이터가 이것을 보여 준다. 한 컴포넌트에 태그가 최대 4개까지 붙고(UNICORN-77432), 예를 들어 design_object_id 4757227 에는 Comparison Table, Hierarchical Tree, List, Highlighted Message 넷이 함께 붙어 있다. 최상위 하나가 이 넷을 동시에 가질 수는 없으니 하위에 여러 유형의 노드를 품고 있다는 뜻이다. 4단계에서 node 단위 태그가 붙으면 검색과 내용 대치를 정확한 범위에 걸 수 있다.

용어 사전#

같은 말을 같은 뜻으로 쓰기 위해 원문 허브가 고정한 용어다. 처음 보는 사람이 가장 많이 막히는 것부터 적는다.

용어 쉽게 말하면
RLSC 디자인 하나의 레이아웃 구조와 각 요소의 역할(제목, 설명, 마커 등)을 담은 트리 데이터. 디자인을 AI 가 읽을 수 있게 만든 구조 표현이다
design object / 컴포넌트 검색과 대치의 단위가 되는 디자인 조각. 슬라이드 한 장 안의 의미 있는 묶음 하나
Semantic Component Category (옛 이름 semantic tag) "이 콘텐츠가 어떤 의미 구조인가"를 가리키는 분류값. List, Steps, ComparisonTable 같은 값이다. 2026-09-05 에 정원석이 이 이름으로 확정했다. 과거 RLSC Role v1.0 의 Role.Component.* 와 비슷한 목적으로, RLSC tree 안 node 마다 심는 값이다
semantic taxonomy (v7 단어장) 쓸 수 있는 Semantic Component Category 값의 목록. 단어장 밖의 값은 못 쓴다
Summary 사용자 입력을 검색과 대치에 쓰려고 변환한 산출물
Summary 생성기 사용자 입력을 받아 Summary 를 써 내는 LLM 모듈. 오너는 허재영, 황현진
Visual Summary Summary 를 의미 구조로 구조화하는 스펙. v0.1 부터 v0.5 까지 왔고 현행 값은 0.5.4
capability (성립 조건) 어떤 semantic tag 로 배치하려면 최소한 무엇이 있어야 하는가의 조건. 예를 들어 비교표는 비교 대상이 둘 이상 있어야 성립한다
fallback (강등) 요청한 카테고리가 성립하지 않거나 검색 결과가 없을 때 다음 카테고리로 내려가는 규칙
복잡도 레벨 Visual Summary 하나가 가질 수 있는 위상의 범위. L1(컴포넌트 하나), L2(최상위 병치까지), L3(컴포넌트 경계를 넘는 중첩까지) 셋이고 누적이다. 산출물 한 건의 모양이 아니라 생성기에 허용된 범위를 가리킨다. '질의'라고 부르지 않는다. 질의로 바뀌는 것은 질의 변환기를 거친 뒤다. 실서비스 생성기는 L2 까지이고 PoC 생성기는 이미 L3 를 낸다
structure Summary YAML 안의 entities, relations, dataset. 검색과 fallback 과 projection 의 입력이다
projection (투영) fallback 으로 다른 카테고리로 내려갈 때 원래 구조의 내용을 도착 카테고리가 읽는 자리로 옮겨 담는 규칙
comp-sign 이석님 소유의 npm 패키지이자 모듈. 컴포넌트 구조를 해시로 만들어 검색하고, 텍스트를 사용자 내용으로 바꿔 넣는다. 실서비스 경로다
Search Signature v2 컴포넌트 쪽 검색용 정적 데이터. RLSC 와는 별개 contract 다
I.I.S. (증분 정보 구조화기) RLSC 를 추출한 뒤 부족한 정보를 AI 모듈로 더 뽑아 원래 RLSC 에 속성을 덧붙이는 모듈 계열. Incremental Information Structuriser
I.I.S. (Semantic Component Boundary) 그 계열 중 4단계판. 컴포넌트 안 어느 노드 묶음이 어느 semantic category 인지 경계를 정한다. 코드 식별자 iisSemanticComponentBoundary
I.I.S. (Semantic Component Property) 같은 계열 5단계판. 경계가 잡힌 묶음에서 둘을 뽑는다. 카테고리 고유 속성(properties)과 구조 대응(structure map)이다. 코드 식별자 iisSemanticComponentProperty. 이 이름의 'Property' 만 넓은 뜻이고 줄여 쓰지 않는다
params / properties / 구조 대응 같은 것을 층마다 다르게 부른다. 카테고리 고유 속성은 질의 쪽에서 params, 색인 쪽에서 properties 다. 노드가 structure 의 어느 필드인지의 대응표는 구조 대응이라 부르고 질의 쪽 structure(내용)와 구분한다
꾸밈구조 (decoration) 컴포넌트에 붙는 꾸밈 선호. RLSC 의 role Decoration(장식 요소)과는 다른 것이고 앞에 무엇이 붙는지로 구분한다
Eregion Visual Summary PoC 화면이 들어 있는 내부 개발용 앱 경로. /visual-summary
RAT pod I.I.S. 본편을 소관하는 다른 팀 단위. 담당 류민
MORDOR RLSC 추출과 구조화 파이프라인을 담당하는 팀 및 시스템 이름. 이 저장소가 그 코드베이스다

헷갈리기 쉬운 것 셋.

첫째, semantic tag(의미 분류)와 Layout Container type(물리 배치, HStack/VStack/Grid)은 다르다. 단어장에 배치명과 겹치는 라벨이 있어 헷갈리기 쉽다.

둘째, Content Archetype(이 페이지가 문서에서 어떤 역할인가, 표지/목차/본문/마무리)과 semantic tag(이 콘텐츠가 어떤 의미 구조인가)는 서로 다른 것을 분류한다. 지금 실데이터에는 이 둘이 섞여 있다.

셋째, 색인 쪽과 질의 쪽이 다르다. Search Signature v2 는 이미 존재하는 컴포넌트를 색인하는 쪽이고, Summary 의 structure 는 사용자 입력을 질의로 만드는 쪽이다. 분포 수치를 볼 때 이 둘을 섞으면 결론이 뒤집힌다.

사람과 소유 경계#

사람 무엇을 갖는가
정원석 (MORDOR PE) Visual Summary 규약과 스펙, 이 폴더 문서 SSoT, PoC 구현, 질의 변환기 스펙
김수현 (미리캔버스 PM) v7 단어장 오너, 제품 배포 여부 최종 판단, 생성기 정책 판단
허재영 (프로덕트 그룹 TPM) 골든셋과 판정 실무, Summary 생성기 프롬프트 튜닝
황현진 (DX개발그룹 PE) 추출 모듈, Layout Description 추출과 카탈로그 라우팅 분류기
이석 comp-sign 패키지. 구조 검색, 랭킹, 내용 대치의 실서비스 경로
류민 (RAT pod) I.I.S. 본편
최운식 (BE) 파이프라인에 태깅 정보 적재, 시그니처 v2 에 태그 검색 적용

[그림: 소유 경계: 각 층을 누가 실서비스로 갖고 누가 PoC로 대행하는가] ↗

이후 절 안내#

절 무엇에 답하나
2 규약이 정확히 무엇을 정했나 (스키마 v0.5, 카테고리 성립 조건, fallback)
3 일정과 작업 상태, Jira 구조, 아직 열려 있는 결정
4 PoC 가 어디까지 왔고, 코드가 어디에 있으며 어떤 로직으로 도는가

↑ 목차로 · 7부 머리로

50장보고서 2 · 규약 파생파생물 · 4장의 보고서판

원문 report/sections/02-schema.md GitHub 에서 보기 41 KB

규약: Visual Summary 스키마 v0.5#

무엇을 표현하는 형식인가#

Visual Summary 는 사용자 입력을 컴포넌트 몇 개, 각 컴포넌트의 의미 유형, 내부 항목 수와 관계로 구조화해 적는 YAML 이다. 최상위는 항상 components[] 배열이고, 컴포넌트가 하나여도 길이 1 배열로 적는다. 표면 형식은 YAML 로 확정했다(2026-08-01 초안, 2026-08-02 v0.5 확정). LLM 이 YAML 을 직접 생성하고, 검증 단계에서 JSON 으로 변환해 스키마를 검사한다.

형식 선택 근거. YAML 은 JSON 보다 구두점 토큰이 적어 같은 내용을 더 적은 생성 토큰으로 쓴다. KDL(대안으로 검토한 다른 텍스트 형식)은 토큰이 12.9% 더 적지만 채택하지 않았다. 이유는 셋이다. 사전 조사에서 LLM 생성 신뢰성 벤치마크가 YAML 은 풍부하고 KDL 은 0건이었고, 2026-07-13 v0.2 때부터 이미 YAML 로 예시가 쌓여 있었으며, 규약 확정 마감(2026-08-07)까지 KDL 전환 비용을 감당할 시간이 없었다. 이 결정은 사후 검증 대상으로 남겨 뒀고, 실측으로 KDL 이 뚜렷하게 앞서면 재검토한다.

설계 원칙 셋#

규약 1절이 정한 원칙이다. 생성 단계에서 최적 semantic category(의미 분류값)를 정한다. 생성기가 입력을 읽고 어느 카테고리로 그릴지 골라 type 에 적는다. 검색은 그 카테고리를 capability(성립 조건)와 대조해 실제로 그릴 수 있는지 확인하고, 못 그리면 fallback(강등) 순서를 따라간다.

structure 의 필드 이름을 카테고리에 묶지 않는다. venn.sets 가 아니라 entities 이고, profile.avatar 가 아니라 entity.avatar 다. 필드 이름이 중립이면 특정 필드를 못 읽는 카테고리는 그 필드를 그냥 지나칠 뿐 오류가 나지 않는다. 이것이 fallback 이 안전하게 도는 이유다.

카테고리마다 다른 것은 params 가 담는다. structure 는 내용이고 params 는 표현 선택이다. 이 구분이 이 절 전체의 핵심이므로 아래에서 따로 다룬다.

이 세 원칙 위에 예외 없는 규칙 셋이 서 있다. 첫째, 어떤 structure 든 리스트로 읽히는 entities 를 가진다(dataset 만 있으면 거기서 파생한다). 둘째, 선언되지 않은 것은 무시한다(관계나 슬롯을 못 읽는 컴포넌트는 그냥 안 읽는다). 셋째, 중첩은 평탄화된다(entity.components[] 를 못 읽는 컴포넌트는 자식의 entities 를 부모의 items 로 편다). 이 셋이 성립하기 때문에 fallback 순서가 언제나 끝나고, 컴포넌트가 무엇을 못 그리든 결과가 비지 않는다.

structure, params, decoration: 셋의 역할 구분#

같은 스키마 안에 "내용을 담는 곳"이 셋으로 나뉘어 있어서 헷갈리기 쉽다. 표로 정리한다.

구분 무엇을 담나 카테고리마다 다른가 fallback 시
structure 내용. entities, relations, dataset 아니다. 필드 이름이 모든 카테고리에서 같다 그대로 옮긴다(P1~P8 규칙)
params 표현 선택. orientation, apex, icon, header 같은 것 그렇다. 카테고리마다 허용 키가 다르다(capability-table.md 2절) 버린다(P2). 도착 카테고리가 선언하지 않은 표현 선택은 뜻이 없기 때문이다
decoration 꾸밈 선호. ornament, background 두 축 아니다. 카테고리와 무관하다 컴포넌트 → page → 카테고리 기본값 → 시스템 전역 기본값 → indifferent 순으로 해석한다

params 를 열린 객체로 둔 이유. 카테고리마다 표현 선택 속성이 다르다. 45종의 속성을 전부 스키마 조건문으로 쓰면 스키마가 어휘에 묶여 카테고리가 늘 때마다 스키마 버전이 올라간다. 그래서 스키마는 "객체다"까지만 검사하고, 실제 허용 키와 기본값은 capability 테이블이 카테고리별로 정한다.

decoration 이 params 도 structure 도 아닌 이유. params 는 카테고리별 표현 선택인데 꾸밈구조는 카테고리와 무관하고, structure 는 내용인데 꾸밈구조는 내용이 아니다. 그래서 structure 의 형제로 별도 필드를 둔다.

핵심과 장식 사이에 한 층이 더 있다(열린 자리). 간트차트 가장자리에 붙은 짧은 주석처럼 내용은 담고 있지만 그 카테고리의 성립 조건에는 관여하지 않는 조각이 실사용에 있다. 장식이 아니므로 지우면 내용이 줄고, 핵심이 아니므로 없어도 그 카테고리는 성립한다. 색인 쪽은 이 층을 이미 갖고 있다. 신영빈이 Steps 대상으로 구현하고 류민이 규칙을 설계한 RLSC 노드 분류 CORE / DESC / DECO / OUT 이 그것이고(2026-07-31), DESC 가 정확히 "core 는 아니지만 설명 역할을 하는 노드, 있으면 채우는 슬롯"이다(design/content-binding-contract.md 1.1절).

층 색인 쪽(RLSC 노드 분류) 질의 쪽(스키마 v0.5)
핵심 CORE. 그 카테고리를 성립시키는 노드 structure.entities 와 성립 조건이 요구하는 필드
위성 DESC. 있으면 채우고 없으면 비우는 설명 노드 전용 자리가 없다. entity.items 뒤에 섞이거나 별도 컴포넌트로 쪼개진다
꾸밈 DECO. 비우거나 제거 가능 decoration 두 축(ornament, background)

즉 3단 구분은 색인 쪽에만 있고 질의 쪽 스키마에는 대응이 없다. 지금 v0.5 에서 위성 조각은 entity.items 에 딸려 들어가거나 Enumeration.List 컴포넌트 하나로 떨어져 나가는데, 앞은 "핵심 항목의 하위 설명"과 구분되지 않고 뒤는 원래 컴포넌트와의 종속 관계를 잃는다. 이 자리를 여는 것은 요소 대치가 동작한 뒤에 값이 생기고(지금은 DESC 슬롯을 채울 주체가 없다), 축을 늘리는 결정은 Element.Separator 를 세 번째 꾸밈 축으로 열지와 같은 성격의 미결이다(2절 마지막 "아직 정하지 않은 것"). 아직 TODO 번호가 붙어 있지 않은, 이 문서가 새로 지목하는 구멍이다.

필드 전체 구성#

규약이 정의하는 필드를 계층별로 정리한다. 원문은 TypeScript 타입과 JSON Schema 두 벌로 적혀 있고 둘은 같은 것을 정의한다(SSoT 는 JSON Schema).

최상위 (VisualSummary)

필드 필수 무엇인가
version 예 현행 "0.5.4". 형식은 MAJOR.MINOR.PATCH 이고 검증은 MAJOR·MINOR 만 대조한다
title 예 빈 문자열 불허
components[] 예 1개 이상
relations[] 아니오 page 수준 관계. 자리만 예약, 생성기는 채우지 않는다
decoration 아니오 page 수준 꾸밈 선호. 자리만 예약, 생성기는 채우지 않는다

규약을 고치면 버전을 올린다(2026-08-29 신설). 종전에는 v0.5 를 자리에서 네 번 고치면서 버전을 그대로 뒀고, 그래서 "지금 규약이 무엇인가"를 버전으로 가리킬 수 없었다. 형식은 MAJOR.MINOR.PATCH 이고 각 자리 0~99 다.

자리 언제 올리나 예
MAJOR 기존 산출물이 실제로 무효가 된다 v0.4 → v0.5 의 "최상위가 항상 components[]"
MINOR 기존 산출물은 유효하고 새로 쓸 수 있는 것이 는다 entities+columns 대안 경로 추가
PATCH 유효성이 안 바뀐다. 문구 정정, 실제로 지켜지던 것의 명문화 type 필수화(0.5.1), 대분류 제거(0.5.2), enum 45종 반영(0.5.3)

판정은 원리가 아니라 실측으로 한다. type 필수화는 원리적으로 required 를 좁히는 변경이지만 무효가 되는 산출물이 실제로 0건이라 PATCH 다(골든셋 470/470 이 이미 type 을 갖고 있었다). 실측 없이 좁히는 변경은 MINOR 이상으로 본다. 검증기는 MAJOR·MINOR 만 대조하고 PATCH 는 보지 않는다 — PATCH 가 유효성을 안 바꾼다고 정의해 놓고 patch 차이로 옛 산출물을 떨어뜨리면 자기모순이다. 파일 이름도 MAJOR.MINOR 만 따라간다. 그래서 이 개정에도 schema-v0.5.md 와 visual-summary-0.5.schema.json 은 그대로다. 규칙의 SSoT 는 규약 0.1절이다.

version 은 LLM 이 쓰지 않는다. 프롬프트가 "version 은 쓰지 않는다"고 지시하고 후처리 코드(generator/postprocess.ts)가 상수를 붙인다. 2026-08-20 100회 실행에서 모델도 100/100 정확히 적었으므로 품질 개선이 아니라 책임 분리다. 모델이 판단할 것이 아닌 값은 모델에게 맡기지 않는다.

title 이 필수인 이유는 소비처가 있어서다. 대치가 페이지 제목 슬롯(headSlot, 없으면 첫 headline 유닛의 제목 슬롯)에 이 값을 넣는다(binding.ts 의 pageTitle).

컴포넌트 (Component)

필드 필수 무엇인가
id 아니오 ^[a-zA-Z0-9_-]+$. 생성기는 채우지 않고 파싱 뒤 후처리가 부여한다
type 예 (0.5.1) 생성기가 고른 카테고리. 결합값(대분류.중분류) 45종만 받는다(0.5.2, 0.5.3). 적을 의무는 있고, 검색이 그대로 따를 의무는 없다
alternatives[] 아니오 못 쓸 때 대신 쓸 카테고리를 선호 순서로
params 아니오 표현 선택. 허용 키는 capability 테이블이 카테고리별로 정한다
structure 예 내용. 아래 Structure 참고
decoration 아니오 이 컴포넌트에만 적용되는 꾸밈 선호

type 은 2026-08-29 에 필수가 됐다(정원석). 종전에는 optional 이었고 근거는 "필수로 걸면 '카테고리를 못 고르겠다'가 곧 생성 실패가 된다" 였는데, 뒤집혔다.

근거는 이 단계의 존재 이유다. 설계 원칙 첫째가 "생성 단계에서 최적 semantic category 를 정한다"이다. 어떤 시각 표현형으로 그릴지를 먼저 정해야 거기서부터 표현 방식이 따라 나온다. type 을 비우고 내보내는 것은 그 판단을 검색 쪽으로 미루는 것이고, 시각화 책임을 회피하는 것이다. 원칙 1과 optional 은 처음부터 어긋나 있었고 이 개정이 그것을 맞춘다.

'적을 의무'만 바뀌고 '따를 의무'는 그대로다. 필수가 된다고 type 이 지시가 되지는 않는다. 검색은 여전히 성립 조건과 색인 공급을 보고 fallback 으로 벗어난다. 못 고르겠는 경우의 답은 비우는 것이 아니라 종단 셋 중 하나를 고르는 것이다 — 셋 다 개수 조건만 보므로 어떤 내용이든 하나는 성립한다. 고를 수 없는 입력은 없다.

깨지는 산출물은 없다. 골든셋 v1 의 컴포넌트 470개가 전부 이미 type 을 갖고 있고 PoC 프롬프트도 적으라고 지시하고 있었다. 실제로 지켜지던 것을 규약에 올리는 개정이라 버전은 PATCH 로 올렸다(0.5.0 → 0.5.1).

같은 날 두 번째 개정으로 대분류 단독도 막혔다(0.5.2). type 과 alternatives 는 결합값(대분류.중분류)만 받는다. 종전에는 중분류를 못 정하면 Hierarchy 만 적을 수 있었고 검색이 그 안 전체를 후보로 봤는데, 그것도 같은 성격의 회피다 — 대분류는 그릴 수 있는 표현형이 아니다. Hierarchy 로는 아무것도 못 그리고 그릴 수 있는 것은 Hierarchy.HierarchicalTree 나 Hierarchy.Pyramid 다. 못 정하겠으면 종단 셋 중 하나를 고른다. 대분류 개념 자체는 남는다 — fallback 근접도의 "대분류 일치는 강한 신호"와 규약 6절 셋째의 신호로 값이 아니라 축으로 계속 쓴다. 여기서도 깨지는 산출물이 없다(골든셋의 컴포넌트 type 454개가 전부 결합값, 생성기 프롬프트도 결합값 45종만 준다).

alternatives[] 를 4·5단계에서 켤지는 정해지지 않았다. 스키마는 optional 로 열려 있고 PoC 코드(fallback.ts)는 정적 fallback 순서보다 먼저 보도록 이미 구현했지만, 정적 순서보다 나은 결과를 낸다는 실측이 없어 생성기 정책으로 켜고 끄게 두었다.

구조 (Structure): 네 필드 각각은 optional 이고, 대신 entities 와 dataset 중 적어도 하나라는 조건이 필드 넷에 걸쳐 있다(JSON Schema 의 anyOf). 그래서 아래 표에는 필드별 필수 열을 두지 않는다.

필드 무엇인가 언제 있어야 하나
entities[] 항목 목록. 1개 이상 dataset 이 없으면 필수
relations[] 이 컴포넌트 안 항목 간의 관계 항상 선택
dataset 수치 데이터. Measure 또는 Matrix entities 가 없으면 필수
columns[] entities 로 표를 적을 때 헤더. dataset 없이도 쓴다 항상 선택

항목 (Entity): label 만 필수이고 나머지는 그 카테고리가 읽을 수 있으면 읽는다.

필드 무엇인가 주로 읽는 카테고리
label 항목 이름. 빈 문자열 불허 공통
items[] 하위 설명 목록 공통
value, unit 수치와 단위 BigNumber, Gauge 등
period 기간 표기 Timeline, GanttChart
emphasis 강조 여부 HighlightedMessage
weight 상대 비중 PieChart, DonutChart 계열 fallback
speaker 화자 DialogueChat
author, source 인용 출처 QuoteTestimonial
avatar, organization 인물 정보 ProfileCard
values[] columns 와 위치로 대응하는 나머지 열 값 병합 헤더가 있는 표(2.5.1절 방식)
children[] 하위 항목(같은 컴포넌트 안 계층) Hierarchy 등 (NestedScope 는 2026-09-04 B-34 로 빠졌다)
components[] 중첩된 컴포넌트(컴포넌트 경계를 넘는 계층) 생성기는 아직 채우지 않는다

이름이 중립적으로 지어진 이유가 여기서 드러난다. avatar 와 organization 은 ProfileCard 가, speaker 는 DialogueChat 이, author 와 source 는 QuoteTestimonial 이 읽지만, 필드 이름 자체에는 그 카테고리 이름이 들어가지 않는다. 그래야 다른 카테고리로 fallback 됐을 때도 그 필드가 오류 없이 무시된다.

structure 도 검사한다. 다만 검사하는 주체가 다르다. params 만 검사하고 structure 는 안 하는 것이 아니라, 스키마가 할 수 있는 일은 스키마가 하고 못 하는 일만 capability 가 대신한다. 갈리는 이유는 두 필드의 모양이 다르기 때문이다.

structure params
스키마 모양 필드 이름이 고정 목록이고 zod 가 .strict() 다 열린 객체(Record<string, unknown>). 카테고리마다 키가 달라 스키마에 못 박을 수 없다
어휘 밖 이름을 적으면 스키마가 error 로 막는다. entity.avator 같은 오타나 지어낸 필드가 여기서 죽는다 스키마가 못 잡는다. 무엇을 적어도 통과한다
그래서 capability 가 하는 일 성립 조건 판정 — 개수, dataset 모양, 필수 내용(Gauge 의 measure.value, HierarchicalTree 의 children 등) 허용 키 판정 — 그 카테고리가 선언하지 않은 키를 warning 으로 센다

즉 structure 는 두 겹으로 검사받는다. 필드 이름·타입·필수 조건은 스키마가 전량 보고(entities 1개 이상, label 빈 문자열 불허, entities|dataset 중 하나 등), 카테고리별 최소 요구는 capabilityCheck.ts 가 본다. params 가 검사를 한 겹밖에 못 받는 쪽이고, capability 의 키 검사는 스키마가 못 하는 일의 대역이다.

진짜로 검사하지 않는 것은 하나뿐이다. "그 카테고리가 읽지 않는 내용 필드가 실려 있는 것" — List 에 avatar 를 적는 경우다. 이것은 설계 원칙 셋 중 "선언되지 않은 것은 무시한다"에 따라 오류가 아니고, fallback 때 P3 으로 items 맨 뒤에 붙어 보존된다. params 의 대응 상황(그 카테고리가 안 읽는 키)이 warning 인 것과 비대칭인데, 갈리는 근거는 사라지느냐다. 안 읽는 params 는 P2 로 버려지므로 잘못 적으면 의도가 조용히 소멸한다. 안 읽는 내용 필드는 P3 으로 보존되므로 소멸하지 않는다. 검사는 조용히 사라지는 쪽에 건다.

남는 물음도 닫혔다 (B-33, 2026-09-05). List 에 avatar 를 적은 것은 내용이 안 사라질 뿐 생성기가 카테고리를 잘못 골랐다는 신호다(그 내용이면 ProfileCard 였어야 한다). 사후에 info 로 세는 방식은 기각했다 — 정확하게 생성되지 않으면 semantic 정보 자체가 이상하게 만들어지고, 사후 처리로는 이미 죽은 구조(avatar 가 표현하려던 사람 카드)를 못 살린다. 대신 Summary 생성기 쪽에서 원천 차단한다 — B-11 패턴(카테고리별 capability 에서 뽑은 생성용 스키마를 structured output 에 제약)을 내용 필드에도 확장해 List 를 고른 순간 avatar 를 애초에 못 쓰게 하고, 생성 검증 단계의 strictCapability 는 기본값을 켜 카테고리 불일치를 재생성으로 되돌린다. 이 결정은 생성기 스코프 한정이다 — 색인 검색·fallback(B-30 등 대치 경로)은 이미 존재하는 디자인을 다루므로 재생성이 불가능해 흡수 철학이 그대로 유지된다.

공용 zod 스키마의 한계는 type 쪽에도 있다. type: KPIKeyMetric.Gauge 인데 measure 가 없어도 스키마는 통과시킨다 — 그 구멍을 메우는 것이 아래 표의 등급 판정이다.

판정 등급 무엇을 뜻하나
자기 structure 로 성립한다 통과 정상
자손 컴포넌트(entity.components)를 합치면 성립한다 info 데이터는 있고 적힌 자리가 다르다
fallback 그래프로 성립하는 도착지가 있다 warning 설계상 정상. strictCapability 를 켤 때만 실패로 올린다
마지막 fallback 셋까지 성립하지 않는다 error 어떤 카테고리로도 못 그린다. 항상 검증 실패
그 카테고리가 선언하지 않은 params 키 warning 허용 키는 capability.json 의 reads.params 가 정한다

등급을 셋으로 나눈 이유는 "Gauge 인데 dataset 이 없다"가 그 자체로 결함이 아니기 때문이다. type 은 권고이고, 성립하지 않으면 검색이 fallback 으로 내리는 것이 정상 동작이다. 문서화도 되어 있다. 규약 5절·6절과 capability-table.md 2절·3절이 조건을, 코드 쪽은 capabilityCheck.ts 머리말이 이 등급 나눔의 근거를 담는다. 45종 전량과 코드의 대조는 capability.test.ts 가 한다.

관계 (Relation)

필드 무엇인가
type intersects(교집합) 또는 compares(비교). 이 둘만 허용
of[] 관계로 묶이는 entity 의 label. 파싱 뒤 id 로 바뀐다
desc 그 관계에만 속하는 설명. 예를 들어 두 원의 교집합 영역 텍스트

관계 어휘를 새로 만드는 기준은 하나다. 구조로는 적을 수 없는 제3의 내용을 가진 관계만 어휘로 만든다. 배열 순서나 entity 내부 필드, children 으로 이미 적히는 것은 별도 관계로 만들지 않는다. 이 기준으로 "전후 변화"(화살표에 얹는 텍스트가 실사용에서 관측되는지 미확인, 보류)와 "문제와 해결/원인/결합"(entity 하나로 이미 표현 가능, 기각)이 걸러졌다.

데이터셋 (Dataset): 두 모양(shape) 중 하나다.

모양 필드 무엇인가
measure (단일 수치) value, unit, min, max, target, delta, thresholds[] BigNumber, Gauge 처럼 값 하나가 핵심인 카테고리
matrix (행렬) columns[], rows[][] 표와 차트 계열 전부. 표현 방식은 데이터가 아니라 type 이 정한다

matrix 의 첫 열은 항상 범주 축이다. column.role 이 category(범주), value(수치), x/y/size(산점도·버블), start/end(간트), date(달력)로 각 열의 성격을 밝히고, 생략하면 첫 열은 category, 나머지는 value 로 간주한다.

병합·계층 헤더가 있는 표는 matrix 대신 entities 를 쓴다(2026-08-06 추가). 병합 셀이 있는 실사용 표가 matrix 성립 조건에 걸려 통째로 탈락하는 사례가 나와서, ListingTable 과 ComparisonTable 두 카테고리에만 대안 구조를 열었다. columns(헤더 정의)와 entities(행)를 함께 쓰고, entity.label 이 범주축(첫 열), entity.values[] 가 나머지 열이다. 상위 범주가 하위 행을 거느리는 부모-자식 구조는 entity.children[] 로 적는다.

이 경로가 담는 병합은 "첫 열의 세로 병합" 하나다. columns[] 는 평평한 한 줄이고 Column 에는 병합을 적을 자리(colspan 같은 것)가 없다. 그래서 헤더가 2단인 표(가로로 병합된 상위 헤더 아래 하위 헤더가 붙는 모양)는 질의 쪽에서 표현할 수 없다. 지금 45종 어느 차트·표 카테고리도 그런 구조를 읽지 않기 때문에 자리를 열지 않았다. 세로 병합 중에서도 담기는 것은 범주축 하나가 하위 행을 거느리는 계층형뿐이고, 축이 둘로 갈라지는 독립적 rowspan(주축과 무관하게 특정 열 하나만 여러 행에 걸쳐 묶이는 것, 예를 들어 강의 4행 중 "주말반" 열만 2행씩 묶이는 표)은 트리 하나로 담기지 않아 범위 밖이다.

아래 예시가 바로 그 계층형 세로 병합이다. 첫 열의 PROJECT 01 셀 하나가 아래 두 행(성장률, 매출)에 걸쳐 있는 표이고, 그 병합을 entity.children[] 로 적는다. columns 네 개는 병합 없는 평평한 헤더 한 줄이다.

yaml
version: "0.5.4"
title: 분기별 프로젝트 성과
components:
  - type: AttributeComparison.ComparisonTable
    structure:
      columns:
        - { label: 항목 }
        - { label: 1Q }
        - { label: 2Q }
        - { label: 3Q }
      entities:
        - label: PROJECT 01
          children:
            - { label: 성장률, values: ["35%", "67%", "55%"] }
            - { label: 매출, values: [14000, 25000, 18000] }

이 경로로 채워진 표는 matrix 를 요구하는 카테고리(Quadrant, GanttChart, Calendar 등)의 fallback 도착지가 되지 않는다. 억지로 matrix.rows 로 접으면 병합 의미가 조용히 사라지기 때문이다.

꾸밈구조 (Decoration)

필드 대응하는 RLSC role 값
ornament Element.Decoration, LayoutContainer.Decoration, Element.Separator, LayoutContainer.Separator (Separator 흡수는 2026-09-05, B-21) prefer(선호), indifferent(무관, 기본값), avoid(회피)
background Element.Background, LayoutContainer.Background 위와 동일

require(필수)는 없다. 꾸밈을 필수로 걸면 후보가 0이 되어 전부 List 로 강등되기 때문이다. 해석은 축마다 따로 하고 구체적인 쪽을 우선한다. 컴포넌트 → page → 카테고리 기본값 → 시스템 전역 기본값 → indifferent 순이다.

값의 모양은 셋으로 갈라 판정한다(2026-08-29 확정). 셋을 한 덩어리로 묶으면 "객체는 안 된다"와 "점수는 안 된다"가 뒤섞인다.

모양 판정
enum (prefer·indifferent·avoid) 4단계와 5단계가 쓰는 것이 이것이다. 지금 규약에 있는 형태다
객체 (축 아래에 구체적인 값을 담는 것) 나중 단계의 자리다. 기각이 아니라 4·5단계 범위가 아닐 뿐이고, 그 단계에서 무엇을 담을지는 정하지 않았다
수치 점수 (ornament: 0.8 같은 가중치) 앞으로도 두지 않는다. 값에 점수를 매기는 것이 의미가 없다고 판단했다. 채점 코드를 건드리지 않기로 한 결정과 어긋나고 entity.weight(number)가 다른 뜻으로 이미 있다는 종전 근거도 그대로다

그래서 "prefer 가 몇 점인가"는 물음 자체가 성립하지 않는다. 남는 것은 점수가 아닌 형태로 검색이 이 선호를 순위에 어떻게 반영하는가, 그리고 fallback 보다 먼저 보는가다. 값 말고 늘어날 수 있는 것은 축의 개수다 — Element.Separator 를 세 번째 축으로 열지가 미결이고(2절 마지막), 새 축을 열 때는 기본값을 indifferent 로 두면 기존 동작이 바뀌지 않는다.

yaml
version: "0.5.4"
title: 분기 실적
decoration:                    # page 수준. 아래 컴포넌트의 기본값이 된다
  ornament: prefer
  background: prefer
components:
  - type: Enumeration.List     # 물려받는다. 둘 다 prefer
    structure:
      entities: [ { label: 신규 계약, items: [12건] } ]
  - type: ProcessFlow.Steps
    decoration:
      background: indifferent  # 이 축만 되돌린다. ornament 는 prefer 그대로다
    structure:
      entities:
        - { label: 계약 검토 }
        - { label: 체결 }

1단계에서 생성기는 decoration 과 entity.components[], page 수준 relations 를 채우지 않는다. 스키마에 자리만 열려 있고 소비할 주체(요소 대치)가 아직 동작하지 않기 때문이다.

page 수준에 있는 것은 relations 와 decoration 둘뿐이고, "페이지를 어떻게 배치하는가"는 규약 밖이다. page 수준 relations 는 컴포넌트 블록끼리의 의미 관계를 적을 자리이지 배치 지시가 아니고(그래서 컴포넌트 블록에 optional id 를 함께 열어 두었다, TODO B-15), decoration 은 꾸밈 선호다.

page layout 규약은 5단계 범위에도 없다(2026-08-29 확정). 5단계가 뽑는 것은 노드 태그와 properties 와 구조 대응이지 배치가 아니다. 다만 계획이 없는 것이 아니라 미룬 것이다 — 배치를 다루자는 결정은 5단계 이후로 잡혀 있고, 그때까지 질의 쪽 스키마에 좌표나 그리드를 넣지 않는다. 지금 넣으면 배치를 실제로 정하는 쪽(검색이 고른 컴포넌트와 그것이 속한 템플릿)과 두 벌로 갈린다.

그 전에 풀려야 할 것 둘이 먼저다. 복합 구조 fallback 에서 N3·N4 를 반복하면 최상위 컴포넌트 수가 늘어나는데 그 상한이 없고(B-32), 여러 조각으로 쪼갠 검색 결과를 한 페이지로 조립하는 오케스트레이션 코드도 없다(I-11). 개수와 조립이 정해지지 않은 상태에서 배치 규약을 먼저 쓸 수 없다.

YAML 예시 셋#

관계(relations)를 쓰는 예시. 벤 다이어그램처럼 두 항목의 교집합에 별도 텍스트가 붙는 경우다.

yaml
version: "0.5.4"
title: 장수기업 HR의 핵심 과제
components:
  - type: RelationalComparison.VennDiagram
    structure:
      entities:
        - { label: 전통, items: [유산, 정체성, 신뢰] }
        - { label: 변화, items: [혁신, 적응, 미래] }
      relations:
        - type: intersects
          of: [전통, 변화]
          desc: 두 요소를 조화롭게 관리하는 것이 장수기업 HR의 핵심

of 에는 entity 의 label 을 그대로 적는다(파싱 뒤 id 로 치환). desc 는 어느 entity 에도 속하지 않는 내용이라서 관계로 뽑아 적는다.

params 와 alternatives 를 함께 쓰는 예시.

yaml
version: "0.5.4"
title: 매슬로 욕구 5단계
components:
  - type: Hierarchy.Pyramid
    params: { apex: last }
    alternatives:
      - Progression.Staircase
      - Enumeration.List
    structure:
      entities:
        - { label: 생리적 욕구 }
        - { label: 안전 욕구 }
        - { label: 사회적 욕구 }
        - { label: 존경 욕구 }
        - { label: 자아실현 }

apex(어느 쪽이 꼭대기인가)는 안 적어도 된다. 기본값이 last 다. alternatives 는 생성기가 입력 내용을 보고 미리 좁혀 두는 대안 목록이고, 적으면 검색이 정적 fallback 순서보다 먼저 참고한다.

type 을 문자열 하나로 적는 이유. 대분류가 중분류의 이름공간이다. Pyramid 는 Hierarchy 아래에만 있고 BarChart 는 MagnitudeRanking 아래에만 있어서, Hierarchy.Pyramid 처럼 점으로 이어 적는다. 2026-08-29 부터 스키마 semanticCategory 는 결합값 45종만 받는다. 대분류 단독은 값이 아니다. Hierarchy.BarChart 처럼 어긋난 조합은 검증에서 걸린다. 저장 키(DB, comp-sign)는 밑줄 표기(Enumeration_List)를 쓰고, 검색 모듈이 받는 즉시 점을 밑줄로 바꾼다. 이 변환은 코드 한 곳에만 있다.

Capability(성립 조건) 테이블의 구조#

docs/projects/mordor/visual-summary/spec/capability-table.md 가 semantic tag 45종 전량에 대해 세 가지를 정의한다. 성립 조건(이게 없으면 그 종류로 배치가 불가능한 것), 선택 속성과 기본값(안 적어도 되는 것), fallback 순서(그 종류가 색인에 없을 때 물러날 곳)다.

45종이라는 숫자의 경위. v7 semantic taxonomy(단어장)는 49종이다. 여기서 지도 전용 4종(Distribution, Location, Highlight, Flow)을 뺀 45종이 이 표의 범위다. 지도 데이터가 있어야 성립하는 유형이라 structure 로 표현할 대상이 아니고 Summary 생성 대상에서도 뺀다(2026-08-17 스쿼드 확인, 2026-08-29 규약 확정). 이전에 41종으로 적혔던 것은 페이지 역할 4종(Opening, TableOfContents, SectionDivider, Ending)을 빼고 세던 값이다. 그 4종은 어휘와 성립 조건 모두 45종 안에 들어 있다. C-4 가 정하는 것은 뽑힌 뒤의 템플릿 공급이지 성립 조건이 아니다.

성립 조건은 최소로 줄인다(2026-08-02 결정). 필수/선호/무시 세 등급으로 나누는 대신, 성립 조건은 배치 자체가 불가능한 것만 남기고 나머지는 기본값을 가진 선택 속성으로 옮겼다. 기본값은 표현 선택에만 준다. 피라미드에서 어느 쪽이 꼭대기인지(apex)는 표현 선택이라 기본값을 줘도 되지만, 숫자가 얼마인지(value)는 내용이라 기본값을 주면 없는 데이터를 지어내는 셈이 되므로 성립 조건에 남긴다.

대표 카테고리 예시다. 45종 전량은 5절 부록 C 에 있다.

카테고리 개수 조건 내용 조건
Enumeration.List 1 이상 없음
AttributeComparison.Versus 정확히 2 없음
AttributeComparison.ComparisonTable 열 2 이상 matrix 또는 entities+columns
RelationalComparison.SWOT 정확히 4 없음
RelationalComparison.VennDiagram 2 이상 3 이하 (실측) 없음
ProcessFlow.Steps 2 이상 없음
RelationalComparison.Quadrant 1 이상 matrix 필수(항목마다 x, y)

"실측" 표시가 붙은 값은 배치 엔진(jari) 0.12.0 의 LAYOUT_CONTENT_CONTRACTS 에서 그대로 가져온 것이라 근거가 확실하다. 표시가 없는 값은 단어장 정의문에서 유도한 설계 판단이다.

fallback(강등) 사다리#

fallback 은 요청한 카테고리가 성립하지 않거나 검색 결과가 없을 때 다음 카테고리로 내려가는 규칙이다. 규약 5절이 정한 다섯 가지다.

  1. 카테고리의 성립 조건을 못 채우면 그 카테고리가 후보에서 빠진다. 성립 조건은 개수 범위와 내용 존재만 본다.
  2. 후보가 없으면 fallback 순서를 따라 내려간다. 상한을 두지 않는다. 근접도 순서만 지키면 몇 칸이든 허용한다(2026-08-17 개정. 종전에는 "최대 두 칸"이었다가 폐지됐다).
  3. 마지막 fallback 은 셋 중 하나다. entities 가 2개 이상이면 Enumeration.List, 1개이고 여러 문장이면 TextBasedCommunication.LongFormTextLayout, 1개이고 한 문장이면 TextBasedCommunication.HighlightedMessage 다. 셋 다 개수 조건만 보므로 언제나 하나는 성립하고, 그래서 빈 결과가 사용자에게 가지 않는다.
  4. 선택된 카테고리가 못 읽는 내용은 버리지 않고 옮긴다. 어디로 옮기는지는 다음 절의 projection 규칙 8가지가 정한다.
  5. type 이 가리킨 카테고리의 성립 조건을 structure 가 못 채우면 구조가 우선이다. 그 카테고리는 후보에서 빠지고 fallback 순서로 넘어간다.
  6. 질의가 중첩이나 병치로 노드를 여럿 가지면, 1번부터 5번과 별개로 어느 노드를 먼저 포기하는지를 정해야 한다. 순서는 다섯 단계이고 다음 절에서 다룬다(2026-08-28 신설).

[그림: fallback 전 과정: 단일 노드 강등 사다리와 복합 구조 N1~N5, 그리고 엣지마다 붙는 projection] ↗

그림을 읽는 법. 갈림길은 하나다. 중첩이 없으면(L1·L2) 아래 줄(강등 사다리)만 돌고 — L2 는 최상위 원소마다 그것을 독립으로 돌린다 — 중첩이 있으면(L3) 위 줄(N1~N5)이 먼저 돌아 질의를 조각으로 나눈 뒤 조각마다 아래 줄이 다시 돈다. 두 줄은 예산이 따로다(N-R4). 사슬이 막히는 이유는 둘인데(요청 카테고리의 성립 조건 미충족, 색인 공급 없음) 아래 줄의 첫 칸과 셋째 칸에 각각 걸린다. 앞의 것은 대개 생성 결함이고 규약이 그것을 오류 대신 fallback 으로 흡수한다 — 근거는 바로 아래 절이다. 어느 경로로 가든 끝은 종단 셋이라 빈 결과가 없고, 칸을 하나 옮길 때마다 그 엣지의 projection 이 붙어 내용은 옮겨지고 params 만 버려진다. 위 줄의 N1 과 N2 는 이종중첩에서는 지금 정책상 언제나 비어 있어(D-7), 실질적으로 N3 에서 시작한다.

사슬이 막히는 이유 둘과, 한 단계씩 따라간 예시 셋#

말로만 읽으면 "카테고리를 내린다"가 무엇을 하는 일인지 잡히지 않는다. 실제로 무엇이 판정되고 무엇이 옮겨지는지 세 경우로 따라간다. 앞의 둘이 사슬이 막히는 이유 둘에 각각 대응하고, 셋째가 노드가 여럿일 때다.

먼저 하나를 분명히 해 둔다. 요청한 type 의 성립 조건을 그 컴포넌트의 structure 가 못 채우면, 그것은 잘못 생성된 Visual Summary 다. 생성기 프롬프트가 "정의의 개수 조건과 dataset 요구를 채울 수 있는지 먼저 확인하고, 못 채우면 정의의 '못 쓸 때 대안' 순서를 따른다"고 지시하기 때문에, 못 채우는 type 을 적은 것은 지시 위반이다. 그런데도 규약은 이것을 검증 오류로 두지 않는다. 이유가 셋이다.

# 왜 오류로 하드 실패시키지 않나
1 type 은 권고다(규약 6절). 최종 결정은 검색이 하므로, 권고가 틀렸다는 것만으로 문서 전체를 버릴 근거가 못 된다
2 데이터가 다른 자리에 적혀 있을 수 있다. entity.components[] 중첩 안에 있으면 자손을 합쳐 성립하는 경우가 있어, 이건 결함이 아니라 "적힌 자리가 다른 것"이다
3 빈 결과보다 낫다. 하드 실패시키면 재생성 루프를 돌아야 하고 그것마저 실패하면 사용자에게 아무것도 못 준다. fallback 으로 흡수하면 내용은 다 살아서 나간다

대신 생성 품질 신호로 센다. 코드(capabilityCheck.ts)가 이 경우를 warning 등급으로 세고(capabilityFallbackCount), strictCapability 를 켜면 검증 실패로 올린다. 어떤 카테고리로도 못 그리는 경우만 항상 error 다. 즉 기본 설정에서는 재생성으로 고치지 않고 fallback 이 흡수한다. 이것이 얼마나 자주 일어나는지는 아직 실측이 없다.

성립 미충족이 결함이 아닌 경우도 있다. 도착지에서의 미충족이 그것이다. 아래 예시 1 의 마지막 칸에서 HighlightedMessage 가 "정확히 1"인데 항목이 셋이라 P8 로 접어 받는데, 이건 생성기가 틀린 게 아니라 강등 경로가 원래 그렇게 도는 것이다. type 을 아예 안 적는 것은 2026-08-29 부터 스키마 위반이다 — 종전에는 optional 이라 결함이 아니었다.

예시 1. 생성기가 성립하지 않는 카테고리를 골랐다 — Gauge 인데 measure 가 없다.

yaml
components:
  - type: KPIKeyMetric.Gauge
    structure:
      entities:
        - { label: 서울, value: 82, unit: "%" }
        - { label: 부산, value: 61, unit: "%" }
        - { label: 대구, value: 47, unit: "%" }

생성기는 달성률이니 게이지라고 판단했다. 그런데 Gauge 의 성립 조건은 dataset.shape: measure 가 필수이고 measure.value 도 필수다(부록 C). 이 산출물은 수치를 entity.value 로 적었지 dataset 을 적지 않았다. 위에 적은 대로 이것은 생성 결함이다. 게다가 값이 셋이라 게이지(값 하나가 핵심인 카테고리) 자체가 애초에 어울리지 않는 입력이었을 가능성이 높다. 구조가 우선이므로 Gauge 는 후보에서 빠지고(규칙 5번), 검증은 이 컴포넌트를 warning 하나로 세면서 통과시킨다. 여기서부터 자기 행을 따라간다.

칸 도착 판정 무엇이 일어나나
1 KPIKeyMetric.BigNumber 성립 개수 1 이상이고 필수 내용이 entity.value 인데 셋 다 갖고 있다. 여기서 멈춘다

걸리는 규칙은 P1 P2 P3 이다. entities 셋은 순서까지 그대로 간다(P1). 버릴 params 는 없다 — KPIKeyMetric 둘은 params 키를 아예 선언하지 않는 16종에 속하기 때문에 P2 가 할 일이 없다. 이 질의에 dataset 이 있었다면 min, max, target, thresholds 는 BigNumber 가 안 읽는 값이라 각 entity 의 items 맨 뒤에 이름: 값 으로 붙었을 것이다(P3). 잃는 것은 "목표 대비 어디쯤인가"라는 의미 하나뿐이고 숫자와 라벨은 하나도 안 없어진다.

같은 질의에서 색인이 얕으면 사슬이 더 간다. BigNumber 태그를 단 컴포넌트가 색인에 하나도 없으면 다음 칸으로 내려간다.

칸 도착 판정 무엇이 일어나나
2 TextBasedCommunication.HighlightedMessage P8 로 성립 개수 조건이 "정확히 1" 인데 셋이라, 초과분 둘을 마지막 항목의 items 로 접어서 받는다(P8). 이 칸의 미충족은 생성 결함이 아니라 강등 경로가 원래 그렇게 도는 것이다

PictogramChart·BarChart 는 Gauge 의 fallback 경로에 없다 (B-30, 2026-09-05 확정). 단일 값(measure)과 매트릭스(matrix)는 형태가 근본적으로 달라서 서로 변환하는 규칙을 두지 않기로 했다 — BigNumber가 실패하면 곧바로 HighlightedMessage로 간다.

예시 2. 색인 공급 없음 — 구조는 멀쩡한데 그 태그를 단 컴포넌트가 없다.

yaml
components:
  - type: AttributeComparison.Versus
    structure:
      entities:
        - { label: 재택근무, items: [출퇴근 시간 0분] }
        - { label: 사무실 근무, items: [즉시 소통] }
      relations:
        - type: compares
          of: [재택근무, 사무실 근무]
          desc: 협업 밀도에서 갈린다

Versus 의 성립 조건은 "정확히 2" 하나이고 이 산출물은 정확히 2다. 생성은 흠이 없고 구조도 통과한다. 예시 1 과 다른 점이 여기다 — 이쪽은 생성기를 탓할 것이 없다. 그런데 Versus 태그를 단 컴포넌트가 색인에 없으면(질의 쪽 비중은 3.11% 인데 색인 쪽 공급은 별개다) 후보가 0이 되고, 이유가 구조가 아니라 공급이어도 사슬은 똑같이 내려간다. Versus 행은 ComparisonTable → List 다.

칸 도착 무엇이 일어나나
1 AttributeComparison.ComparisonTable entities 가 matrix 의 행으로 펴지고(P6), 관계의 desc("협업 밀도에서 갈린다")가 of 첫 항목인 재택근무의 items 맨 뒤로 간다(P7)
2 Enumeration.List 여기도 안 되면 종단이다. entities 가 2개 이상이므로 종단 셋 중 List 가 성립한다. desc 는 여기서도 P7 로 보존된다

두 이유는 문서에서 다르게 다뤄진다. 구조 조건 미충족은 카테고리마다 다르므로 부록 G 의 행별 이유에 적혀 있고, 색인 공급 없음은 어느 후보에나 걸릴 수 있어 행별로 따지지 않는다. 대신 모든 사슬의 끝을 항상 성립하는 종단 셋으로 맺어 대비한다. 유형별 색인 공급 실측은 아직 없다 — 부록 C 의 비중은 질의(수요) 분포이고 공급 근거가 아니다.

예시 3. 노드가 여럿 — 어느 노드부터 포기하나. 위 둘은 컴포넌트 하나 안의 일이고, 질의가 중첩(L3)이면 그전에 "어느 노드를 먼저 포기하나"를 정해야 한다. 다음 절의 N1~N5 가 그것이고, 한 단계씩 따라가는 예시는 6절 부록 7.5 에 있다.

이 사다리는 2026-08-13 리뷰에서 v2 로 고쳐졌다. v1 초안(김수현, 허재영, Google Sheet)을 정원석이 검토해 원칙 넷을 정했고, 그 넷이 지금 표의 모양을 정한다.

# 원칙
1 List 금지 원칙을 채택하지 않는다. "페이지역할, 단일메시지, 인물, KPI 11종은 List 폴백 금지" 제안(류민, 2026-08-05 계통도 분석)은 참고 자료로만 남긴다. 대안 없는 금지는 쓰지 않는다. 내용이 유실되거나 대치 자체가 안 되는 것이 List 로 대치되는 것보다 나쁘다고 판단했다
2 우선순위는 근접도로 정한다. 의미가 가까운 유형을 먼저 둔다. 의미의 근거는 단어장 정의문이 공유하는 핵심 개념이다. 대분류 일치는 강한 신호지만 배제 조건은 아니다. Funnel 과 Steps 는 대분류가 다르지만 "단계"를 공유해 가깝다고 본다. 반대로 개수나 축 형태가 닮았다는 구조적 이유만으로는 순서를 정하지 않는다
3 모든 행이 반드시 무언가로 끝난다. v1 에서 완전히 공란이던 6행(Cycle, FlowChart, Opening, SectionDivider, Ending, Flow)을 채웠다. 서로만 가리켜 상대가 없으면 둘 다 막히던 상호참조 4쌍(BigNumber 와 Gauge, Pyramid 와 NestedScope, Calendar 와 GanttChart, Location 과 Highlight)에도 각자 안전망을 붙였다
4 대체 후보 수 상한을 폐지한다. 종전 "최대 두 칸"은 실측 근거 없는 설계 판단이었다. 근접도 순서만 지키면 몇 칸이든 허용한다

사슬이 막히는 이유가 둘이고, 표는 하나만 행별로 적는다. '구조 조건 미충족'(성립 조건을 내용이 못 채운다)은 카테고리마다 다르므로 행별 이유에 적혀 있다. '색인 공급 없음'(성립 조건은 채우는데 그 유형으로 태깅된 컴포넌트가 색인에 없거나 얕다)은 어느 후보에나 걸릴 수 있어 행별로 따지지 않는다. 대신 모든 사슬의 끝을 항상 성립하는 종단 셋으로 맺어 대비한다. 유형별 색인 공급 실측은 아직 없다. 부록 C 의 비중은 질의(수요) 분포이고 공급 근거가 아니다.

서로를 가리키는 쌍이 있다. ListingTable과 ComparisonTable, Steps와 Timeline, Pyramid와 NestedScope, BigNumber와 Gauge, BarChart와 LineAreaChart, PieChart와 DonutChart, DialogueChat와 QuoteTestimonial, ProfileCard와 ResumeCV 여덟 쌍이 서로를 후속 우선순위의 첫 자리로 가리킨다. 순환처럼 보이지만 순환이 아니다. 검색 모듈이 "자기 행만" 따라가므로 도착 카테고리의 행으로 넘어가는 일이 없다. 예를 들어 Steps 의 fallback 순서는 Timeline → Staircase → List 이고 Timeline 의 fallback 순서는 Steps → Staircase → List 인데, Steps 에서 출발해 Timeline 후보가 없으면 다음은 Staircase 이지 Timeline 행에 적힌 Steps 로 되돌아가지 않는다.

대표 fallback 체인 몇 가지다. 45종 전량은 5절 부록 G 에 있고, 엣지 130개 전량과 엣지마다 발동하는 projection 규칙은 부록 H 다.

카테고리 fallback 순서 한 번 따라갈 때 잃는 것
Enumeration.List ListingTable 행과 열의 대응. List 자체는 entities 1개 이상이면 항상 구조적으로 성립하지만, 색인에 후보가 없으면 물러난다
ProcessFlow.Steps Timeline → Staircase → List Timeline(시간축), Staircase(상승 방향)를 차례로 잃는다
Hierarchy.Pyramid NestedScope → Staircase → List NestedScope와 상호참조라 각자 안전망이 없으면 둘 다 막힌다
KPIKeyMetric.Gauge BigNumber → HighlightedMessage → List 목표 대비 위치라는 의미가 옅어지다가 강조형으로 끝난다. PictogramChart·BarChart는 measure↔matrix 변환 규칙을 안 만들기로 해 후보에서 빠졌다(B-30). HighlightedMessage는 개수 초과 시 성립하지 않아(P8 폐기, B-27) List를 안전망으로 더했다
RelationalComparison.SWOT List → ListingTable 내부/외부, 긍정/부정의 2축 배치. Quadrant나 ComparisonTable 경유 대신 List 를 1순위로 확정했다
TimelineSchedule.GanttChart Timeline → Calendar → ListingTable → List 기간의 길이와 병렬 진행. 같은 TimelineSchedule 대분류인 Calendar 를 안전망으로 먼저 거친다

차트 계열은 별도의 작은 순환 구조를 이룬다. PieChart, TreeMap, PictogramChart, StackedBarChart, ComboChart, RadarChart, BubbleChart 가 모두 BarChart 로 모이고, BarChart 와 LineAreaChart 는 서로를 가리키며, 최종적으로 ComparisonTable 을 거쳐 List 로 끝난다.

엣지 수의 변천. fallback 그래프의 엣지(카테고리 대 카테고리 연결) 수는 여러 차례 다시 계산됐다. 2026-08-04 에 51개에서 75개로(행을 경로로 잘못 읽어 누락된 엣지 25개를 보정), 2026-08-17 에 75개에서 111개로, 다시 125개로 늘었고, 2026-08-21 ComboChart 개정으로 131개가 됐다. 2026-09-05에 B-30으로 measure↔matrix 엣지 4개가 빠지며 127개로, 이어서 B-27로 P8을 폐기하면서 HighlightedMessage가 종단이던 세 행(BigNumber·Gauge·QuoteTestimonial)에 안전망 List를 더해 130개로 늘었다. 현재 값은 130개이고 부록 H 가 전량이다.

복잡도 레벨 셋: 질의가 어디까지 복잡해질 수 있나#

앞 절까지의 모양들은 Visual Summary 하나가 가질 수 있는 위상의 범위로 셋으로 묶인다. 그 범위를 가리키는 이름이 복잡도 레벨이고, SSoT 는 규약 8.8절이다. 레벨은 산출물 한 건의 모양을 분류하는 이름이 아니라 생성기에 허용된 범위의 이름이다. 그래서 누적이고(L1 ⊂ L2 ⊂ L3), L3 를 켠 생성기가 컴포넌트 하나짜리 Visual Summary 를 내는 것은 정상이다.

'질의'라고 부르지 않는다. 복잡도는 생성기 산출물(Visual Summary) 의 성질이고, 그것이 검색용 질의로 바뀌는 것은 질의 변환기(query.ts)를 거친 뒤다. 이 문서가 '질의 쪽'이라고 쓸 때는 색인 쪽과 대비하는 진영 이름이지 변환기 산출물이 아니다.

레벨 이름 최상위 components[] entity.components[]
L1 단일 컴포넌트 (single component) 길이 1 안 쓴다
L2 병치 위상 (juxtaposition topology) 여럿 허용 안 쓴다
L3 전체 위상 (all topology) 여럿 허용 쓴다

레벨을 가르는 축은 하나다. 컴포넌트 경계를 넘는 계층(entity.components[])을 쓰느냐다. entity.children[](같은 컴포넌트 안 계층)은 어느 레벨에서나 쓴다. 그것은 한 컴포넌트 내부의 내용 구조이지 위상이 아니다. L1 과 L2 를 가르는 것은 그다음 축인 최상위 원소 수다.

레벨 fallback 이 무엇을 쓰나 그 레벨을 켜려면 무엇이 있어야 하나
L1 앞 절의 강등 사다리만. 복합 순서는 쓸 일이 없다 없다. 지금 도는 것이 이것이다
L2 사다리가 최상위 원소마다 독립으로 돈다. 벗길 부모가 없어 N1~N5 는 쓰지 않는다 조각마다 검색을 호출하고 결과를 한 페이지로 조립하는 오케스트레이션(I-11). 색인의 후보 단위가 컴포넌트 하나라 한 후보가 최상위 원소 둘을 함께 담는 일이 없다
L3 다음 절의 N1~N5 전부와 규칙 여섯 위의 것 전부에 더해, 생성기가 entity.components[] 를 채우는 정책, 중첩 후보의 대치 로직(D-7 이 "기존 조합 컴포넌트와는 별개의 대치 로직이 필요하다"고 적은 것), N3·N4 반복으로 최상위 원소가 늘어날 때의 상한(B-32)

생성기가 둘이고 지금 레벨이 서로 다르다. 섞어 읽으면 "지금 어디까지 되는가"가 뒤집힌다.

생성기 지금 레벨 근거
Summary 생성기(실서비스, 허재영·황현진) L2 까지 규약 1절이 "생성기는 entity.components[] 를 채우지 않는다"고 정한 그 생성기다. 실사용 로그 10건 실행에서 입력 10건이 컴포넌트 15개를 냈으므로 L2 는 관측된다(4절)
PoC 생성기(정원석, MOR-1910) 이미 L3 프롬프트에 ## 이종 컴포넌트 절이 있어 포함을 entity.components 로 적으라고 지시하고, 골든셋 v1 에 중첩 산출물이 들어 있다

그래서 "L3 는 자리만 열려 있다"는 말은 생성 쪽에는 더 이상 맞지 않는다. 비어 있는 것은 그다음 두 칸 — 조각마다 검색을 호출하고 결과를 조립하는 오케스트레이션(I-11)과 중첩 후보의 대치 로직(D-7)이다. 4단계 실서비스에서 어느 레벨까지 켤지는 정해진 바 없다.

레벨을 통제하려면 프롬프트가 블록으로 떼어져야 한다. 레벨을 올리는 지시가 흩어져 있으면 "L2 로 내려라"가 한 군데를 지우는 일이 아니라 문장을 골라내는 일이 되고, 그때부터 프롬프트 판본마다 실제 레벨이 달라진다. 지금은 절반쯤 되어 있다 — 위상 지시가 ## 이종 컴포넌트 한 절에 뭉쳐 있어 물리적으로는 한 덩어리지만, ## 컴포넌트 블록 절에 "entity.components 는 포함 관계를 적을 때만 쓴다" 한 줄이 밖에 남아 있고, 포함(L3)과 병치(L2)가 한 절 안에 섞여 있어 레벨 경계로 갈리지 않으며, 프롬프트 조립(fillPrompt)이 {{TOKEN}} 치환만 해서 블록을 넣고 뺄 수단 자체가 없다. 필요한 모양과 검사 방법은 규약 8.9절에 적었다. 아직 구현되지 않았다.

'병치'라는 말이 두 층에서 쓰인다. 질의 쪽 병치(여기의 L2)는 최상위 components[] 원소가 여럿인 것이고 컴포넌트 경계가 이미 갈려 있다. 색인 쪽 Juxtaposition(규약 8.7절 위상 관계)은 이종중첩으로 판정된 한 컴포넌트 안에서 서로 다른 semantic category 의 시각 영역이 겹치지 않고 나란히 놓인 것이다. 방향이 반대다. 앞은 나눠서 찾는 쪽이고 뒤는 이미 하나로 뭉쳐 있는 것을 부르는 이름이다. L2 질의의 조각들이 각각 검색돼 한 페이지에 놓이면 겉모습은 닮지만 후보 단위가 다르므로 같은 것이 아니다.

레벨과 카테고리 강등 칸 수는 서로 다른 예산이다. L3 질의라고 해서 카테고리를 더 많이 내릴 수 있는 것도, 덜 내려야 하는 것도 아니다. 다음 절 N-R4 가 정한 것과 같은 구분이다.

복합 구조 fallback: 노드가 여럿일 때 어느 노드부터 포기하나#

앞 절의 사다리는 노드 하나의 카테고리를 내리는 규칙이다. 질의가 중첩(entity.components[])이나 병치(최상위 components[] 여럿)로 노드를 여럿 가지면, 그것과 별개로 어느 노드를 먼저 포기하는지를 정해야 한다. 2026-08-28 에 정원석이 확정했고 SSoT 는 spec/capability-table.md 3.6절이다. 그전까지 이 자리는 design/content-binding-contract.md 3.4절에 "아직 한 문서에 못 박지 않았다"로 열려 있었다.

다섯 단계다. 위에서부터 시도하고 후보가 나오면 멈춘다.

단계 무엇을 하나 잃는 것
N1 질의 트리 전체를 그대로 담은 후보를 색인에서 찾는다 없다
N2 트리 모양을 고정한 채 노드마다 앞 절의 fallback 순서를 따라 내려간다 노드마다의 카테고리 표현
N3 가장 바깥 부모를 벗겨 자식을 최상위 components[] 로 올리고 N1 부터 다시 시작한다 벗긴 부모의 카테고리 표현과 형제 사이의 공간적 인접성
N4 남은 노드가 모두 단일 카테고리가 될 때까지 N3 을 반복한다 위와 같다. 깊이마다 한 번씩
N5 규약 1절 "중첩은 평탄화된다"대로 자식 entities 를 부모 items 로 편다 자식 전부의 카테고리 표현

사다리는 최상위 components[] 원소마다 독립으로 돈다. 최상위 병치는 이미 컴포넌트 경계가 갈려 있어 벗길 부모가 없고, 색인의 후보 단위도 컴포넌트 하나이므로 한 후보가 최상위 원소 둘을 함께 담는 일이 없다.

왜 위부터 벗기나. 부모를 벗기는 것과 자식을 벗기는 것이 대칭이 아니다. 부모를 벗기면 질의 1개가 질의 여럿으로 갈라지고, 질의가 작아지므로 후보 풀이 커진다. 자식의 내용과 카테고리 표현은 전부 남는다. 반대로 자식을 벗기면 벗긴 자식의 내용이 지워지거나 부모로 접히는데, 부모는 여전히 복합 구조라 매칭 난이도가 그대로다. 내용을 지불하고 검색 가능성을 얻지 못한다. 그래서 N3 은 위부터만 간다.

왜 평탄화가 마지막인가. 규약 1절은 종전에 "중첩은 평탄화된다"만 정하고 있었고, 그것이 유일한 확정 기법이었다. 평탄화와 떼어내기(N3)는 둘 다 텍스트를 하나도 버리지 않는다. 다른 것은 표현이다. 평탄화는 자식 전부의 카테고리 표현을 잃는 대신 부모의 배치를 지키고, 떼어내기는 자식의 카테고리 표현을 지키는 대신 부모의 배치를 잃는다. 표현을 더 많이 남기는 쪽을 먼저 두기로 정했으므로 떼어내기가 앞이고 평탄화가 뒤다. 평탄화는 앞 절의 마지막 fallback 셋으로 끝나므로 반드시 성립하고, 그래서 복합 구조에서도 빈 결과가 나오지 않는다.

규칙 여섯이 붙는다.

# 규칙
N-R1 벗긴 부모의 label, items, relations[].desc 는 버리지 않고 승격된 첫 조각의 첫 entity items 맨 뒤로 보낸다. P3 과 P7 과 같은 방식이다
N-R2 형제는 큰 묶음부터 그리디로 나눈다. 4형제면 [4], [3,1], [2,2], [2,1,1], [1,1,1,1] 순이다. 연속한 형제만 묶고 순서를 바꾸지 않는다
N-R3 색인 태깅 어휘의 Unclassified 는 카테고리가 아니라 경계다. 질의 type 이 될 수 없으므로 그 자리를 카테고리로 검색하지 않고 부모의 슬롯 경계로만 쓴다
N-R4 N 단계 수와 카테고리 fallback 칸 수는 서로 다른 예산이다. N3 한 번이 카테고리 강등 칸을 소모하지 않는다
N-R5 확정 (2026-09-05, B-31). 부모의 대분류가 Hierarchy 면 N3 을 건너뛰고 N5 로 간다. 깊이 3 이상에서 소속 관계가 사라지는 걸 막기 위해 실측 없이 구조적 논증만으로 확정했다
N-R6 N2 의 조합 순서는 강등 칸수 합이 작은 것부터다. 합이 같으면 뿌리에서 먼 노드를 먼저 내린다. 뿌리를 내리면 자식이 들어갈 슬롯이 함께 바뀌기 때문이다

N1 은 지금 정책상 언제나 비어 있다. D-7 이 이종중첩 템플릿을 검색 풀에서 제외하기로 확정했으므로(2026-08-11 체크인, 전체 약 2%), 뿌리가 Unclassified 인 템플릿이 후보로 걸리지 않는다. 같은 이유로 N2 도 이종 구조에서는 후보가 0이다. 모양이 이종중첩인 채로 남기 때문이다. 즉 이종 질의는 실질적으로 N3 에서 시작한다. 동종중첩(자식 type 이 부모와 같은 경우)은 D-7 밖이라 N1 과 N2 가 살아 있다. D-7 을 되돌릴지는 열려 있다(D-8). D-7 의 결정 근거가 "대부분 조합 Fallback 만으로 해결될 것으로 예상"이었으므로, N3 부터 N5 의 결과 품질이 그 예상의 검증 자료가 된다.

이 확정으로 닫힌 것과 새로 열린 것.

항목 어떻게 됐나
질의 쪽 중첩을 평탄화한 뒤 fallback 할지, 구조를 유지한 채 노드 태그로 자를지 (I-12) 닫혔다. 유지한다
평탄화와 떼어내기 중 어느 쪽이 내용을 더 잘 보존하는지 닫혔다. 떼어내기가 먼저다
병치를 쪼개고 조각마다 검색하고 결과를 조립하는 코드 (I-11) 열려 있다. 검색 재료만 있고 오케스트레이션이 없다
이종중첩 템플릿을 검색 풀에 되돌릴지 (D-8) 열려 있다
계층 대분류에서 N3 을 건너뛸지 (B-31) 열려 있다
N3 과 N4 반복으로 최상위 컴포넌트 수가 늘어날 때 상한 (B-32) 열려 있다

한 단계씩 따라가는 예시는 6절 부록 7.5 에 있다.

structure projection: fallback 이 넘어갈 때 내용을 옮기는 규칙#

fallback 은 카테고리를 바꾸는 일이라, 출발 카테고리가 쓰던 필드를 도착 카테고리가 못 읽는 경우가 생긴다. 이때 "어디로 옮기는지"를 정하는 것이 projection 규칙 여덟 가지(P1~P8)다. measure↔matrix 를 서로 접는 projection 규칙(옛 제안 P9, P10)은 만들지 않기로 확정했다(B-30, 2026-09-05) — 단일 값과 복합 데이터는 형태가 근본적으로 달라 변환하면 원래 의도를 왜곡하기 때문이다.

규칙 무엇을 하나
P1 entities 는 그대로 간다. 순서와 label, items, children 은 어느 엣지에서도 손대지 않는다
P2 도착이 선언하지 않은 params 는 버린다. 표현 선택이지 내용이 아니기 때문이다
P3 도착이 안 읽는 내용 필드는 그 entity 의 items 맨 뒤에 이름: 값 형태로 붙인다
P4 matrix 를 못 읽는 곳으로 가면 entities 로 편다. 행마다 entity 를 만들고 첫 열이 label, 나머지 열이 items 로 들어간다
P5 measure 를 못 읽는 곳으로 가면 value 와 unit 을 entity 로 옮긴다. 나머지는 P3 을 따른다
P6 entities 만 있는데 matrix 가 필요하면 행으로 편다. label 이 첫 열, items 가 나머지 열이다
P7 도착이 관계를 못 그리면 relation.desc 를 of 첫 항목의 items 맨 뒤로 보낸다
P8 도착의 개수 상한을 넘으면 초과분을 마지막 항목의 items 로 접는다

내용은 버리지 않고 표현만 버린다. P2 만 버리는 규칙이고 나머지는 전부 옮기는 규칙이다. orientation, apex, icon 같은 표현 선택은 도착 카테고리에서 뜻이 없으므로 버려도 내용이 줄지 않는다.

실제 엣지에 규칙이 어떻게 겹쳐 적용되는지 예시 셋.

출발 → 도착 적용 규칙 무엇이 옮겨지나
BarChart → List P1 P2 P3 P4 matrix 의 행이 entities 가 되고, unit과 value가 items로 간다. orientation, sort는 표현 선택이라 버린다
BigNumber → HighlightedMessage P1 P2 P3 P5 value와 unit이 entity로 옮겨지고, delta와 emphasis도 함께 간다. 1개를 넘으면 이 엣지는 성립하지 않는다(P8 폐기, B-27) — List가 안전망이다
ComparisonTable → Versus P1 P2 P4 P8 matrix의 행이 entities가 되고, 2개를 넘으면 나머지를 마지막 항목에 접는다(Versus는 정확히 2가 조건이므로)

alternatives 와 fallback 순서의 차이#

둘 다 "다른 카테고리로 갈 수 있다"는 점은 같지만 보는 것이 다르다.

fallback 순서 alternatives
어디 있나 capability 상수. 카테고리마다 미리 박혀 있다 생성물. 입력마다 다르다
무엇을 보나 카테고리 대 카테고리만 본다 그 입력의 내용을 본다
언제 정하나 규약을 쓸 때 한 번 생성할 때마다

fallback 순서는 정적이다. Gauge → BigNumber → List 는 게이지에 무엇이 담기든 같다. 반면 생성기는 입력 내용을 이미 읽었으므로, 같은 Hierarchy.Pyramid 라도 층마다 크기 차이가 의미를 지니면 Progression.Staircase 가 낫고 그냥 3단 분류면 Enumeration.List 로 가도 손해가 적다는 것을 알 수 있다. 이런 판단을 alternatives 에 미리 적어 두면 검색이 정적 fallback 순서보다 먼저 참고한다. 다만 이것은 가정이고 아직 실측이 없다. 생성기가 고른 alternatives 가 정적 순서보다 실제로 나은 결과를 내는지는 검증된 적이 없어서 optional 로 열어 두고 생성기 정책으로 켜고 끌 수 있게 했다.

꾸밈구조(decoration): 등급 하나에서 축 여럿으로#

structure 가 내용이고 params 가 카테고리별 표현 선택이라면, 꾸밈구조는 카테고리와 무관한 표현 선호다. 그림 장식이 붙은 컴포넌트를 원하는지, 배경이 깔린 것을 원하는지를 적는다. 설계 의도의 SSoT 는 design/decoration-model.md 이고 확정 표기는 규약 2.1절, 2.2절, 2.7절이다.

7/24 회의는 이것을 누적 등급 셋(N, N+α, N+β)으로 제안했다. 같은 레이아웃 하나에서 여러 벌을 만들어 두고 사용자 수요에 맞는 것을 고른다는 발상이다. 세 등급의 원 정의는 다음과 같다(원문은 archive/schema-v0.4.md 4-1절에 보존한다).

등급 원 정의 왜 그 등급인가
N 그 카테고리의 최소 필요 조건만 만족하는 버전. 예를 들어 Steps 라면 단계 항목만 있는 것 필수. 언제나 만든다
N+α N 에 타이틀과 디스크립션까지 살린 버전 입력에 제목·설명이 있는 사용자를 위한 것
N+β N+α 에 요소 배치와 장식까지 살린 버전 장식은 지울 수 있어도 없는 것을 더하기는 어려우니, 장식 보존본을 처음부터 함께 내보낸다

N ⊂ N+α ⊂ N+β 로 뒤 등급이 앞을 포함하는 모양이다. 2026-08-03 에 정원석이 이 모양을 기각하고 축 여럿으로 바꿨다. 기각 근거가 다섯이다.

# 왜 등급 하나로는 안 되나
1 한 등급 안에 서로 다른 것이 섞인다. 회의 원문은 N+β 를 "요소 배치와 장식까지"로 적는데, 배치가 정교한데 그림이 없는 컴포넌트와 그림이 많은데 배치가 단순한 컴포넌트를 구분해 요구할 수 없다. N 과 N+α 의 경계는 더 나쁘다. 타이틀과 디스크립션 유무는 내용이고 장식 양은 표현 선택인데 한 축에 섞여 있다
2 누적이라서 표현할 수 없는 조합이 있다. "꾸밈은 많지만 디스크립션은 없는 것"에 등급 이름이 없다
3 축이 늘 때 끼워 넣을 자리가 없다. 테두리나 그림자가 새로 필요해지면 "N+β 안쪽"이라고 말할 수밖에 없고, 그 판단이 문서마다 달라진다. 축으로 두면 새 축을 열고 기본값을 indifferent 로 두면 기존 동작이 안 바뀐다
4 채점 실물이 이미 축별이다. comp-sign DEFAULT_SCORING_WEIGHTS 가 illustrationBonus, titleFontSizePenalty, styleMismatch, visualSiblingSplit 로 항목별로 나뉘어 있다. 등급으로 뭉치면 "rich 를 요구했는데 왜 이 점수인가"를 설명할 수 없다
5 N 은 애초에 꾸밈 축이 아니다. N 의 '최소 필요 조건'은 capability 성립 조건과 같은 대상을 가리키므로, N 을 등급 최하단에 두면 같은 사실이 두 곳에 적힌다

축은 둘이고 각 축이 RLSC role 하나에 대응한다. 축 하나가 role 하나에 붙으면 셋이 정해진다. 추출이 그 role 을 이미 붙이므로 표식을 새로 만들지 않고, 요소 대치가 지울 노드를 role 로 특정할 수 있고, 새 꾸밈 role 이 생기면 축을 하나 더 열면 된다.

축 대응 RLSC role role 정의
ornament Element.Decoration, LayoutContainer.Decoration, Element.Separator, LayoutContainer.Separator "시각적 꾸밈 목적, 내용 대치 시 변경 불필요" — Separator 도 순서·내용을 안 담아 같은 근거를 공유 (2026-09-05, B-21)
background Element.Background, LayoutContainer.Background "Parent position 거의 가득 채움, 크기 늘림 선호"

축에서 뺀 것이 셋이다. description 과 highlight 는 내용이라 structure 와 entity.emphasis 가 담고, arrangement 는 노드에 붙는 성질이 아니라 노드를 놓는 방식이라 role 대응이 없다(배치를 보는 채점 항목은 areaRatio, offCenterPenalty, visualSiblingSplit 로 이미 있다). Element.Marker 는 처음에 판정을 미뤘다가 2026-08-04 에 축이 아니라고 정했다. 마커는 순서를 담고 순서는 내용이다(마커 보유 540건 중 534건이 기하 순서와 일치). Element.Separator 는 순서도 내용도 담지 않아 세 번째 축 후보로 열려 있다.

값은 셋이고 require 가 없다.

값 뜻 왜 있나
prefer 그 role 노드를 가진 후보를 앞에 둔다. 없어도 후보로 남는다 탈락시키지 않는다
avoid 그 role 노드를 가진 후보를 뒤로 보낸다. 있어도 후보로 남는다 "꾸밈 없는 깨끗한 것"을 요구하는 자리
indifferent 보지 않는다. 기본값 안 적은 것과 같은 뜻이지만, 위 층에서 물려받은 값을 취소하려면 명시할 수 있어야 한다

require 를 두지 않은 근거는 검색 풀 분포다. VennDiagram, HierarchicalTree, NestedScope, Pyramid 가 각 164건 이하이고 데이터 차트가 각 51건 이하인데, 여기에 꾸밈까지 필수로 걸면 후보가 0이 되어 전부 List 로 강등된다. 수치 점수(ornament: 0.8)도 두지 않고 앞으로도 두지 않는다(2026-08-29 확정). 채점 코드를 건드리지 않기로 한 결정과 어긋나고 entity.weight(number)가 다른 뜻으로 이미 있다.

프리셋 문자열을 스키마 값으로 두지 않는다. N 을 minimal, N+β 를 rich 로 부를 수는 있지만 문서와 회의에서만 쓴다. 이유가 셋이다. 문자열과 객체 두 모양을 허용하면 소비자가 분기해야 하고, 프리셋은 축으로 펼쳐지므로 정보를 더 담지 않으며, 축이 하나 늘 때마다 rich 가 가리키는 범위가 달라져 등급을 기각한 것과 같은 결함이 되살아난다.

선언 층이 넷이고 축마다 따로, 구체적인 쪽부터 해석한다. 층 전체를 통째로 갈아치우지 않고 축 단위로 병합하는 것이 중요하다. 그래야 "page 는 꾸밈을 다 원하는데 이 컴포넌트만 배경 빼고"를 표현할 수 있다.

순서 어디 누가 채우나
1 component.decoration 생성기. 1단계에서는 안 채운다
2 page.decoration 생성기. 1단계에서는 안 채운다
3 카테고리 기본값 capability 상수. 미정
4 시스템 전역 기본값 검색 설정. 1단계에서는 여기만 채운다
5 indifferent 규약 기본값

1단계는 시스템 전역 기준만 잡고 Summary 는 생성하지 않는다. 스키마에 optional 로 자리만 연다. 근거가 다섯이다. 지금 소비할 주체가 없고(요소 대치가 아직 동작하지 않으며 내용 대치는 꾸밈에 관여하지 않는다), 색인이 대조할 role 표식을 받을 경로가 없고(B-22), 지금 필요한 것은 "꾸밈 많은 것을 우선"이라는 전역 정책 하나로 되고, 생성기에 필드를 늘리면 토큰과 생성 실패 자리가 늘고(entity.id 를 뺀 근거가 토큰 5.9% 증가였다), entity.components[] 와 page 수준 relations 가 이미 같은 방식으로 열려 있다는 선례가 있다.

그래서 지금 꾸밈 축이 쓰이는 곳은 검색 순위 하나다. 꾸밈 노드를 지우거나 넣는 것은 요소 대치가 생긴 뒤에 할 수 있다. 색인에는 장식 보존본을 한 벌만 둔다. 등급 3단으로 벌을 나누면 comp-sign 변형 3종과 곱해져 컴포넌트당 최대 9벌이 되는데, 보존본 한 벌이면 3벌에서 그친다. 근거는 7/24 비대칭 원칙이다. 장식은 삭제할 수 있어도 없는 것을 추가하기는 어렵다.

이렇게 쓰지 않는다.

안티패턴 왜
decoration: { ornament: require } require 는 없는 값이다. 후보가 0이 되어 전부 List 로 강등된다
decoration: { ornament: 0.8 } 수치 점수를 두지 않는다. 앞으로도 두지 않는다
decoration: { description: prefer } description 은 축이 아니다. 설명 텍스트는 내용이고 structure 가 담는다
decoration: rich, decoration: minimal 프리셋 문자열을 받지 않는다. 축으로 적는다
decoration 과 style 을 섞어 적는 것 키 이름이 다르다. decoration 은 RLSC role 과 겹치고 style 은 스타일 대치가 쓴다
생성기가 decoration 를 채우는 것 1단계에서는 안 채운다

아직 정하지 않은 것이 여덟이다. Element.Separator 를 세 번째 축으로 열지, LayoutContainer.PageHeader 와 PageFooter 를 어떻게 볼지(이 둘은 짝이 되는 Element role 이 없어 "축 하나가 Element.X 와 LayoutContainer.X 쌍에 대응한다"는 전제가 깨진다), DECO 노드 분류가 Element.Decoration role 과 같은 집합인지, 카테고리 층을 둘지, 45종의 카테고리 기본값, 색인이 role 표식을 받는 경로(B-22, 이것이 먼저 풀려야 검색이 대조할 수 있다), prefer 와 avoid 를 점수가 아닌 형태로 검색이 순위에 어떻게 반영하고 fallback 보다 먼저 보는지, comp-sign 변형 3종을 대치 시점으로 옮길지(B-19)다. 실물 Decoration 형 정의와 해석 예시 넷은 5절 부록 K 에 있다.

포맷 실측과 다시 볼 조건#

docs/projects/mordor/visual-summary/spec/format-alternatives.md 가 YAML 과 KDL 을 비교한 사전 조사 결과를 담는다. 공개 벤치마크에서 확인된 것은 넷이다. JSON 의 파싱 유효율이 100%, S-expression 이 95.8%, YAML 이 91.7% 였고 YAML 은 구조 오류 시 "조용한 손상(silent corruption, 파싱은 성공했지만 값의 의미가 조용히 바뀌는 것)"이 상대적으로 많다는 점, 텍스트에서 YAML 을 만드는 일 자체는 모델이 이미 90% 이상 능숙한 쉬운 작업이라는 점, 11개 모델과 4개 포맷을 비교한 실험에서 포맷 자체가 정확도에 유의한 영향을 주지 않았다는 점(모델별 민감도 -7.7%~+2.7%), KDL 쪽은 언어 스펙과 구현체만 있고 생성 신뢰성 벤치마크가 0건이라는 점이다.

이 조사에서 내린 실무적 결론은 "공개 사례로는 우열을 가릴 수 없으니 사전 확률(YAML 우세)로 초안을 확정하고, 자체 실측은 사후 검증으로 돌린다"이다. YAML 을 선택했으므로 감시해야 할 것은 "파싱은 됐는데 타입이 뒤바뀐" silent corruption 이다. 이 지표는 실패로 보이지 않기 때문에 처음부터 계측에 넣었다(구현은 parse.ts 의 parseYamlWithDiagnostics(), 4절 참고).

사후 검증 설계(아직 실행 전). 같은 스키마 N개 케이스를 YAML, KDL, JSON 각각으로 생성해 파싱 실패율, 스키마 위반율, silent corruption 율, 재현성(temperature 0 에서 k회 반복했을 때 출력 일치율) 넷을 재는 계획이다. 다른 마감이 걸려 있지 않을 때 돌린다.

자기정합 검사#

2026-08-04 에 스키마, capability 테이블, YAML 예시가 서로 어긋나지 않는지 검사 14가지를 기계로 돌렸고, 2026-08-29 에 전량 다시 돌렸다(docs/projects/mordor/visual-summary/spec/schema/README.md). 2026-08-04 실행에서 결함 다섯 건을 찾아 고쳤고 그 과정에서 열린 결정 두 건(B-27, B-28)이 새로 생겼다. 2026-08-29 실행에서는 생성물 둘의 미반영(부록 F 1·2번)과 예시 하나를 고쳤고, 검사 정의 자체도 둘 고쳤다(F2 폐기, F3 을 "끝나는가"에서 "거치거나 자신이 그 셋인가"로). fallback 순서는 이때 그래프 형태로 확정됐다(B-26). 이 검사는 코드에도 그대로 옮겨져 있다. src/lib/visual-summary/capability.test.ts 가 spec/schema/capability.json 과 45종 전량을 대조해서 문서와 코드가 어긋나지 않는지 확인한다. 다만 docs/ 폴더가 비공개 submodule 이라 CI 체크아웃에는 없어서, 이 대조는 로컬에서만 돌고 CI 에서는 건너뛴다.

↑ 목차로 · 7부 머리로

51장보고서 3 · 일정과 작업 상태 파생파생물

원문 report/sections/03-status.md GitHub 에서 보기 7 KB

일정과 작업 상태#

지금 한 줄로 말하면#

규약(스펙)은 2026-08-02 에 확정됐고 현행 값은 0.5.4 다. 질의 갈래 PoC 와 I.I.S. 색인 갈래 코드는 이미 돈다. RAT pod(류민 소관)의 I.I.S. 본편은 기다리지 않고, PoC 가 같은 출력을 계속 대행 생산한다(D-5, 2026-08-22). 2026-09-05 에 규약 열린 결정(B절)은 전량 닫혔다. 같은 주에 Week 27 PoC(K절)가 로컬 인덱스 12,348건 위에서 자유 입력부터 렌더까지 한 번에 돌고, 2026-09-07 에는 노드 검색 엔진이 플레이그라운드에 폴백 없이 따로 붙었다. 그래서 지금 상태를 정확히 읽으면 "스펙과 PoC 골격과 규모 있는 로컬 색인은 서 있고, 내용 대치 contract(I절), 노드 검색 정확도 판정, 프로덕션 I.I.S. 추출이 다음 병목"이다.

로드맵 5단계 진행 상황#

단계 무엇이 바뀌는가 목표 시점 지금 상태
1단계 (현행) Markdown Summary, 임베딩 검색 지금 운영 중, 변동 없음
2단계 컴포넌트에 Semantic Component Category 2026-08-18 로 잡혀 있었으나 폐기 2026-08-07 사실상 스킵 확정. 4단계로 직행
3단계 YAML + structure 검색 없음 실행하지 않음, Jira 상 Cancelled (2026-08-06)
4단계 RLSC node 태그로 정확한 범위 특정 9월 말 dev/stg, 10월 중순 production 판정 PoC 진행 중. 질의 갈래는 Eregion 에서 동작. 로컬 인덱스 12,348건. 노드 검색 엔진은 컴포넌트 검색과 폴백 없이 분리
5단계 node 에 semantic category 속성까지 미정 속성 추출(T-7) 로컬 파일럿 3,955건. T-8 투영은 미구현. 정확도 검수는 K-9-1

2단계를 스킵한 경위. 2026-08-03 에 내용시각화 제품의 8/25 production 배포가 연기로 정해지면서, 2단계 배포(MOR-1774)의 8/18 마감이 이 연기에 포함되는지가 미정이었다. 2026-08-07 김수현님이 Confluence 코멘트로 "8/18 마감은 연기에 포함된다. 8/18 은 폐기하고 4단계로 직행한다"고 답하면서 정리됐다. 같은 자리에서 "List 편중 81.1% 를 그대로 두고 2단계를 낼지"(TODO D-2)도 "2단계를 안 하니 결정할 것이 없다"로 닫혔다. MOR-1774 티켓 자체는 Jira 상 Cancelled 로 전환되지는 않고 Draft 상태를 유지한다.

3단계를 건너뛴 이유. 2026-07-24 회의에서, 3단계에서 개발할 내용 대치 로직이 4단계에서는 버려질 수 있다고 판단해, 정보가 확실한 4단계로 바로 가는 편이 낫다고 봤다. 3단계였던 "Summary 생성기를 YAML 로 바꾸는 것"은 2026-08-02 결정으로 4단계 범위에 편입됐다. 그 결과 4단계는 생성기 교체와 node 태그 도입과 검색 로직 변경이 한 번에 일어난다. 이것이 품질 저하 시 원인을 세 가지 중 무엇으로 볼지 가리기 어렵게 만드는 위험이라, 4단계 착수 때 단계별 계측 지점을 설계로 보완하기로 했다(TODO C-3).

Jira 구조#

상위 Artifact 는 MOR-1923 이고 2026-08-06 에 신설됐다. 그 아래 마일스톤 MOR-1776(4단계)에 Subtask [4-1]부터 [4-15]까지가 실행 트랙이다.

구간 Subtask 무엇을 다루나 담당
[4-1]~[4-3] MOR-1909~1911 생성기 프롬프트와 fallback map 정의 허재영, 황현진
[4-4]~[4-9] MOR-1924~1929 검색 질의 변환기 스펙, capability 상수, 대치 PoC, 계측 설계, 평가셋, 판정 지표 정원석
[4-10]~[4-15] MOR-1932~1937 내용 대치 contract 와 열린 결정 해소 정원석, 이석

규약 정의 티켓 MOR-1773은 Done, 3단계 마일스톤 MOR-1775는 Cancelled, 2단계 마일스톤 MOR-1774는 위에서 설명한 대로 사실상 스킵 상태에서 due date 필드만 잠정 유지 중이다.

할 일 SSoT(TODO.md)의 절 구조#

docs/projects/mordor/visual-summary/TODO.md 가 할 일을 열한 절(A부터 K까지)로 나눈다. 아직 열린 항목은 5절 부록 I 에 있다. 훑기 표 기준 남은 것은 2026-09-07 시점에 A 1, B 0, C 2, D 1, E 3, F 1, H 3, I 14, J 8, K 2(K-9-1 과 K-11. 훑기 표의 K-9·K-10 은 본문에서 이미 닫힘)이다. H절은 저장소 표기 통일이라 부록 I 에서 다루지 않는다.

절 무엇을 다루는가 마감 남은 것
A 규약 확정 2026-08-07 1건 (A-4)
B 규약의 열린 결정 A 에 종속 0건. 2026-09-05 전량 닫힘
C 2단계 배포 사실상 스킵 (2026-08-07) 2건 (C-6, C-7)
D 스쿼드 차원에서 정할 것 각각 다름 1건 (D-8)
E 7/24 회의 후속 미정 3건
F 사후 검증 (마감 후로 미룸) 마감 후 1건 (F-2)
G 최근 해소된 것의 기록 기록만 없음
H 문서 정합 백로그 없음 3건
I 내용 대치 contract 미정 14건 (I-12 만 닫힘)
J 생성 모델 파인튜닝 착수 전 8건
K Week 27 PoC. 10,000 규모 인덱스 위 전체 파이프라인 week 27 2건 (K-9-1, K-11)

품질을 좌우하는 것은 여전히 I절(내용 대치 contract)이다. 실행 1순위는 I-8(평가 pool bake)이다. K절은 그 위에서 규모와 화면을 먼저 세운 것이고, I-13(복잡도 레벨)은 K-4·K-5 로 실질 구현됐지만 TODO 항목은 아직 열어 둔다.

확정된 결정과 폐기된 결정#

날짜 결정 근거
2026-08-01 Summary 형식을 YAML 로 초안 확정 2절 참고
2026-08-02 규약 v0.5 확정 (마감 8/7 보다 닷새 앞섬) spec/schema-v0.5.md
2026-08-03 꾸밈구조와 계층 결정(B-2, B-3)이 뒤집힘 design/decoration-model.md
2026-08-04 규약 표기에 꾸밈구조 반영, 자기정합 검사 14가지 실행 결함 5건 발견, 열린 결정 2건(B-27, B-28) 신규. 그 둘은 이후 닫힘
2026-08-06 Jira 재편, Artifact MOR-1923 신설, MOR-1775 Cancelled plan/jira-artifact-blueprint.md
2026-08-07 2단계 배포 마감 폐기, 4단계 직행 확정 김수현 Confluence 코멘트
2026-08-12 Eregion PoC 화면 세워짐, 질의 갈래 end-to-end 동작 plan/eregion-poc.md
2026-08-13 fallback map v2 대부분 반영 plan/fallback-map-review-2026-08-13.md
2026-08-15 I.I.S. 계열 이름을 Semantic Component Boundary(4단계)와 Semantic Component Property(5단계) 둘로 확정 README.md 5절
2026-08-17 capability 테이블 45종 확정 spec/capability-table.md
2026-08-22 D-5 닫힘. I.I.S. 본편 재개를 4단계 착수 조건으로 두지 않기로 결정 README.md 5절, plan/poc-module-map.md
2026-08-28 노드가 여럿인 질의의 복합 구조 fallback 순서 확정(N1~N5). I-12 닫힘 spec/capability-table.md 3.6절
2026-08-29 type 필수, 대분류 단독 제거, enum 45종, 자기정합 검사 전량 재실행 spec/schema/README.md
2026-09-05 B절 전량 닫힘. 규약 현행 0.5.4. Semantic Component Category 이름 확정. K-1~K-8 완료. 로컬 인덱스 12,348건 TODO.md, README.md
2026-09-06 I.I.S. 로컬 파일럿 추출. 경계 4,764건, 속성 3,955건 (K-10) TODO.md K절
2026-09-07 노드 검색 엔진을 플레이그라운드에 폴백 없이 배선 (K-9). 검색 단위 약 7,696 노드 TODO.md K-9

아직 폐기 여부만 확정되고 새 날짜가 안 나온 것. 2단계 배포 마감(8/18)은 폐기됐지만 재조정된 새 마감은 원문에 없다.

가장 급한 병목 셋 (2026-09-07 기준)#

  1. 평가 pool과 사전 적재(I-8). 로컬 색인은 12,348건이고 I.I.S. 파일럿은 경계 4,764건·속성 3,955건까지 왔다. 세 대치 경로를 같은 design_object_id 부분집합에서 비교하려면 bake 정의와 문서화가 아직 남는다.
  2. 노드 검색 정확도 판정(K-9-1)과 프로덕션 I.I.S. 추출(K-11). 화면과 로컬 데이터는 있다. 어느 엔진이 얼마나 맞히는지는 사람이 실사용 입력으로 판정해야 하고, 프로덕션 쓰기는 AI 세션에서 못 한다.
  3. 내용 대치 contract(I절). T-8 속성 투영(I-7), 5단계 대치 PoC(I-9), 복합 구조 오케스트레이션(I-11)이 규약만 있고 코드가 비어 있는 자리이다. 나쁜 결과의 85% 가 이 구간에서 나온다는 실측(4절)은 그대로다.

5단계 로드맵 지도의 T-번호 작업 상태#

docs/projects/mordor/visual-summary/plan/node-tag-and-property-poc-blueprint.md 가 정의한 작업 단위다.

작업 무엇을 하나 상태
T-4 I.I.S. 경계(Semantic Component Boundary) 추출 코드 있음. 로컬 파일럿 4,764건(K-10). 사람 검수는 아직
T-5 노드 단위 태그로 검색 코드 있음. 2026-09-07 플레이그라운드 검색 엔진으로 배선(K-9). 컴포넌트 경로와 폴백 없음
T-6 노드 단위 태그로 대치 코드 있음. K-9 와 함께 배선. 노드 경로는 용량 자가 없어 결함 채점을 돌리지 않음
T-7 카테고리 고유 속성 추출 v1 코드 있음. 로컬 파일럿 3,955건. 정확도 검수 미완
T-8 property projection(이종 카테고리 간 속성 슬롯 매핑) 미구현

시간순으로 본 일정#

[그림: 확정된 마감을 시간순으로 놓은 간트차트] ↗

2026-07-13  ~  2026-08-02   규약 확정 (A절)                     완료
2026-08-01  ~  2026-09-15   4단계 내용 대치 contract 정의 (I절)  진행 중
2026-08-12                  4단계 PoC: Eregion 질의 갈래 동작     진행 중
2026-08-22                  4단계 I.I.S.: T-4/T-5/T-6/T-7 진행    진행 중
2026-09-05                  Week 27 PoC 1차 (K-1~K-8), B절 전량 닫힘
2026-09-07                  노드 검색 엔진 배선 (K-9)
2026-09-30                  4단계 dev/stg 판정                   목표 마일스톤
2026-10-15                  4단계 production 판정                 목표 마일스톤

위험과 감수하는 것#

가장 크게 감수하는 위험은 4단계에서 생성기 교체, node 태그 도입, 검색 로직 변경이 동시에 일어난다는 점이다. 품질이 떨어졌을 때 원인이 이 셋 중 무엇인지 가리기 어렵다. 이를 보완하기 위해 단계별로 계측 지점을 따로 설계하기로 했다(TODO C-3). 데이터 쪽에서는 README 3절의 List 편중 81.1%(design object 83,771건, 2026-07-22 스냅샷)가 아직 허브에 남아 있다. 2026-09-05 프로덕션 실측은 design_object_semantic_tags version 2.2 가 178,095건이고 전량이 검색 후보 조건(sheet_json 과 structure_json 둘 다)을 채운다. 재측정은 C-7 이다. RAT pod 소관 I.I.S. 본편이 2026년 6월 중순부터 정지된 상태라는 것도 계속 감시해야 할 리스크다. 다만 2026-08-22 결정으로 이 정지가 4단계 진행을 막지는 않는다. 컴포넌트 검색과 노드 검색을 폴백으로 섞지 않기로 한 것(K-9)은 평가를 위해 감수하는 제약이다. 한쪽이 비면 후보가 0인 채로 끝난다.

↑ 목차로 · 7부 머리로

52장보고서 4 · 구현물 로직 파생파생물

원문 report/sections/04-implementation.md GitHub 에서 보기 19 KB

구현물 로직: PoC 파이프라인과 코드 구조#

왜 PoC 를 만들었는가#

2026-07-26 조사에서, 대치 결과가 나쁘다고 판정된 것 200건을 직접 읽고 자동 채점 16,819행을 함께 집계한 결과, 나쁜 결과의 85% 가 그림이 잘못 그려져서가 아니라 "고른 컴포넌트와 넣으려는 내용이 안 맞아서" 였다. 세부 원인은 넷이다. 글자가 자리에 안 들어가 넘치는 것, 제목에 들어갈 내용이 설명 자리에 들어가는 식으로 위계가 어긋나는 것, 항목 수와 칸 수가 안 맞는 것, 대치가 안 돼 템플릿 원문이 그대로 남는 것이다.

이 구간에는 구현이 없는 것이 아니라 규약이 없었다. 이석님의 packages/comp-sign 패키지가 시그니처 추출, 검색, 랭킹, 내용 대치 네 단계를 이미 구현하고 있다. 비어 있던 것은 "고른 다음에 무엇을 어디에 어떻게 넣을 것인가"의 규약이다. 그래서 PoC 의 목적은 데모가 아니라 스쿼드가 못 닫고 있는 결정을 실측으로 닫는 것이다. 화면이 있는 이유도 보여 주기 위해서가 아니라 판정 이력을 사람이 열어 볼 수 있게 하기 위해서다.

전체 흐름 (여덟 단계)#

사용자가 쓴 글 한 덩어리가 들어가서, 슬라이드에 그려질 결과물과 결함 채점표가 나오기까지 여덟 단계를 거친다.

[그림: 질의 갈래 파이프라인 여덟 단계] ↗

사용자 입력
  → 1. Visual Summary 생성 (LLM 이 YAML 을 쓴다)
  → 2. 검증 (파싱 → id 부여 → 스키마 검사)
  → 3. 카테고리 확정 (성립 조건 판정 + fallback)
  → 4. 검색 질의 변환 (structure → 태그와 제약)
  → 5-A. 시멘틱 검색 (semantic tag 로 후보 탐색)   ─┐
  → 5-B. 구조 검색 (구조 해시로 후보 탐색)          ─┤ 두 경로 병행
  → 6-A. 자체 대치 (RLSC 슬롯에 직접 삽입)          ─┤
  → 6-B. comp-sign 대치 (comp-sign 이 삽입)         ─┘
  → 7. 실서비스 렌더러로 렌더 (브라우저에서 실제 렌더러로 그림)
  → 8. 결함 채점 (같은 기준으로 두 결과를 비교)

1. 생성. 사용자 글을 LLM 에 넣어 YAML 로 구조화한다. 지금 쓰는 모델은 gpt-5.4-mini 이고, 스키마를 어기면 오류 메시지를 되먹여 재생성하는 루프가 있다(최대 3회).

2. 검증. YAML 을 파싱하고 각 항목에 배열 순서대로 식별자를 붙인 뒤 스키마로 검사한다. "파싱은 됐는데 값의 타입이 조용히 바뀌는" silent corruption 을 따로 세는 계측이 붙어 있다.

3. 카테고리 확정. 생성기가 지목한 카테고리가 성립 조건을 못 채우면 후보에서 빠지고, 정해진 fallback 순서를 따라 내려간다. 마지막 fallback 후보 셋(List, LongFormTextLayout, HighlightedMessage)은 개수 조건만 보므로 언제나 하나는 성립한다.

4. 검색 질의 변환. 확정된 카테고리와 구조를 검색이 쓸 수 있는 태그와 제약(항목 수, 글자 용량 등)으로 바꾼다.

5~6. 검색과 대치. 여기서 두 갈래로 갈린다. 다음 절에서 자세히 다룬다.

7. 렌더. 대치된 결과를 이미지 합성 서버를 왕복하지 않고, 합성 서버가 쓰는 것과 같은 페이지 렌더러를 브라우저에 직접 띄워 그린다.

8. 결함 채점. 두 갈래의 결과물을 같은 기준으로 재서 결함을 센다.

실서비스 코드와 PoC 코드의 경계#

"PoC" 라는 말이 "전부 임시로 만든 것"으로 읽히면 곤란하므로 경계를 분명히 적는다.

구간 상태 설명
Visual Summary 생성 실서비스 코드 실제 OpenAI 호출과 프롬프트, 재생성 루프가 그대로 돈다
검증, 카테고리 확정, 질의 변환 실서비스 코드 규약을 코드로 옮긴 것. 문서와의 정합을 테스트가 기계로 대조한다
시그니처 색인 계산은 실서비스 코드, 저장은 PoC 코드 실제 createCompSignatureBuilder() 로 만들지만, 실서비스는 미리 테이블에 적재하는 반면 PoC 는 서버가 뜰 때 메모리에 즉석으로 만든다
후보 조회 PoC 코드 실서비스는 백엔드가 데이터베이스에서 조회하는데, PoC 는 메모리 안에서 해시를 맞춰 본다
랭킹과 대치 (구조 검색 쪽) 실서비스 코드 이석님 패키지의 rankCompCandidates, buildContentReplacement 를 그대로 호출한다. 내용시각화 BE 가 쓰는 4단계 경로다
랭킹과 대치 (시멘틱 검색 쪽) PoC 자체 구현 검색은 search.ts, 대치는 binding.ts(규약 RLSC 방식). comp-sign 이나 BE 경로가 아니다
렌더 실서비스 코드 합성 서버가 쓰는 것과 같은 페이지 렌더러
결함 채점 자체 구현 실서비스에 대응물이 없는, 이 PoC 가 새로 만든 계측기

색인 규모는 2026-09-05 에 로컬을 12,348건으로 키웠다(이전 로컬 seed 약 2,494건. K-3). 프로덕션 design_object_semantic_tags version 2.2 는 178,095건이고 전량이 sheet_json 과 structure_json 을 갖춘다(K-2). PoC 는 서버가 뜰 때 메모리에 시그니처를 즉석으로 만든다. 실서비스 전량 178,095건을 같은 방식으로 올릴 수는 없고, 운영 적재 경로는 아래 "아직 남은 것"에 있다.

범위 밖으로 둔 것은 차트와 이미지 데이터 대치, 색상 대치, 실제 테이블 적재, 백엔드 검색 실행이다.

왜 검색 경로를 두 갈래로 나란히 두었는가#

같은 입력을 두 가지 방식으로 검색하고 대치해서 나란히 비교한다. 어느 쪽을 실서비스에 넣을지는 말로 정할 문제가 아니라 실측으로 정할 문제라고 보기 때문이다.

시멘틱 검색(자체 구현 경로) 은 design_object_semantic_tags 에 붙은 의미 태그로 후보를 찾는다. 태그가 맞는 후보를 먼저 채우고, 그 안에서 항목 수와 글자 용량이 얼마나 맞는지로 점수를 매긴다.

구조 검색(comp-sign 실제 코드 경로) 은 의미 태그를 보지 않는다. 사용자 내용의 구조를 markdown 으로 바꿔 comp-sign 에 넣으면 구조 해시와 변형들이 나오고, 색인에서 해시가 정확히 일치하는 컴포넌트를 후보로 삼아 comp-sign 의 채점 항목 20여 종(면적 비율, 줄 수 초과, 스타일 불일치 등)으로 순위를 매긴다.

두 축이 깨끗하게 갈려 있다는 것이 뜻밖의 자산이 됐다. 스쿼드가 합의한 판정 지표가 "시멘틱 검색과 구조 검색의 교집합 상위 10건 품질"인데, 이 PoC 에 그 두 검색이 이미 각각 실서비스 코드로 있어서 교집합을 실제로 계산할 수 있다.

두 경로를 잇는 다리. comp-sign 의 입력은 markdown 인데 Visual Summary 는 항목 트리를 내놓는다. 그 사이를 잇는 것이 structure → markdown 변환이다. 트리를 직접 조립하지 않고 markdown 을 거치는 이유는, comp-sign 이 트리와 해시를 같은 파싱 과정에서 만들기 때문이다. 트리를 직접 만들면 해시 경로와 어긋날 수 있다고 패키지 소스가 명시한다.

결과물 품질을 재는 방법#

이전 라운드에서는 대치 결과 23장을 렌더해 손으로 보고 판정했다. 결함을 실제로 찾아냈지만 코드를 고칠 때마다 23장을 다시 볼 수 없어 "이 변경이 좋아졌나"를 말할 수 없었다. 그래서 같은 판정 기준을 기계로 옮겼다.

결함 16종을 정의했고, 각각은 나쁜 결과의 원인 넷 중 하나에 대응한다.

원인 결함 종류 무슨 일이 벌어진 것인가
텍스트 용량 초과 capacity.lineOverflow 넣은 글이 그 자리의 줄 수를 넘는다
capacity.charOverflow 줄 수를 모르는 자리에서 글자 수가 용량을 넘는다
매핑과 위계 오류 mapping.categoryDrift 요청한 카테고리에서 규약이 허용하지 않는 카테고리의 컴포넌트를 골랐다
mapping.roleInversion 세부 내용이 제목 자리로 올라가거나 페이지 제목이 설명 자리로 내려간다
mapping.markerAbuse 순서 번호가 들어갈 자리에 문장이 들어간다
mapping.orderInversion 화면에 놓인 순서가 입력 순서와 뒤집힌다
슬롯 수 불일치 slots.unitDeficit 항목이 칸보다 많다
slots.unitSurplus 칸이 항목보다 많아 빈 칸이 남는다
slots.lineDeficit 여러 항목이 한 자리에 접히거나 아예 버려진다
slots.lineSurplus 채우지 못한 텍스트 자리가 남는다
대치 실패 replace.residue 결과에 남은 글이 사용자 입력에서 온 것이 아니다
replace.placeholder comp-sign 이 임시 문구로 채웠다
replace.emptied 채울 내용이 없어 템플릿 원문을 비웠다
replace.markupLeak markdown 표기가 결과 글에 그대로 나갔다
replace.unmapped 대치할 텍스트를 실제 요소에 못 붙였다
replace.contentLoss 사용자가 넣은 조각이 결과 어디에도 없다

심각도는 셋이다. blocker(가중치 5, 사용자 내용이 사라졌거나 남의 내용이 남은 것), major(가중치 2, 위계가 어긋난 것), minor(가중치 1, 자리가 비거나 임시 문구가 들어간 것)다. 결함 수를 그냥 더하면 가벼운 결함이 많은 결과가 더 나빠 보이므로, 가중치를 곱해 더한 값을 한 건의 점수로 쓴다.

설계 원칙은 하나다. 런타임 랭킹이 최적화하는 바로 그 양을 잰다. 넘침 판정을 comp-sign 의 채점과 같은 계산으로 맞췄다(글자 수가 아니라 줄 수 기준, 올림(조각 길이 ÷ 줄당 글자 수) 를 더해 비교). 자리마다의 용량(줄 수, 줄당 글자 수, 역할 이름)도 comp-sign 시그니처가 담은 값을 두 경로 모두에서 그대로 쓴다. 이게 성립하려면 RLSC 노드 식별자와 sheet 요소 식별자가 같아야 하는데, 당시 로컬 색인 2,468건의 텍스트 자리 17,899개 전량에서 확인했고 결합률이 100% 였다. 이후 로컬 색인을 12,348건으로 키웠으므로 이 결합률은 그 시점 표본의 실측이다.

기계 채점을 그대로 믿지 않고 실제 텍스트를 눈으로 확인해 잘못 잡은 것 셋을 걷어냈다. 순서 번호를 "입력에서 오지 않은 글"로 잘못 세던 것, 정상 배치(페이지 제목이 제목 자리로, 항목 이름이 설명 자리로 가는 것)를 위계 어긋남으로 잘못 세던 것, 잔존한 템플릿 원문에 markdown 기호가 있으면 한 결함을 두 번 세던 것이다. 셋 다 회귀 테스트로 고정했다.

결함을 생성, 검색, 대치 중 누구 탓으로 돌리는가#

귀속은 단일 결과가 아니라 대안과의 비교에서만 나온다. 두 경로 다 상위 후보 전부에 대해 대치를 돌려 보고 그중 하나만 쓰는데, 채점기가 그 버려지던 것을 주워 담아 다음 세 갈래로 나눈다.

후보 풀 안 어느 것으로도 못 피한 결함        → 생성 탓
같은 경로의 다른 후보로는 피할 수 있던 것    → 검색 탓
같은 후보를 다른 경로가 더 잘 채운 것        → 대치 탓

여기에 정직성 장치가 하나 붙는다. 랭킹이 애초에 볼 수 없었던 결함(시그니처 밖 자리의 넘침 등)은 검색 탓에서 덜어낸다. 랭킹에게 없는 정보로 벌점을 매기면 이 수치로 가중치를 튜닝할 때 잘못된 방향으로 가기 때문이다.

지금까지 나온 기준선 수치 (2026-08-15 갱신, 입력 50건, 컴포넌트 65개, 상위 10건 기준)#

지표 시멘틱 검색 구조 검색 순위 융합
심각도 가중 평균 (낮을수록 좋음) 20.0 22.0 20.7
1위 후보가 결함 0 인 비율 0.0% 1.6% 1.5%
무거운 결함(blocker)이 없는 입력 비율 8% 7% 원문에 없음

읽어야 할 것 셋. 첫째, 의미 적합성을 재기 시작하자 두 경로의 격차가 사라졐다. 2026-08-15 이전에는 구조 검색(17.5)이 시멘틱 검색(19.9)을 크게 이겼는데, "검색이 허용한 카테고리 밖에서 고른 것"을 결함으로 새로 세자 순위가 뒤집혔다(시멘틱 20.0, 구조 22.0). 구조 검색이 앞서 보이던 것의 대부분은 의미를 재지 않은 덕이었다.

둘째, 텍스트 넘침이 양쪽 모두 1위 결함이고 건수가 거의 같다(시멘틱 176건, 구조 177건). 경로를 바꿔도 줄지 않는다는 것은 대치 방식이 아니라 검색 풀과 용량 자체의 문제라는 뜻이다.

셋째, 결함의 대부분이 검색 탓이다. 귀속을 세면 생성 36, 검색 466, 대치 17, 랭킹이 못 본 것 74 다. 랭킹 가중치 실험(MOR-1936)이 대치 알고리즘을 다듬는 것보다 값이 큰 다음 수라는 근거가 여기서 나온다. 또한 두 검색은 1위 후보가 65개 중 2개(3%)만 겹쳐서, "교집합 상위 10건"을 그대로 지표로 쓰면 표본이 거의 남지 않는다.

정책 결정 중 눈여겨볼 것#

두 검색 경로가 처음에는 서로 다른 조건으로 색인을 읽고 있어서(한쪽은 컴포넌트 구조가 없으면 페이지 전체 구조로 대신, 다른 쪽은 sheet 와 구조가 둘 다 있는 것만) 비교가 불공정했다. "sheet 와 컴포넌트 구조가 둘 다 있음"이라는 엄격한 교집합으로 통일해 해결했다.

채점은 무료이고 결정적이어야 한다는 원칙에 따라 생성과 채점을 두 단계로 분리했다. 생성 결과를 저장소에 스냅샷으로 커밋해 두고, 채점은 LLM 을 한 번도 부르지 않고 그 스냅샷 위에서 돈다. 스냅샷 캐시 키에는 프롬프트 본문의 해시가 들어가서, 프롬프트를 고치면 스냅샷이 자동으로 낡은 것이 되고 도구가 멈춘다.

비교 조건(프롬프트, 모델, 색인 풀, 채점 임계값, comp-sign 버전, 평가셋)이 하나라도 다르면 도구가 비교 자체를 거부한다. 이 판단이 실제로 값을 했다. 처음에는 평가셋을 지문에 안 넣어서, 10건짜리 실행과 50건짜리 실행을 비교했더니 통과했고 모든 결함이 100건씩 늘어난 것처럼 보인 적이 있었다.

[그림: 코드 모듈 지도: lib, api, app 세 층] ↗

comp-sign v4.3.8 이 실제로 하는 일 넷#

실서비스의 검색과 대치는 이석님의 miridih/miricanvas-iui packages/comp-sign 이 한다. 이 절이 그 실물을 정리한다. 이 구간이 비어 있다고 적힌 문서가 한때 있었는데 사실이 아니다. 비어 있는 것은 구현이 아니라 규약이다. 패키지 README 가 스스로를 "컴포넌트 매칭 파이프라인 전체"로 정의하고, 네 함수로 되어 있다.

# 함수 하는 일
1 buildCompSignatures 컴포넌트를 구조 해시와 text_map, text_node_roles, 채점 feature 로 바꾼다. DB 적재용이다
2 buildCompSearchData markdown 을 요약 트리(textTree)와 조회용 {hash, variant, demand}[] 로 바꾼다
3 rankCompCandidates 후보를 채점하고 중복을 제거하고 정렬한다
4 buildContentReplacement 대치 텍스트를 만들고 box 를 보정하고 형제 그룹을 만든다

DB 접근과 후보 fetch 는 호출자(BE) 몫이고 이 패키지는 순수 계산만 한다. 입력이 markdown 이라는 점이 중요하다. Visual Summary YAML 이 이 경로에 직접 들어갈 자리가 아직 없고, 그것을 잇는 것이 3단계 질의 변환기가 하는 일이다.

태그 군 셋이 랭킹을 정한다#

"v7 태그를 후보 필터로 쓸지 순위 가산점으로 쓸지"가 열린 결정(D-1)이었는데, 답은 둘 다 아니고 태그 군(tier)이다. 구현에 이미 들어 있다.

군은 컴포넌트의 성질이 아니라 그 컴포넌트를 후보로 끌어온 근거의 출처를 나눈다. 같은 컴포넌트도 검색 조건이 달라지면 다른 군에 들어간다.

군 그 컴포넌트가 후보에 들어온 이유 근거의 출처
0군 사용자가 직접 고른 분류에 속한 태그를 갖고 있다 검색 조건 UI 의 분류와 태그 선택
1군 LLM 이 사용자의 글을 보고 추정한 태그를 갖고 있다. 0군과 겹치는 태그는 뺀다 실사용에서는 LLM 이 글과 함께 내려준다
2군 0군에도 1군에도 해당하는 태그가 하나도 없다 태그와 무관하게 구조 해시만으로 걸린 후보

0군과 1군의 차이는 사용자가 명시한 의도인가, 기계가 추정한 의도인가 다. 명시한 쪽을 항상 앞에 둔다.

후보를 채울 때는 군 순서로 채우고 목표치(기본 50)를 넘으면 뒤 군을 조회하지 않는다. 한 군 안에서 여러 태그가 자리를 나눌 때는 max-min fair 분배를 쓴다. 태그마다 1/M 씩 균등하게 가져가되, 그만큼 채우지 못하는 태그의 남은 자리를 여유 있는 태그끼리 다시 나눈다.

정렬할 때 군 순서가 점수보다 절대적으로 우선한다. 1군 후보가 아무리 점수가 높아도 0군 후보를 앞설 수 없고, 같은 군 안에서만 점수로 정렬한다. 그래서 태그 일치에 주는 가점 같은 것이 없다. 0군이 12개뿐이면 1군으로 38개를 더 채우는데, 최종 정렬에서 0군 12개가 점수와 무관하게 1군 38개보다 앞에 놓인다.

규약과 어긋나는 지점이 하나 있다. 규약 5절은 "성립 조건을 못 채우면 후보에서 빠진다"는 이분법인데 구현은 제외하지 않고 뒤 군으로 미룬다. 규약은 컴포넌트가 무엇을 소화하는지를 보고 구현은 그 후보가 어디서 왔는지를 본다. 축이 다르므로 맞춰야 한다(I-5).

태그 값의 형식도 코드가 SSoT 다. Enumeration_List 처럼 {major}_{minor} 이고 DB 에 그대로 들어 있다. minor 이름만 담던 옛 어휘(v2.0)는 버렸다. 두 형식을 섞으면 조회가 조용히 0건이 된다. 분류 규모도 코드 쪽이 정확해서 major 23, minor 49 다.

검색 fallback 군 넷: 후보가 어디서 오나#

앞 절의 군 셋은 comp-sign(실서비스) 이 쓰는 것이고 나누는 축이 "그 후보를 끌어온 근거가 사용자냐 LLM 이냐"다. PoC 의 실색인 검색(search.ts)이 쓰는 군 넷은 다른 축이다. 나누는 축이 "그 태그가 카테고리 강등 사다리의 어느 지점에서 나온 것이냐"다. 이름이 같아서 섞이기 쉬우니 항상 어느 쪽 군인지 밝혀 적는다.

검색 군 질의가 그 태그를 어디서 얻나 무엇을 뜻하나
1군 생성기가 적은 type 과 alternatives 사용자 내용을 읽은 쪽이 이걸로 그리자고 했다
2군 요청 카테고리의 fallback 행 요청한 것이 색인에 없을 때 규약이 정한 물러날 곳
3군 강등 사다리가 최종 확정한 카테고리 구조를 실제로 그릴 수 있다고 판정된 것
4군 마지막 fallback 셋 안전망. 1~3군이 하나도 못 찾았을 때만 발동한다

4군의 태그는 Enumeration.List, TextBasedCommunication.LongFormTextLayout, TextBasedCommunication.HighlightedMessage 셋이다.

[그림: 검색 fallback 군 넷과 군 안의 채점 넷] ↗

군이 점수보다 절대 우선한다. 2군 후보의 구조 적합 점수가 아무리 높아도 1군 후보를 앞서지 못한다. 군 안에서도 태그 순위가 점수보다 앞선다. 2군은 fallback 행의 순서 그대로라 Timeline 이 List 보다 항상 먼저다. 이 규칙은 원래 없었고, 실사용 로그 10건 실행에서 "같은 군 안에서 순서가 점수에 밀리는" 문제를 발견해 고정한 것이다.

한 단계씩 따라간다. 앞 절(2절)의 Gauge 예시가 검색 질의로 바뀌는 모습이다. 요청은 KPIKeyMetric.Gauge, 구조는 entities 3개에 각각 value 가 있고 dataset 은 없다.

검색 군 이 질의에서 실제 값 왜 그렇게 되나
1군 비어 있다 Gauge 는 measure 가 없어 성립이 탈락했다. 성립이 탈락한 카테고리는 1군에서 뺀다. 구조가 못 그리는 카테고리를 최우선으로 검색하면 안 되기 때문이다. alternatives 가 적혀 있었다면 그것이 1군을 채웠을 것이다
2군 BigNumber, HighlightedMessage Gauge 행의 fallback 순서 그대로다. 이 순서가 점수보다 앞선다. PictogramChart·BarChart는 measure↔matrix 변환 규칙을 안 만들기로 해(B-30, 2026-09-05) 이 행에 없다
3군 따로 발동하지 않는다 확정 카테고리인 BigNumber 가 이미 2군에 들어 있기 때문이다. 군 판정은 태그마다 가장 낮은 군 하나만 매긴다
4군 발동 안 함 1·2군에서 후보가 나왔기 때문이다

그러면 3군은 언제 쓰나. 확정 카테고리가 1군에도 2군에도 없을 때의 보루다. 요청 type 이 성립 탈락해 1군에서 빠졌는데 확정된 곳이 그 요청의 fallback 행에도 없는 경우가 그것이고, 위 표의 1군 행에 적은 계약("성립 탈락한 요청 카테고리를 1군에 되살리지 않는다")이 성립하려면 이 자리가 반드시 있어야 한다. 2군과 역할이 다르다. 2군은 "물러날 후보들"이고 3군은 "그릴 수 있다고 판정된 하나"다.

1군이 비는 것이 결함이 아니다. 다만 여기에 구제 조항이 하나 있다. 생성기가 type 을 아예 안 적었으면 1군이 처음부터 비는데, 그때는 확정 카테고리를 1군에 넣어 준다. 이 분기는 2026-08-29 type 필수 개정으로 규약상 도달할 수 없는 경로가 됐다. 스키마 검증이 먼저 막기 때문이다. 코드에는 아직 남아 있고, 뺄지는 개정 반영 작업에서 함께 본다. 반대로 "확정 카테고리를 항상 1군에 넣는" 방식으로 바꾸면 안 된다. 그렇게 고쳐 봤더니 위 표의 첫 행 계약(성립 탈락한 요청 카테고리는 1군에서 빠진다)이 깨졌다. 그 자리는 3군이 맡는다.

4군은 발동했을 때만 질의에 적힌다. 안전망 태그를 늘 질의에 넣어 두면 "쓰지도 않은 안전망"까지 허용 태그가 되어 판정이 무뎌진다. 그래서 랭킹 결과에서 4군 후보가 실제로 나왔을 때만 그 태그를 질의 태그 집합에 되돌려 적는다. 이 안전망 자체가 실사용 10건 실행에서 발견된 구멍의 대응이다. 마지막 fallback 카테고리조차 색인에 없으면 후보가 0이 되던 문제였다.

1군 판정에는 함정이 하나 있었다. 1군을 채울 때 커널 어휘 41종 상수로 카테고리 여부를 보면 확장 4종(PageNavigation.*)이 1군에서 빠진다. 그런데 위의 구제 조항 때문에 alternatives 가 없는 컴포넌트는 무사해서 오래 안 보였다. alternatives 에 커널 종이 하나라도 있으면 그것이 1군을 채워 구제가 발동하지 않고 확정 카테고리가 질의에서 통째로 사라진다. resolvedId 는 멀쩡히 PageNavigation_Opening 인데 검색은 그 태그를 묻지 않으므로 지표에 흔적이 남지 않는다. 골든셋 실측에서 PageNavigation 40행 중 13행이 이 모양이었다(2026-08-26 코드리뷰). 지금은 주입된 45종 사전으로 판별한다.

군 안에서의 채점은 구조 적합도 넷이다. 대치가 하게 될 일을 미리 셈하는 것이라, 태그가 맞는지는 이미 군이 봤고 여기서는 "그 후보에 이 내용이 들어가는가"만 본다.

항목 가중치 무엇을 보나
unitFit 0.40 entity 수 대 RLSC 반복 단위 수. 단위가 모자라면(접힘 = 내용 손실) 남는 것(빈 단위)보다 크게 감점한다
itemFit 0.25 entity 당 상세 줄 수 대 단위당 Description 슬롯 수. 상세가 버려질 자리 부족을 잡는다
charFit 0.20 단위당 글자 용량. 총량이 아니라 단위 기준이라 단위 수가 달라도 왜곡이 없다
featureFit 0.15 수치가 있으면 Subtitle 자리, matrix 면 차트 노드, 구조 탐지 성공 여부

규약과 구현이 어긋나는 지점은 여기에도 남아 있다. 규약 5절은 "성립 조건을 못 채우면 후보에서 빠진다"는 이분법인데, comp-sign 은 제외하지 않고 뒤 군으로 미룬다. PoC 는 1군에서만 빼고 2군과 3군으로 내려보내므로 규약 쪽에 가깝지만 완전히 같지는 않다. 축을 맞추는 것이 I-5 다.

대치 입력 용어 세 층#

같은 "트리"라도 층이 셋이다. 문서와 코드에서 이름을 섞어 쓰면 fallback 이후 대치 계층을 따라갈 수 없다.

이름 무엇인가 만드는 곳과 쓰는 곳
Visual Summary structure YAML 의 entities, relations, dataset. 검색 질의와 structure projection(P1~P8)의 입력 생성기, validate.ts, projection.ts
textTree buildCompSearchData 가 markdown 을 파싱해 만든 대치용 텍스트 트리와 변형 compSignService.ts, buildContentReplacement 입력
text_map, text_node_roles 컴포넌트 시그니처의 슬롯 용량과 역할(chars, lines, role). 런타임 용량의 SSoT buildCompSignatures 적재, 채점과 대치
RLSC text slots PoC 자체 대치가 RLSC 서브트리의 Title, Description, Marker 슬롯에 직접 넣는 경로 binding.ts, rlsc.ts

다리는 structure → markdown → buildCompSearchData → textTree 다. comp-sign 경로는 Visual Summary YAML 을 직접 받지 않는다.

fallback 이후 대치 계층 다섯#

카테고리 fallback 으로 어떤 컴포넌트 종류를 고를지 정한 뒤, 무엇을 어디에 넣을지는 아래 계층을 따른다.

계층 무엇을 하나 코드 상태
복합 구조 순서(N1~N5) 노드가 여럿일 때 어느 노드부터 포기하나 없다 규약 확정(2026-08-28). 코드 미착수(I-11)
exact structure match 해시와 태그로 동일 구조 후보를 찾는다 comp-sign buildCompSearchData, PoC search.ts 동작한다. 풀이 얕으면 실패가 잦다
semantic fallback 카테고리 강등과 P1~P8 fallback.ts, projection.ts 규약과 코드 모두 완료
4단계 comp-sign 대치 textTree 와 시그니처로 텍스트를 넣는다 compSignService.ts, comp-sign 패키지 실서비스 경로. 규약 contract 미완
5단계 property 대치 이종 type 사이 카테고리 고유 속성 투영(좁은 뜻) T-7 추출만 있고 T-8 미착수 부분

PoC 규약 RLSC 대치(binding.ts)는 실서비스 경로가 아니다. Visual Summary structure 를 RLSC 슬롯 규칙으로 직접 넣는 MORDOR PoC 전용 기준선이고, 내용시각화 BE 가 쓰는 대치는 comp-sign buildContentReplacement 하나다. 두 경로를 같은 후보 풀과 같은 용량 근거로 재야 비교가 성립한다.

structure projection 과 property projection 은 이름만 비슷하고 하는 일이 다르다. 앞은 내용(질의 쪽 structure)을 옮기고 뒤는 카테고리 고유 속성(질의 쪽 params, 색인 쪽 properties)을 옮긴다. 여기서 property 는 좁은 뜻이다. 5단계 국면 이름 I.I.S. (Semantic Component Property) 의 'Property'(속성과 구조 대응 둘을 합친 넓은 뜻)와 구분한다. 1절 용어 사전의 표기 규칙을 따른다. P1~P8 만으로는 "List 안의 BigNumber" 처럼 서로 다른 카테고리 사이에서 속성을 옮기는 문제를 풀지 못하고, 그것이 5단계와 T-8 의 영역이다.

평가 pool 을 고정하는 규칙 다섯#

전 컴포넌트(로컬 수만 건, 실서비스 17만여 건)에 comp-sign 시그니처와 I.I.S. 속성을 한꺼번에 구울 수는 없다. 세 대치 경로를 공정하게 비교하려면 같은 후보 풀 위에 미리 구워 둬야 한다.

# 규칙
1 기준 pool 은 indexPool.ts 술어와 같다. sheet_json 과 structure_json 이 둘 다 있는 것이고, 확장 색인과 같은 집합을 쓴다
2 평가 pool 은 그중 고정 subset 이다. 실사용 50건(컴포넌트 65개)이 검색과 대치의 입력이다
3 후보 pool 은 로컬 인덱스 12,348건을 쓸 수 있다 (K-3, 2026-09-05). 2026-08-23 에 제안했던 약 5,000건은 그때 로컬이 2,468건이던 때의 목표였다. I.I.S. 파일럿은 그 제안과 비슷한 규모로 돌렸다. 경계 4,764건, 속성 3,955건(K-10). 실서비스는 178,095건이다
4 comp-sign 시그니처, I.I.S. 경계, I.I.S. 속성을 같은 design_object_id 집합에 굽는다. 한쪽만 있으면 5단계 경로 비교가 성립하지 않는다
5 5단계 화면은 속성 bake 와 T-8 규칙이 있어야 연동된다. 지금은 4단계 comp-sign 대 PoC RLSC 2-way 만 가능하다

비용 어림의 근거는 실측이다. 시그니처 빌드가 2만 건에 약 4.4분, 5만 건에 약 9.9분이고 properties 추출이 137건에 4분(입력 23만 토큰)이었다. 5,000건이면 그 추출이 약 2.4시간, 입력 약 840만 토큰으로 어림된다.

코드가 어디 있는가#

무엇 어디
규약을 코드로 옮긴 것(어휘, 타입, 스키마, capability 상수, establish/fallback/projection, query, audit, intersect) src/lib/visual-summary/
항목 트리를 markdown 으로 바꾸는 다리 src/lib/visual-summary/markdown.ts
결함 채점과 귀속 src/lib/visual-summary/audit.ts
교집합과 순위 융합(RRF) src/lib/visual-summary/intersect.ts
I.I.S. 대행 생산(경계 추출, properties·구조 대응 추출, 노드 검색, 노드 대치) src/lib/visual-summary/iis/
Summary 생성 프롬프트와 재생성 루프 src/lib/visual-summary/generator/
규약 예시 18종, 안티패턴 5종, 평가셋 50건, 생성 스냅샷 src/lib/visual-summary/fixtures/
두 경로가 공유하는 후보 풀 src/app/api/visual-summary/indexPool.ts
comp-sign 실제 코드 호출 src/app/api/visual-summary/compSignService.ts
Eregion 화면 src/app/visual-summary/ (canvas-frame, iis-boundary, iis-boundary-gold)
평가 도구 CLI scripts/visual-summary-eval.ts, scripts/visual-summary-poc.ts, scripts/visual-summary-real-poc.ts

src/lib/visual-summary/README.md 가 모듈 구성을 더 세밀하게 정리해 둔다. 핵심 파일은 다음과 같다. taxonomy.ts(어휘. 규약은 결합값 45종. 코드 반영은 I-14. 점과 밑줄 표기 변환), parse.ts(YAML 파싱과 silent corruption 계측), assignIds.ts(id 부여), validate.ts(파싱→id→검증 파이프라인), capability.ts(45종 상수), establish.ts(성립 조건 판정), capabilityCheck.ts(카테고리별 정합성 검사, 성립 조건과 params 키를 error/warning/info 로 나눠 판정), projection.ts(P1~P8 계산과 적용), fallback.ts(카테고리 확정), query.ts(검색 질의 변환기), sheet.ts(sheet JSON 텍스트 대치와 box 보정), pipeline.ts(end-to-end 조립).

Eregion 화면#

내부 도구(Eregion)의 사이드바에서 "Visual Summary PoC" 를 연다. 로컬 개발 환경에서는 계정 없이 열리고, 배포 빌드에서만 로그인 게이트가 강제된다. 플레이그라운드에서는 임의 입력이나 YAML 직접 검증을 돌리고, 복잡도 레벨 L1/L2/L3 토글과 세 레벨 동시 생성(K-4, K-5), 검색 엔진 토글(컴포넌트 단위 vs I.I.S. 노드 단위, 서로 폴백 없음, K-9)이 있다. 결과는 여정 뷰(RunFlowView.tsx, K-8)가 기본이다. 컴포넌트마다 구조, 확정, 검색, 매핑, 결과를 표로 보고 행을 고르면 카드로 펼친다. 기존 상세 trace 는 토글로 남긴다. 대치 결과 캔버스는 이미지 합성 서버를 왕복하지 않고 @miri-unicorn/miricanvas-editor-2 의 PageRootView(합성 서버가 쓰는 페이지 루트 컴포넌트)를 화면에 직접 마운트해서 그린다. 노드 엔진은 용량 자가 컴포넌트 전체에 붙어 있어 서브트리 대치에 못 대므로 결함 채점을 돌리지 않는다. 화면이 "이 엔진은 미채점"으로 적어 결함 없음과 구별한다.

명령어#

bash
# 1) 생성 — 돈이 들고 드물게 돈다. 결과 스냅샷은 저장소에 커밋한다
DOTENV_CONFIG_PATH=.env.local pnpm tsx scripts/visual-summary-eval.ts --generate --k 1

# 2) 채점 — LLM 호출이 없고 결정적이다
DOTENV_CONFIG_PATH=.env.local NODE_OPTIONS="--max-old-space-size=12288" \
  pnpm tsx scripts/visual-summary-eval.ts --score --top 10 --out eval/baseline --html

# 3) 회귀 비교 — 조건이 다르면 거부한다
pnpm tsx scripts/visual-summary-eval.ts --diff eval/before/report.json eval/after/report.json

실사용 데이터 E2E 실측 (2026-08-12, gpt-5.4-mini)#

실사용 로그 원문 10건으로 생성부터 실색인 검색, 내용 대치까지 끝까지 돌린 결과, 생성은 10/10 성공(재생성 루프 사용 7회), 검색은 15개 컴포넌트 전부 후보를 확보했으며(앞 절의 검색 군으로 1군 5, 2군 8, 3군 1, 4군 안전망 1. 절반 이상이 2군이라는 것은 생성기가 고른 카테고리를 색인이 그대로 대 주지 못한다는 뜻이다), 대치는 entity 56개 중 56개를 수용했다(43개 배치, 13개 접기). 이 실행에서 시스템 구멍 두 가지를 찾아 코드에 반영했다. 하나는 같은 fallback 군 안에서 순서가 점수에 밀리는 문제였고(2군 정렬을 태그 순위 우선으로 고정), 다른 하나는 마지막 fallback 카테고리가 색인에 없으면 후보가 0이 되는 문제였다(4군 안전망 검색 추가).

아직 남은 것#

PoC 가 직접 할 수 있는 것. 평가 pool 고정과 사전 적재(I-8), 노드 검색 정확도 판정(K-9-1), 사람 판정과 기계 판정의 일치율 측정, 랭킹 가중치 실험(MOR-1936).

남의 답을 기다리는 것. 평가셋을 38개 중분류로 넓히는 작업(MOR-1928, 허재영님). 프로덕션 I.I.S. 추출(K-11)은 쓰기 권한이 있는 사람이 스크립트를 돌려야 한다.

스쿼드가 정해야 하는 것. 운영 규모(178,095건) 색인을 어떻게 만들 것인가. 선택지는 이 저장소의 시그니처 워커를 comp-sign 4.3.x 로 올려 다시 적재하거나, 이석님 쪽 테이블을 직접 조회하는 것 두 가지다. 검색 후보 데이터 자체는 프로덕션에 이미 있다. 병목은 로컬과 운영 적재 경로이다.

↑ 목차로 · 7부 머리로

53장보고서 5 · 카테고리와 필드 전량 명세 파생파생물 · 5장의 보고서판

원문 report/sections/05-reference.md GitHub 에서 보기 47 KB

부록: 카테고리와 필드 전량 명세#

이 부록이 무엇인가#

2절이 규약의 설계 원칙과 fallback 규칙을 설명하되 카테고리와 필드는 대표 예시만 실었다. 이 부록은 그 자리를 채운다. semantic category 45종 전량, params 키 27종 전량, structure 아래 필드 전량, 45종의 fallback 순서 전량과 엣지 130개 전량, 아직 열린 할 일(2026-09-07 기준, B절은 전량 닫힘), 자기정합 검사 14가지, 꾸밈구조 형 정의를 표로 적는다. 읽고 나서 임의의 입력에 대해 어느 카테고리를 고를지, 그 카테고리에 무엇을 적을 수 있는지 판정할 수 있어야 한다.

찾는 것 절
대분류 21종이 각각 무엇을 담나 A
중분류 45종을 각각 언제 고르나 B
45종의 성립 조건과 실사용 빈도 C
카테고리별 params 키와 기본값 D
structure 아래 필드 전량과 값 범위 E
문서 사이에 아직 어긋나 있는 자리 F
검색 유형별로 어디로 물러나나. 45종 전량 G
fallback 엣지 130개 전량과 엣지마다 발동하는 projection 규칙 H
아직 열린 할 일 (B절은 닫힘) I
문서와 코드가 어긋나지 않는지 보는 검사 14가지 J
꾸밈구조 형 정의와 해석 예시 넷 K

SSoT 는 이 부록이 아니다. 값의 정본은 카테고리 어휘가 spec/schema/visual-summary-0.5.schema.json 의 semanticCategory enum, 성립 조건과 params 가 spec/schema/capability.json, 선택 기준 문장이 reference/semantic-taxonomy-v7.md(원본은 단어장 시트)다. 어긋나면 그쪽을 따른다.

A. 대분류 21종#

대분류는 중분류의 이름공간이다(2절 "type 을 문자열 하나로 적는 이유"). 아래는 스키마 semanticCategory enum 에 들어 있는 21종이고, 괄호 안이 단어장이 정한 정의다.

대분류 무엇을 담나
Enumeration 동등한 수준의 정보 단위를 관계나 흐름, 비교보다 목록화와 묶음 중심으로 보여줄 때
AttributeComparison 동일한 기준이나 속성 축을 기준으로 대상 간 차이, 강약점, 우열을 비교할 때
RelationalComparison 항목별 차이를 나열하기보다 대상 간 상대적 위치, 영역, 교집합, 구조적 연관성을 보여줄 때
Transformation 동일 대상의 전후 상태, 개선 방향, 변화 결과를 보여줄 때
ProcessFlow 시간표보다 절차, 순서, 실행 단계가 더 중요할 때
LogicFlow 조건, 판단, 분기, 의사결정 규칙에 따라 흐름이 달라지는 구조를 보여줄 때
Progression 상태, 수준, 성숙도, 성과가 단계적으로 높아지거나 발전하는 과정을 보여줄 때
TimelineSchedule 날짜, 기간, 순서, 마일스톤 등 시간 축을 기준으로 사건이나 작업을 보여줄 때
Hierarchy 상하 관계, 소속 관계, 계층 구조, 레이어 구조가 핵심일 때
ScopeCoverage 중심에서 외부로 확장되는 범위, 포함 영역, 적용 범위를 보여줄 때
Cycle 과정이나 상태가 한 방향으로 끝나지 않고 반복, 순환, 재귀적으로 이어질 때
ReasoningStructure 원인, 문제, 해결책, 조합 등 결과나 주장을 도출하는 논리 구조를 보여줄 때
KPIKeyMetric 핵심 숫자, 대표 성과 지표, 목표 대비 달성 상태를 강조할 때
TextBasedCommunication 별도의 관계 구조보다 문장이나 본문의 의미를 전달하는 것이 핵심일 때
QnACard 질문과 답변을 대응하여 보여줄 때
Utterance 질문, 대화, 증언, 인용문처럼 발화 형식의 콘텐츠를 보여줄 때
PortraitProfile 인물, 역할, 경력, 프로필 정보를 소개할 때
Conversion 단계가 진행될수록 대상이 선별, 축소, 전환되는 흐름과 변화 비율을 보여줄 때
ProportionComposition 전체 대비 구성 비율이나 상대적 규모 차이를 보여줄 때
Trend 시간이나 순서에 따른 수치 변화, 증가와 감소 패턴, 흐름을 보여줄 때
MagnitudeRanking 항목 간 수치 크기, 순위, 상대적 규모 차이를 비교할 때

enum 에 없는 대분류가 둘 있다. PageNavigation(페이지 역할 4종)은 2026-08-06 에 어휘로 편입하기로 정했지만(B-24) 스키마 enum 과 capability.json 에는 아직 반영돼 있지 않다. Geo(지도 전용 4종)는 지도 데이터가 있어야 성립해 structure 가 표현할 대상이 아니라서 뺐다(2026-08-17 스쿼드 확인). F절에 다시 적는다.

단어장 쪽 수와 맞춰 읽는 법. 단어장 v7 은 대분류 23종, 중분류 49종이고 일반 카탈로그 29종과 표차트 카탈로그 23종으로 갈라 쓴다. 두 카탈로그에 Timeline, HierarchicalTree, Flow 셋이 겹쳐 들어 있어 52 에서 3 을 빼면 49 다. 여기서 지도 전용 4종을 빼면 이 부록이 다루는 45종이 된다.

B. 중분류 45종: 언제 고르나#

번호는 spec/capability-table.md 2절의 행 번호와 같다. 표기는 규약(질의 쪽) 표기인 점 표기로 적었고, 색인과 검색이 읽는 저장 키는 같은 값의 밑줄 표기다(Enumeration.List 와 Enumeration_List 는 같은 카테고리다).

# 카테고리 언제 고르나
1 Enumeration.List 순서나 우열 없이 섹션 형태로 병렬 나열할 때. 시각 배치 형태가 달라도 동등 나열이면 모두 이 라벨 하나로 고른다
2 Enumeration.ListingTable 비교가 아니라 목록 정보를 행과 열로 구조화해 표로 정리할 때. 행이 항목이고 열이 그 항목의 속성이다
3 AttributeComparison.Versus 2개의 대상을 서로 대응시켜 차이점, 장단점, 우열을 직접 대조할 때. 세부 속성 비교보다 대결 구도 자체를 전달한다
4 AttributeComparison.ComparisonTable 여러 기준이나 속성을 행과 열로 정리해 여러 대상을 같은 기준으로 나란히 견줄 때. 행이 지표이고 열이 대상이라 ListingTable 과 축이 뒤집혀 있다
5 RelationalComparison.SWOT 내부와 외부, 긍정과 부정의 네 영역으로 전략적 상황 관계를 정리할 때
6 RelationalComparison.VennDiagram 집합 간 공통점과 차이점 관계를 원형 도형으로 교차해 동시에 보여줄 때
7 RelationalComparison.Quadrant 두 축을 기준으로 대상의 상대적 위치나 포지션을 네 영역으로 나누어 보여줄 때
8 Transformation.Transformation 동일 대상의 Before/After, AS-IS/TO-BE, 개선 전후 상태를 보여줄 때. 전후의 변화가 핵심이다
9 ProcessFlow.Steps 업무나 절차를 순차 단계로 구분해 진행 순서를 보여줄 때. 단계가 핵심이다
10 TimelineSchedule.Timeline 날짜, 기간, 순서, 마일스톤 등 시간 축을 기준으로 사건이나 작업을 보여줄 때
11 Progression.Staircase 상태, 수준, 성숙도, 성과가 단계적으로 상승하고 성장하는 것을 상향 형태로 보여줄 때
12 Cycle.Cycle 프로세스나 상태가 끝나지 않고 반복, 순환, 피드백되는 구조를 보여줄 때
13 LogicFlow.FlowChart 조건, 판단, 분기, 예외 처리에 따라 흐름이 달라지는 구조를 보여줄 때. 중간에 분기가 생기는 것이 핵심이다
14 Conversion.Funnel 많은 대상이 단계별로 줄어들거나 선별되고 전환되는 퍼널 전환을 보여줄 때
15 TimelineSchedule.GanttChart 작업별 시작, 종료, 기간, 병렬 진행을 일정표 형태로 보여줄 때. 열에 시간, 행에 작업을 둔다
16 TimelineSchedule.Calendar 월 단위의 일정이나 이벤트를 달력 형태로 보여줄 때
17 Hierarchy.HierarchicalTree 상위와 하위, 부모와 자식, 조직 구조처럼 계층적 연결 관계를 보여줄 때
18 Hierarchy.Pyramid 넓은 기반에서 좁은 정점으로 쌓이는 층 구조. 각 층의 너비와 면적이 중요도, 우선순위, 위계를 암시한다
19 ScopeCoverage.NestedScope 중심 개념을 기준으로 포함 범위와 적용 영역이 안쪽에서 바깥쪽으로 확장되는 동심원 구조. TAM/SAM/SOM 이 전형이다
20 ReasoningStructure.Fishbone 문제나 결과의 원인을 여러 갈래로 분해해 분석할 때. 문제를 중심에 두고 복수 원인이 퍼져나간다
21 ReasoningStructure.CombinedResult 여러 요소의 결합이 하나의 결과나 결론으로 이어지는 구조를 보여줄 때
22 ReasoningStructure.ProblemSolution 문제와 대응 방안, 기대 효과를 짝지어 보여줄 때
23 KPIKeyMetric.BigNumber 가장 중요한 수치나 KPI 를 크게 강조해 한눈에 전달할 때
24 KPIKeyMetric.Gauge 목표 대비 달성률, 진행률, 임계치 대비 상태를 게이지 형태로 보여줄 때
25 MagnitudeRanking.BarChart 항목 간 수치 크기, 순위, 상대적 규모를 막대 길이로 비교할 때
26 ProportionComposition.StackedBarChart 항목별 전체값 안의 구성 비중을 막대 세그먼트로 보여줄 때
27 ProportionComposition.PieChart 전체를 여러 조각으로 나누어 구성 비율을 보여줄 때
28 ProportionComposition.DonutChart 중앙 공간을 활용하면서 전체 대비 구성 비율을 보여줄 때
29 ProportionComposition.TreeMap 면적 크기로 전체 대비 구성 비율과 상대적 규모를 보여줄 때
30 ProportionComposition.PictogramChart 아이콘 반복이나 채움 정도로 비율과 수량을 직관적으로 보여줄 때
31 Trend.LineAreaChart 시간이나 순서에 따른 수치 변화와 추세를 선 또는 면적으로 보여줄 때
32 Trend.ComboChart 동일한 시간이나 순서 축 위에서 서로 다른 성격의 지표를 한 차트에 겹쳐 보여줄 때
33 AttributeComparison.RadarChart 동일한 평가 기준 여러 개에서 대상의 강약점이나 프로파일 차이를 수치로 비교할 때
34 RelationalComparison.BubbleChart 두 축의 위치와 버블 크기를 함께 써서 상대적 위치와 규모를 동시에 보여줄 때
35 TextBasedCommunication.HighlightedMessage 반드시 기억해야 할 하나의 핵심 문장이나 하나의 완결된 주장을 화면 중심에 크게 배치할 때
36 TextBasedCommunication.LongFormTextLayout 하나의 주제나 배경, 사례를 여러 문장 이상의 본문 흐름으로 풀어 설명할 때. 장문으로 시각화한 것이 핵심이다
37 QnACard.QnACard 질문과 답변이 쌍을 이루며 반복될 때. FAQ, OX 퀴즈, 골든벨이 전형이다
38 Utterance.DialogueChat 두 화자가 교번으로 메시지를 주고받는 대화를 말풍선이나 채팅 형태로 보여줄 때
39 Utterance.QuoteTestimonial 고객 후기, 인터뷰 인용, 핵심 발언을 강조할 때. 응답이나 교환 없이 단방향으로 제시한다
40 PortraitProfile.ProfileCard 인물의 사진, 이름, 역할, 핵심 정보를 한 카드 안에 압축해 소개할 때
41 PortraitProfile.ResumeCV 경력, 학력, 역량, 프로젝트 이력을 섹션과 구분선으로 문서형 배치할 때
42 PageNavigation.Opening 발표 슬라이드의 맨 앞장을 구성할 때만. 제목, 부제목, 발표자, 날짜를 중앙에 배치한다
43 PageNavigation.TableOfContents 발표의 전체 구성과 섹션, 순서를 번호와 제목으로 안내할 때. 흐름이나 단계 표현은 없다
44 PageNavigation.SectionDivider 발표 중간의 장 전환을 알릴 때. 콘텐츠 없이 단일 섹션의 번호와 제목만 중앙에 둔다
45 PageNavigation.Ending 발표 맨 마지막 장에서 종결 메시지만 크게 배치할 때. 본문 내용의 결론 요약은 해당하지 않는다

헷갈리기 쉬운 경계 넷. 첫째, ListingTable(행이 항목, 열이 속성)과 ComparisonTable(행이 지표, 열이 대상)은 전치 관계라서 무엇이 첫 열에 오는지로 가른다. 둘째, 발화 계열 셋은 교환이 있는지로 갈린다. QuoteTestimonial 은 단방향, QnACard 는 질문과 답변의 쌍, DialogueChat 은 두 화자의 교번이다. 셋째, NestedScope 와 HierarchicalTree 는 원래(2026-09-02 이전) 둘 다 children 을 쓰는 단일 사슬 포함 관계라 구조가 같아지고 동심원으로 그리는지로만 갈렸는데, 2026-09-04 B-34 로 NestedScope 가 children 을 아예 안 쓰게 되면서(entities 평평한 배열 + direction) 이제 데이터 구조 자체가 갈린다 — HierarchicalTree 는 가지가 갈라질 수 있고 NestedScope 는 단일 사슬로만 표현된다(golden-set-production-notes.md 2절이 이 갈라짐 여부가 실측한 진짜 판별 조건이라고 적는다). 넷째, Opening 과 Ending 은 원본 시트 정의문이 서로 겹쳐 있었고, 2026-08-25 에 황현진이 Opening 을 "맨 앞장만" 으로 좁혀 배타적으로 만들었다.

C. 45종의 성립 조건과 실사용 빈도#

순서는 B절과 같다. 개수 조건의 단위는 따로 적지 않으면 entities 개수이고, 표에 적힌 계열과 조각, 행과 축은 그 카테고리가 세는 단위다. 실측 표시는 배치 엔진 jari 0.12.0 의 LAYOUT_CONTENT_CONTRACTS 에서 그대로 가져온 값이고, 표시가 없는 값은 단어장 정의문에서 유도한 설계 판단이다. 비중은 2026-07-06 부터 07-12 까지 사용자 입력 7,168 block 의 질의 쪽 분포이고, 우선순위는 단어장 시트의 구현 우선순위 열(P0 이 가장 높다)이다.

카테고리 개수 dataset 필수 내용 비중과 우선순위
Enumeration.List 1 이상 없음 31.96% P0
Enumeration.ListingTable 행 2 이상 matrix matrix 또는 entities+columns 15.08% P0
AttributeComparison.Versus 정확히 2 없음 3.11% P0
AttributeComparison.ComparisonTable 열 2 이상 matrix matrix 또는 entities+columns 7.24% P0
RelationalComparison.SWOT 정확히 4 없음 0.11% P0
RelationalComparison.VennDiagram 2 이상 3 이하 실측 없음 0.08% P1
RelationalComparison.Quadrant 1 이상 matrix matrix 필수. 항목마다 x, y 0.14% P3
Transformation.Transformation 정확히 2 없음 0.86% P0
ProcessFlow.Steps 2 이상 없음 10.92% P0
TimelineSchedule.Timeline 2 이상 15 이하 실측 없음 4.27% P0
Progression.Staircase 2 이상 8 이하 실측 없음 0.46% P0
Cycle.Cycle 2 이상 8 이하 실측 없음 0.50% P0
LogicFlow.FlowChart 2 이상 없음 0.39% P3
Conversion.Funnel 2 이상 matrix (선택) 없음 0.07% P2
TimelineSchedule.GanttChart 행 1 이상 matrix matrix 필수. 작업마다 시작과 끝 0.28% P4
TimelineSchedule.Calendar 1 이상 matrix matrix 필수. 날짜와 일정 0.64% P4
Hierarchy.HierarchicalTree 1 이상 실측 children 필수 1.27% P1
Hierarchy.Pyramid 3 이상 없음 0.06% P1
ScopeCoverage.NestedScope 2 이상 없음 (2026-09-04 B-34, children 안 씀) 0.08% P1
ReasoningStructure.Fishbone 2 이상 없음 0.14% P3
ReasoningStructure.CombinedResult 2 이상 없음 0.17% P1
ReasoningStructure.ProblemSolution 1 이상 없음 0.85% P1
KPIKeyMetric.BigNumber 1 이상 measure (선택) value 필수 1.20% P1
KPIKeyMetric.Gauge 1 이상 measure measure.value 필수 0.42% P0
MagnitudeRanking.BarChart 계열 1 이상 matrix matrix 필수 1.67% P0
ProportionComposition.StackedBarChart 계열 2 이상 matrix matrix 필수 0.14% P0
ProportionComposition.PieChart 조각 2 이상 matrix matrix 필수 0.31% P0
ProportionComposition.DonutChart 조각 2 이상 matrix matrix 필수 0.20% P0
ProportionComposition.TreeMap 1 이상 matrix matrix 필수 0건 P4
ProportionComposition.PictogramChart 1 이상 matrix matrix 필수 0.06% P2
Trend.LineAreaChart 계열 1 이상, 축 2 이상 matrix matrix 필수 0.70% P0
Trend.ComboChart 계열 2 이상 matrix matrix 필수, 계열 type 2종 이상 또는 axis 2종 이상 0.25% P0
AttributeComparison.RadarChart 축 3 이상 matrix matrix 필수 0.03% P2
RelationalComparison.BubbleChart 1 이상 matrix matrix 필수. 항목마다 x, y, 크기 0건 P4
TextBasedCommunication.HighlightedMessage 정확히 1 없음 5.75% P0
TextBasedCommunication.LongFormTextLayout 1 이상 없음 4.77% P0
QnACard.QnACard 1 이상 없음 0.46% P2
Utterance.DialogueChat 2 이상 없음 0.18% P1
Utterance.QuoteTestimonial 1 이상 없음 0.25% P1
PortraitProfile.ProfileCard 1 이상 없음 1.09% P2
PortraitProfile.ResumeCV 1 이상 없음 0.22% P2
PageNavigation.Opening 정확히 1 제목이 있고 발표자, 날짜, 소속 중 하나 이상이 함께 온다 0.99% P1
PageNavigation.TableOfContents 2 이상 뒤에 올 섹션의 번호와 제목을 나열한다. 흐름이나 단계 표현이 없다 0.88% P1
PageNavigation.SectionDivider 정확히 1 다음 한 섹션의 번호와 제목. 본문 콘텐츠가 없다 0.24% P1
PageNavigation.Ending 정확히 1 종료를 알리는 단일 메시지. 별도 콘텐츠가 없다 0.47% P1

성립 조건이 내용을 요구하는 것은 여덟이다 (2026-09-04 이전은 아홉 — NestedScope 가 children 요구를 뺐다, B-34). BigNumber 와 Gauge 의 수치, HierarchicalTree 의 children, ComboChart 의 혼합 조건, 그리고 페이지 역할 4종의 내용 조건이다. 나머지 36종은 개수 조건과 matrix 유무만 본다. 옛 테이블에서 필수였던 apex, author, speaker, intersects 는 전부 기본값을 가진 선택 값으로 내려갔다.

상위 5종이 질의의 약 65% 다. List, ListingTable, Steps, ComparisonTable, HighlightedMessage 순이고, 나머지 20여 종은 각 0.5% 미만의 롱테일이다. TreeMap 과 BubbleChart 는 표본 7,168 block 에 한 번도 나오지 않았다.

42번부터 45번은 2026-08-27 에 채웠다. 페이지 역할 4종은 어휘에는 들어왔지만(B-24, 2026-08-06) 성립 조건이 오래 비어 있었다. C-4(검색 풀 정책, 정원석)가 닫혀야 채울 수 있다고 적혀 있었는데, C-4 가 정하는 것은 그 종이 뽑힌 뒤의 템플릿 공급이지 성립 조건이 아니었다. 단어장 원본 시트에 넷의 조건이 이미 있어서 그 값으로 채웠다. dataset 은 넷 다 쓰지 않고 entities 로 적는다. Opening 이면 제목과 발표자, 날짜가 각각 entity 이고 TableOfContents 면 섹션마다 entity 하나다. Opening은 위치가 아니라 내용으로만 판정한다 (2026-09-05 정원석 확정). 파이프라인이 페이지를 독립 처리해 위치를 알 수 있는 경로가 없어 "맨 앞장 전용"은 시스템이 보장할 수 있는 조건이 아니다 — 성립 조건(제목 + 발표자·날짜·소속 중 하나 이상)만으로 판정한다.

D. params 전량 27종#

params 는 표현 선택을 담는다. 스키마는 "객체다" 까지만 검사하고 허용 키와 기본값은 capability.json 의 reads.params 가 카테고리별로 정한다(2절 "params 를 열린 객체로 둔 이유"). 도착 카테고리가 선언하지 않은 키는 fallback 때 버려진다(P2). 성립 조건이 정해진 45종이 선언한 키를 모으면 27종이고, 아래가 전량이다.

키 어느 카테고리가 읽나 값과 기본값
apex Hierarchy.Pyramid 어느 쪽이 꼭대기인가. 기본값 last
axis AttributeComparison.ComparisonTable, Trend.ComboChart 두 카테고리에서 뜻이 다르다. 표에서는 첫 열이 기준 축인가, ComboChart 에서는 계열이 어느 축에 붙는가(left 기본값, right)
axisLabels RelationalComparison.Quadrant 두 축의 이름. 없으면 축 이름을 비운다
center Cycle.Cycle 원 가운데 라벨. 없으면 가운데를 비운다
centerLabel ProportionComposition.DonutChart 도넛 가운데 라벨. 없으면 가운데를 비운다
conversionRate Conversion.Funnel 단계 간 전환율 표기. 없으면 비율 표기를 생략한다
direction Progression.Staircase, Transformation.Transformation, ScopeCoverage.NestedScope Staircase 는 ascending 기본값, Transformation 은 forward(앞이 전) 기본값, NestedScope 는 inward/outward 중 inward(배열 첫 항목이 안쪽) 기본값 (2026-09-04 B-34 신설)
fill Trend.LineAreaChart 기본값 false. 참이면 area 로 그린다
header Enumeration.ListingTable, AttributeComparison.ComparisonTable 첫 행이 머리글인가
icon ProportionComposition.PictogramChart 반복할 아이콘. 없으면 기본 아이콘
labelPosition ProportionComposition.PieChart, ProportionComposition.DonutChart 조각 라벨 위치. 기본값 outside
marker ProcessFlow.Steps 단계 표식. 기본값 number
maxValue AttributeComparison.RadarChart 축의 최대값. 기본값은 계열 최대값
normalize ProportionComposition.StackedBarChart false 고정. true 는 렌더러가 지원하지 않아 2026-08-17 에 스펙에서 뺐다
order RelationalComparison.SWOT 네 영역의 순서. 기본값은 강점, 약점, 기회, 위협 순
orientation Hierarchy.HierarchicalTree, TimelineSchedule.Timeline, MagnitudeRanking.BarChart, ProportionComposition.StackedBarChart 방향. 트리는 vertical, Timeline 은 horizontal, 막대 둘은 vertical 이 기본값이다
rotation Cycle.Cycle 회전 방향. 기본값 clockwise
sequence ProcessFlow.Steps 단계 번호. 없으면 배열 순서를 쓴다
shape ScopeCoverage.NestedScope 도형. 기본값 circle. rectangle 도 논의됐으나 문서에 없고 확정 아니다
sizeScale RelationalComparison.BubbleChart 버블 크기 척도. 기본값 linear
sort MagnitudeRanking.BarChart 정렬. 기본값은 입력 순서
startAngle ProportionComposition.PieChart, ProportionComposition.DonutChart 첫 조각의 시작 각도. 기본값 0
title TextBasedCommunication.LongFormTextLayout 본문 제목. 없으면 본문만
type Trend.ComboChart 계열별 표현. 기본값 bar
unit TimelineSchedule.GanttChart 기간 단위. 기본값 month. entity.unit(수치 단위)과 이름만 같고 다른 것이다
unitPerIcon ProportionComposition.PictogramChart 아이콘 하나가 나타내는 수량. 기본값 1
weekStart TimelineSchedule.Calendar 주의 첫 요일. 기본값 Mon

params 를 하나도 선언하지 않는 카테고리가 16종이다. Enumeration.List, AttributeComparison.Versus, RelationalComparison.VennDiagram, LogicFlow.FlowChart, ReasoningStructure 셋, KPIKeyMetric 둘, ProportionComposition.TreeMap, TextBasedCommunication.HighlightedMessage, QnACard.QnACard, Utterance 둘, PortraitProfile 둘이다. 이 카테고리로 fallback 하면 출발 쪽 params 는 전부 버려진다.

이름이 겹치는 키가 셋 있다. axis(표의 기준 축인가 / 계열이 붙는 축), type(params.type 은 계열 표현, component.type 은 카테고리, column.type 은 열의 차트 표현), unit(params.unit 은 간트의 기간 단위, entity.unit 과 measure.unit 은 수치 단위)이다. P2 는 키 이름만 보고 통과시키므로 뜻이 다른데도 살아남는 자리가 생긴다. ComboChart 에서 ComparisonTable 로 가는 엣지가 그 예이고, 규약은 이 엣지에서 axis 를 살아남는 것으로 적지 않는다.

E. structure 전량#

structure 는 내용을 담고, 필드 이름이 모든 카테고리에서 같다(2절 설계 원칙). 카테고리가 못 읽는 필드는 오류가 아니라 무시다. 아래 표의 값 범위는 spec/schema/visual-summary-0.5.schema.json 에서 가져왔다.

E.1 Structure#

entities 와 dataset 중 적어도 하나가 있어야 한다.

필드 타입 무엇인가
entities[] Entity 배열, 1개 이상 항목 목록
relations[] Relation 배열, 기본값 빈 배열 이 컴포넌트 안 항목 간의 관계
dataset Measure 또는 Matrix 수치 데이터
columns[] Column 배열 entities 로 표를 적을 때의 헤더. dataset 없이도 쓴다. ListingTable 과 ComparisonTable 두 카테고리 전용이다
E.2 Entity#

label 만 필수다. 나머지는 그 카테고리가 읽을 수 있으면 읽는다.

필드 타입 무엇인가 주로 읽는 카테고리
id 문자열, ^[a-zA-Z0-9_-]+$ 파싱 뒤 후처리가 부여한다 공통
label 문자열, 빈 값 불허 항목 이름 공통 (필수)
items[] 문자열 배열 하위 설명 목록 공통
value 수 수치 BigNumber, Gauge
unit 문자열 단위 BigNumber, Gauge
period 문자열 기간 표기 Timeline, ResumeCV
emphasis 참거짓 강조 여부 BigNumber
weight 수 상대 비중 Pyramid
speaker 문자열 화자 DialogueChat
author 문자열 발화자 QuoteTestimonial
source 문자열 인용 출처 QuoteTestimonial
avatar 문자열 인물 사진 ProfileCard
organization 문자열 소속 ProfileCard
values[] 문자열, 수, 널의 배열 columns 와 위치로 대응하는 나머지 열 값 ListingTable, ComparisonTable
children[] Entity 배열 같은 컴포넌트 안의 하위 항목 HierarchicalTree, Fishbone, TreeMap, ResumeCV (NestedScope 는 2026-09-04 B-34 로 빠졌다 — entities 평평한 배열을 쓴다)
components[] Component 배열 컴포넌트 경계를 넘는 중첩. 1단계 생성기는 채우지 않는다 없음

어느 카테고리가 어느 내용 필드를 읽는지가 fallback 을 좌우한다. 도착이 읽지 않는 내용 필드는 버려지지 않고 그 entity 의 items 맨 뒤에 이름: 값 형태로 붙는다(P3). 예를 들어 ProfileCard 에서 List 로 내려가면 avatar 와 organization 이 items 로 옮겨진다.

E.3 Relation#
필드 타입 무엇인가
type intersects 또는 compares 이 둘만 허용한다
of[] 문자열 배열, 2개 이상 관계로 묶이는 entity 의 label. 파싱 뒤 id 로 바뀐다
desc 문자열 그 관계에만 속하는 설명

관계를 읽는 카테고리는 둘뿐이다. RelationalComparison.VennDiagram 이 intersects 를, AttributeComparison.Versus 가 compares 를 읽는다. 성립 조건이 정해진 나머지 39종으로 가면 relation.desc 가 of 첫 항목의 items 맨 뒤로 간다(P7).

관계 어휘를 새로 만드는 기준은 하나다. 구조로는 적을 수 없는 제3의 내용을 가진 관계만 어휘로 만든다. 배열 순서나 entity 내부 필드, children 으로 이미 적히는 것은 별도 관계로 만들지 않는다.

E.4 Dataset: 두 모양#

shape 값이 어느 모양인지 정한다.

measure(단일 수치). shape 와 value 가 필수다.

필드 타입 무엇인가
shape 고정값 "measure"
value 수 (필수) 값
unit 문자열 단위
min, max 수 눈금 범위. Gauge 기본값은 0 과 100
target 수 목표값
delta 수 증감
thresholds[] 객체 배열 임계치. at(수, 필수), label(문자열), state(safe, warn, danger)

matrix(행렬). shape 와 columns, rows 가 필수다.

필드 타입 무엇인가
shape 고정값 "matrix"
columns[] Column 배열, 1개 이상 열 정의. 첫 열이 항상 범주 축이다
rows[][] 배열의 배열, 1개 이상 각 칸은 문자열, 수, 널 중 하나다

Column. label 만 필수다.

필드 값 무엇인가
label 문자열 (필수) 열 이름
role category, value, x, y, size, start, end, date 열의 성격. 생략하면 첫 열은 category, 나머지는 value 로 간주한다
unit 문자열 단위
axis left, right 이중 축에서 어느 축에 붙나. ComboChart 가 읽는다
type bar, line, area 계열의 표현. ComboChart 가 읽는다

role 이 카테고리의 성립 조건과 이어진다. Quadrant 는 x 와 y, BubbleChart 는 x 와 y 와 size, GanttChart 는 start 와 end, Calendar 는 date 를 요구한다.

병합 헤더가 있는 표는 matrix 를 쓰지 않는다. Column 에 병합을 적을 자리가 없어서, ListingTable 과 ComparisonTable 두 카테고리만 columns 와 entities 의 조합을 대안으로 받는다(2026-08-06 추가). 이 경로로 채워진 컴포넌트는 matrix 가 필수인 카테고리로 fallback 하지 않는다.

E.5 Decoration#

카테고리와 무관한 꾸밈 선호다. structure 의 형제로 두고, page 수준과 컴포넌트 수준 양쪽에 적을 수 있다.

필드 대응하는 RLSC role 값
ornament Element.Decoration, LayoutContainer.Decoration, Element.Separator, LayoutContainer.Separator (Separator 흡수는 2026-09-05, B-21) prefer, indifferent(기본값), avoid
background Element.Background, LayoutContainer.Background 위와 같다

require(필수)는 없다. 꾸밈을 필수로 걸면 후보가 0이 되어 전부 List 로 강등되기 때문이다. 해석은 축마다 따로 하고 구체적인 쪽을 우선한다. 컴포넌트, page, 카테고리 기본값, 시스템 전역 기본값, indifferent 순이다. 1단계 생성기는 이 필드를 채우지 않는다.

F. 알려진 격차 다섯#

이 부록과 다음 부록을 쓰면서 문서 사이에 어긋난 자리가 다섯 확인됐다. 파생물 쪽이 규약 본문보다 뒤처진 것이 셋이고, 3번은 2026-08-27 에 닫혔다.

# 무엇이 어긋나 있나 어느 쪽이 맞나
1 visual-summary-0.5.schema.json 에 structure.columns 와 entity.values[] 가 없다 2026-08-29 에 닫혔다. 규약 2.2절에서 파일을 다시 내보냈다 —
2 semanticCategory enum 과 capability.json 에 PageNavigation 4종이 빠져 있다 2026-08-29 에 전부 닫혔다. 규약 enum 이 결합값 45종(0.5.3), capability.json 도 45종·엣지 131개가 됐다 —
3 capability-table.md 2.6절의 페이지 역할 4종 성립 조건은 2026-08-27 에 채워져 이 격차가 닫혔다. 남은 것은 Opening 이 맨 앞장 전용인지 하나다 닫힘 (2026-09-05, 정원석). 위치가 아니라 내용으로만 판정한다 — 파이프라인이 페이지 위치를 알 수 없어 "맨 앞장 전용"은 애초에 검증 불가능한 조건이었다
4 fallback 엣지 수가 131개와 128개로 함께 적혀 있다 2026-08-29 에 오탐으로 판정했다. 남아 있는 "128개"는 전부 "125개(3차 반영 뒤 128개)에서 131개로" 같은 이력 서술이고 현재 값을 주장하는 자리가 아니었다 2026-09-05, B-30으로 131→127, 이어서 B-27로 127→130으로 바뀌었다(측정값 갱신, 오탐 판정과는 별개). 현재 값은 130개다
5 capability.json 이 KPIKeyMetric.BigNumber 의 선택 내용에 delta 를 넣어 뒀는데 스키마의 entity 에는 delta 필드가 없다. measure 에만 있다 항목이 여럿인 BigNumber 는 증감을 적을 자리가 없다. 둘 중 어느 쪽을 맞출지 정해야 한다

spec/schema/README.md 가 정의한 자기정합 검사 14가지 중 S2(파일이 규약 2.2절과 같은가)와 C1(capability 종수가 스키마 결합값과 같은가)이 1번과 2번을 잡는 검사다. 마지막으로 전량을 돌린 것은 2026-08-04 였고 그 뒤 개정 다섯 번이 검사 없이 들어갔는데, 2026-08-29 에 전량 다시 돌려 14가지를 통과시켰다.

검사 정의 자체도 낡아 있었다. F2("어느 행도 대체 후보를 셋 이상 두지 않는가")는 두 칸 상한이 2026-08-17 에 없어져 폐기했고, F3 은 "종단 셋으로 끝나는가"였는데 같은 날 List 뒤에 ListingTable 을 안전망으로 덧붙인 행이 넷 생겨(List, SWOT, ProfileCard, TableOfContents) "거치거나 자신이 그 셋인가" 로 고쳤다. S3·C1·F1·F4·N1 의 수치도 45종·131개로 맞췄다. 이 실행에서 새로 잡힌 결함은 yaml-examples.md 5.2절 예시가 type: Hierarchy(대분류 단독)를 쓰고 있어 0.5.2 뒤 검증에 걸린 것 하나다.

G. 45종의 fallback 순서 전량#

이 표가 "검색 유형별로 어디로 물러나나"에 답한다. 2절이 대표 체인 여섯 줄만 실었고 여기가 전량이다. 순서는 B절과 C절과 같다. 원문은 spec/capability-table.md 3.2절이고 2026-08-17 에 3차에 걸쳐 45종 전량을 확정했다.

읽는 법. 가운데 열은 그 카테고리 하나의 후속 우선순위 목록이고 왼쪽부터 본다. 다른 카테고리의 행을 이어 붙이지 않는다. AttributeComparison.Versus 행이 ComparisonTable → List 인 것은 "Versus 가 안 되면 ComparisonTable, 그것도 안 되면 List" 라는 뜻이고, ComparisonTable 의 fallback 이 List 라는 뜻이 아니다. ComparisonTable 자신의 fallback 은 자기 행이 정하고 ListingTable → Versus → List 다. 그래서 Versus 에서 출발한 대치는 ListingTable 을 거치지 않는다(2026-08-04 확정). 목록 길이의 합이 H절 엣지 수 130과 같다(2026-09-05, B-30으로 131→127, 이어서 B-27로 127→130).

서로를 첫 자리로 가리키는 쌍이 여덟 있고 순환이 아니다. 검색 모듈이 자기 행만 따라가므로 도착 카테고리의 행으로 넘어가는 일이 없고, 되돌아갈 자리가 애초에 생기지 않는다. 방문 기록도 필요 없다.

카테고리 후속 우선순위 한 번 따라갈 때 잃는 것
Enumeration.List ListingTable 행과 열의 대응. List 자체는 entities 1개 이상이면 구조적으로 항상 성립하지만(2절 마지막 fallback 셋), 색인에 List 태그 후보가 없으면 ListingTable로 물러난다
Enumeration.ListingTable ComparisonTable → List 행과 열의 대응
AttributeComparison.Versus ComparisonTable → List 좌우 대비의 시각적 강조
AttributeComparison.ComparisonTable ListingTable → Versus → List 기준 축에 따른 비교 구도. Versus(정확히 2)를 거치면 다시 List로 접힐 수 있어 안전망으로 뒀다
AttributeComparison.RadarChart BarChart → LineAreaChart → ComparisonTable → List 다축 프로파일의 형태. ComparisonTable은 같은 AttributeComparison 대분류라 열 2개 이상이면 대부분 여기서 끝난다
RelationalComparison.SWOT List → ListingTable 내부와 외부, 긍정과 부정의 2축 배치. Quadrant·ComparisonTable 경유 대신 List를 1순위로 확정했다(원저자 판단 채택)
RelationalComparison.VennDiagram Versus → ComparisonTable → List 교집합 영역. Versus는 정확히 2가 조건이라 ComparisonTable 경유를 안전망으로 더했다
RelationalComparison.Quadrant ComparisonTable → List 2축 위 상대 위치
RelationalComparison.BubbleChart PieChart → DonutChart → BarChart → List x축 위치와 버블 크기. 같은 RelationalComparison 대분류인 PieChart·DonutChart를 먼저 거친다
Transformation.Transformation Steps → Versus → ProblemSolution → List 전후의 방향성. Steps(ProcessFlow, "단계")가 Versus보다 의미상 가깝다고 판단해 1순위로 뒀다
ProcessFlow.Steps Timeline → Staircase → List 절차라는 의미. Timeline은 시간축, Staircase는 상승 방향으로 읽힌다
TimelineSchedule.Timeline Steps → Staircase → List 시간축. Steps는 순서만, Staircase는 상승 방향만 남는다
Progression.Staircase Steps → Timeline → Pyramid → List 상승이라는 방향. Steps(순서), Timeline(시간축), Pyramid(위계)를 차례로 거친다
Cycle.Cycle Steps → List 순환. 끝이 처음으로 돌아간다는 사실
LogicFlow.FlowChart Steps → List 분기
Conversion.Funnel Steps → BarChart → LineAreaChart → ComparisonTable → List 단계마다 줄어든다는 사실. v1의 차트 후보 셋을 안전망으로 전부 살렸다(2026-08-17 사용자 결정)
TimelineSchedule.GanttChart Timeline → Calendar → ListingTable → List 기간의 길이와 병렬 진행. Calendar(같은 TimelineSchedule)를 안전망으로 더했다
TimelineSchedule.Calendar Timeline → ListingTable → GanttChart → List 날짜 격자. GanttChart(같은 TimelineSchedule)를 안전망으로 더했다
Hierarchy.HierarchicalTree Pyramid → List 분기 연결선. Pyramid(개수 3 이상)가 위계를 층으로 대신 표현한다
Hierarchy.Pyramid NestedScope → Staircase → List 층 넓이가 나타내던 위계. NestedScope와 상호참조라 각자 안전망이 없으면 둘 다 막힌다
ScopeCoverage.NestedScope Pyramid → Staircase → List 범위의 크기. Pyramid와 상호참조
ReasoningStructure.Fishbone ProblemSolution → List 원인이 한 결과로 모인다는 방향. 같은 ReasoningStructure 대분류인 ProblemSolution으로 직행한다
ReasoningStructure.CombinedResult Steps → ProblemSolution → List 결합의 방향. Steps(ProcessFlow, "단계")가 원인 분해보다 의미상 가깝다고 판단했다
ReasoningStructure.ProblemSolution CombinedResult → Transformation → Steps → List 문제와 해결의 짝. CombinedResult(같은 ReasoningStructure)를 먼저 거친다
KPIKeyMetric.BigNumber Gauge → HighlightedMessage → List 수치의 크기 강조. Gauge(같은 KPIKeyMetric)로 목표 대비 위치를 대신 표현한다. HighlightedMessage는 개수 초과 시 성립하지 않아(P8 폐기, B-27) List를 안전망으로 더했다
KPIKeyMetric.Gauge BigNumber → HighlightedMessage → List 목표 대비 위치. PictogramChart·BarChart는 matrix 필수라 measure 전용인 Gauge에서 변환할 projection 규칙이 없어 뺐다(B-30, 2026-09-05 확정). HighlightedMessage는 개수 초과 시 성립하지 않아(P8 폐기, B-27) List를 안전망으로 더했다
MagnitudeRanking.BarChart LineAreaChart → ComparisonTable → ListingTable → List 수치의 상대 크기. LineAreaChart(상호참조)를 거쳐 ComparisonTable, ListingTable로 물러난다
ProportionComposition.StackedBarChart BarChart → LineAreaChart → List 구성 비중. 계열 합계만 남는다. LineAreaChart를 안전망으로 더했다
ProportionComposition.PieChart DonutChart → BarChart → ComparisonTable → List 도넛과는 가운데 공간 차이뿐이다. ComparisonTable(열 2개 이상)을 안전망으로 더했다
ProportionComposition.DonutChart PieChart → BarChart → ComparisonTable → List 가운데 라벨 자리. ComparisonTable을 안전망으로 더했다
ProportionComposition.TreeMap PieChart → DonutChart → List 면적으로 나타내던 비율과 중첩. 같은 ProportionComposition 대분류인 PieChart, DonutChart를 먼저 거친다
ProportionComposition.PictogramChart BarChart → List 아이콘 반복의 직관성. Gauge·BigNumber는 둘 다 단일값 기반이라 뺐다(B-30, 2026-09-05 확정 — BigNumber도 entity당 value 하나가 필수라 P4로는 그 요구를 못 채운다). 같은 matrix 기반인 BarChart만 거치고 바로 List로 일반화한다
Trend.LineAreaChart BarChart → ComparisonTable → List 추세선의 연속성. BarChart와 상호참조, ComparisonTable을 안전망으로 더했다
Trend.ComboChart ComparisonTable → ListingTable → List 이중 축과 계열 타입 혼합. 차트 계열로는 물러나지 않는다. 계열이 전부 bar/line 인 ComboChart 는 언제나 이중 축이고, 이중 축은 ComboChart 에서만 지원되므로 막대나 선으로 내리면 반드시 축 하나가 사라진다 (2026-08-21 개정)
TextBasedCommunication.HighlightedMessage QuoteTestimonial → List → LongFormTextLayout 인용 부호와 출처 표기. entities 1개·한 문장이면 구조적으로 항상 성립하지만(2절 마지막 fallback 셋), 색인 공급이 부족하면 List나 LongFormTextLayout으로 물러난다
TextBasedCommunication.LongFormTextLayout List 여러 문장 본문 구조. entities 1개·여러 문장이면 구조적으로 항상 성립하지만, 색인 공급이 부족하면 List로 물러난다
QnACard.QnACard DialogueChat → List 카드 형태
Utterance.DialogueChat QuoteTestimonial → QnACard → List 말풍선과 화자 구분. QuoteTestimonial(같은 Utterance 대분류)을 먼저 거친다
Utterance.QuoteTestimonial DialogueChat → HighlightedMessage → List 인용 부호와 출처 표기. DialogueChat과 상호참조. HighlightedMessage는 개수 초과 시 성립하지 않아(P8 폐기, B-27) List를 안전망으로 더했다
PortraitProfile.ProfileCard ResumeCV → List → ListingTable 사진과 카드 배치. ResumeCV(같은 PortraitProfile)를 1순위로 확정했다
PortraitProfile.ResumeCV ProfileCard → Timeline → ListingTable → List 이력 항목의 기간 정렬. ProfileCard(같은 PortraitProfile)를 먼저 거친다
PageNavigation.Opening HighlightedMessage → List 없음. 발표 맨 앞장은 전형적으로 제목 한 줄짜리 단일 메시지형이라 HighlightedMessage를 1순위로 뒀다
PageNavigation.TableOfContents List → ListingTable 목차라는 구조. 나열형이라 List가 1순위이고, 항목이 많으면 ListingTable로 물러난다
PageNavigation.SectionDivider HighlightedMessage → List 없음. 장 전환은 제목 한 줄짜리 단일 메시지형이라 HighlightedMessage를 1순위로 뒀다
PageNavigation.Ending HighlightedMessage → List 없음. 마지막 장의 종결 메시지는 단일 메시지형이라 HighlightedMessage를 1순위로 뒀다

H. fallback 엣지 130개 전량과 적용 규칙#

G절의 각 행이 자기 카테고리에서 나가는 엣지를 정한다. 행마다의 목록 길이를 합하면 130개이고 중복이 없다. 규칙 열은 그 엣지에서 실제로 발동하는 projection 규칙이고 규칙 자체의 정의는 2절 'structure projection' 절에 있다. 원문은 spec/capability-table.md 3.5절이다.

엣지 수의 변천은 F절 4번에 적었다. 51개에서 75개(2026-08-04), 111개와 125개(2026-08-17), 131개(2026-08-21 ComboChart 개정)로 늘었다가, Gauge↔PictogramChart/BarChart measure↔matrix 엣지 3개와 PictogramChart→BigNumber(P4가 value 필수 조건을 못 채움) 1개가 빠지며 127개(2026-09-05, B-30)로 줄었다. 이어서 B-27로 P8(개수 초과 접기)을 폐기하면서, HighlightedMessage가 종단이던 세 행(BigNumber·Gauge·QuoteTestimonial)에 안전망 List를 더해 130개가 됐다. 130개가 현재 값이다.

출발 도착 규칙 무엇이 옮겨지나
BarChart ComparisonTable P1 P2 옮길 것이 없다. 같은 것을 읽는다
BarChart LineAreaChart P1 P2 옮길 것이 없다. 같은 것을 읽는다
BarChart List P1 P2 P3 P4 matrix 의 행이 entities 가 된다, unit, value 가 items 로 간다, orientation, sort 는 표현 선택이라 버린다
BarChart ListingTable P1 P2 옮길 것이 없다. 같은 것을 읽는다
BigNumber Gauge P1 P2 P3 unit, delta, emphasis
BigNumber HighlightedMessage P1 P2 P3 P5 P8 value와 unit이 entity로 간다, unit, delta, emphasis, 1개를 넘으면 나머지를 마지막 항목에 접는다
BubbleChart BarChart P1 P2 sizeScale 는 표현 선택이라 버린다
BubbleChart DonutChart P1 P2 옮길 것이 없다. 같은 것을 읽는다
BubbleChart List P1 P2 P3 P4 matrix 의 행이 entities 가 된다, unit, value 가 items 로 간다, sizeScale 는 표현 선택이라 버린다
BubbleChart PieChart P1 P2 옮길 것이 없다. 같은 것을 읽는다
Calendar GanttChart P1 P2 옮길 것이 없다. 같은 것을 읽는다
Calendar List P1 P2 P3 P4 matrix 의 행이 entities 가 된다, unit, value 가 items 로 간다, weekStart 는 표현 선택이라 버린다
Calendar ListingTable P1 P2 weekStart 는 표현 선택이라 버린다
Calendar Timeline P1 P2 P4 P8 matrix의 행이 entities가 된다, 15개를 넘으면 나머지를 마지막 항목에 접는다
CombinedResult List P1 P2 옮길 것이 없다. 같은 것을 읽는다
CombinedResult ProblemSolution P1 P2 옮길 것이 없다. 같은 것을 읽는다
CombinedResult Steps P1 P2 옮길 것이 없다. 같은 것을 읽는다
ComboChart ComparisonTable P1 P2 type 은 표현 선택이라 버린다. 표는 축을 그리지 않아 이중 축 표현이 사라진다
ComboChart List P1 P2 P3 P4 matrix 의 행이 entities 가 된다, unit, value 가 items 로 간다, axis, type 은 표현 선택이라 버린다
ComboChart ListingTable P1 P2 axis, type 은 표현 선택이라 버린다
ComparisonTable List P1 P2 P3 P4 matrix 의 행이 entities 가 된다, unit, value 가 items 로 간다, axis, header 는 표현 선택이라 버린다
ComparisonTable ListingTable P1 P2 axis 는 표현 선택이라 버린다
ComparisonTable Versus P1 P2 P4 P8 matrix의 행이 entities가 된다, 2개를 넘으면 나머지를 마지막 항목에 접는다
Cycle List P1 P2 center, rotation 는 표현 선택이라 버린다
Cycle Steps P1 P2 center, rotation 는 표현 선택이라 버린다
DialogueChat List P1 P2 P3 speaker 가 items 로 간다
DialogueChat QnACard P1 P2 P3 speaker 가 items 로 간다
DialogueChat QuoteTestimonial P1 P2 P3 speaker
DonutChart BarChart P1 P2 centerLabel 는 표현 선택이라 버린다
DonutChart ComparisonTable P1 P2 옮길 것이 없다. 같은 것을 읽는다
DonutChart List P1 P2 P3 P4 matrix 의 행이 entities 가 된다, unit, value 가 items 로 간다, centerLabel, labelPosition, startAngle 는 표현 선택이라 버린다
DonutChart PieChart P1 P2 centerLabel 는 표현 선택이라 버린다
Ending HighlightedMessage P1 P2 P8 1개를 넘으면 나머지를 마지막 항목에 접는다
Ending List P1 P2 옮길 것이 없다. 같은 것을 읽는다
Fishbone List P1 P2 P3 children 가 items 로 간다
Fishbone ProblemSolution P1 P2 P3 children
FlowChart List P1 P2 옮길 것이 없다. 같은 것을 읽는다
FlowChart Steps P1 P2 옮길 것이 없다. 같은 것을 읽는다
Funnel BarChart P1 P2 conversionRate 는 표현 선택이라 버린다
Funnel ComparisonTable P1 P2 conversionRate 는 표현 선택이라 버린다
Funnel LineAreaChart P1 P2 conversionRate 는 표현 선택이라 버린다
Funnel List P1 P2 P3 P4 matrix 의 행이 entities 가 된다, unit, value 가 items 로 간다, conversionRate 는 표현 선택이라 버린다
Funnel Steps P1 P2 P3 P4 matrix 의 행이 entities 가 된다, unit, value 가 items 로 간다, conversionRate 는 표현 선택이라 버린다
GanttChart Calendar P1 P2 옮길 것이 없다. 같은 것을 읽는다
GanttChart List P1 P2 P3 P4 matrix 의 행이 entities 가 된다, unit, value 가 items 로 간다, unit(기간 단위) 는 표현 선택이라 버린다
GanttChart ListingTable P1 P2 옮길 것이 없다. 같은 것을 읽는다
GanttChart Timeline P1 P2 P3 P4 P8 matrix 의 행이 entities 가 된다, unit, value 가 items 로 간다, 15개를 넘으면 나머지를 마지막 항목에 접는다
Gauge BigNumber P1 P2 P3 target, max, min, thresholds
Gauge HighlightedMessage P1 P2 P3 P5 P8 value와 unit이 entity로 간다, target, max, min, thresholds, 1개를 넘으면 나머지를 마지막 항목에 접는다
HierarchicalTree List P1 P2 P3 children 가 items 로 간다, orientation 는 표현 선택이라 버린다
HierarchicalTree Pyramid P1 P2 P3 children
HighlightedMessage List P1 P2 옮길 것이 없다. 같은 것을 읽는다
HighlightedMessage LongFormTextLayout P1 P2 옮길 것이 없다. 같은 것을 읽는다
HighlightedMessage QuoteTestimonial P1 P2 옮길 것이 없다. 같은 것을 읽는다
LineAreaChart BarChart P1 P2 fill 는 표현 선택이라 버린다
LineAreaChart ComparisonTable P1 P2 옮길 것이 없다. 같은 것을 읽는다
LineAreaChart List P1 P2 P3 P4 matrix 의 행이 entities 가 된다, unit, value 가 items 로 간다, fill 는 표현 선택이라 버린다
List ListingTable P1 P2 P6 entities가 matrix의 행이 된다
ListingTable ComparisonTable P1 P2 옮길 것이 없다. 같은 것을 읽는다
ListingTable List P1 P2 P3 P4 matrix 의 행이 entities 가 된다, unit, value 가 items 로 간다, header 는 표현 선택이라 버린다
LongFormTextLayout List P1 P2 옮길 것이 없다. 같은 것을 읽는다
NestedScope List P1 P2 P3 entities 가 배열 순서 그대로 items 로 간다, direction·shape 는 표현 선택이라 버린다
NestedScope Pyramid P1 P2 P3 entities 가 배열 순서 그대로 층 순서가 된다, weight 는 없어 층 높이가 균등해진다
NestedScope Staircase P1 P2 P3 P8 entities 가 배열 순서 그대로 간다, 8개를 넘으면 나머지를 마지막 항목에 접는다
Opening HighlightedMessage P1 P2 P8 1개를 넘으면 나머지를 마지막 항목에 접는다
Opening List P1 P2 옮길 것이 없다. 같은 것을 읽는다
PictogramChart BarChart P1 P2 옮길 것이 없다. 같은 것을 읽는다
PictogramChart List P1 P2 P4 matrix의 행이 entities가 된다
PieChart BarChart P1 P2 labelPosition, startAngle 는 표현 선택이라 버린다
PieChart ComparisonTable P1 P2 옮길 것이 없다. 같은 것을 읽는다
PieChart DonutChart P1 P2 labelPosition, startAngle 는 표현 선택이라 버린다
PieChart List P1 P2 P3 P4 matrix 의 행이 entities 가 된다, unit, value 가 items 로 간다, labelPosition, startAngle 는 표현 선택이라 버린다
ProblemSolution CombinedResult P1 P2 옮길 것이 없다. 같은 것을 읽는다
ProblemSolution List P1 P2 옮길 것이 없다. 같은 것을 읽는다
ProblemSolution Steps P1 P2 옮길 것이 없다. 같은 것을 읽는다
ProblemSolution Transformation P1 P2 P8 2개를 넘으면 나머지를 마지막 항목에 접는다
ProfileCard List P1 P2 P3 avatar, organization
ProfileCard ListingTable P1 P2 P3 P6 entities가 matrix의 행이 된다, avatar, organization
ProfileCard ResumeCV P1 P2 P3 avatar, organization
Pyramid List P1 P2 P3 weight 가 items 로 간다, apex 는 표현 선택이라 버린다
Pyramid NestedScope P1 P2 P3 entities 가 배열 순서 그대로 간다, weight 는 표현 선택이라 버린다
Pyramid Staircase P1 P2 P3 P8 weight 가 items 로 간다, apex 는 표현 선택이라 버린다, 8개를 넘으면 나머지를 마지막 항목에 접는다
QnACard DialogueChat P1 P2 옮길 것이 없다. 같은 것을 읽는다
QnACard List P1 P2 옮길 것이 없다. 같은 것을 읽는다
Quadrant ComparisonTable P1 P2 axisLabels 는 표현 선택이라 버린다
Quadrant List P1 P2 P3 P4 matrix 의 행이 entities 가 된다, unit, value 가 items 로 간다, axisLabels 는 표현 선택이라 버린다
QuoteTestimonial DialogueChat P1 P2 P3 author, source
QuoteTestimonial HighlightedMessage P1 P2 P3 P8 author, source 가 items 로 간다
RadarChart BarChart P1 P2 maxValue 는 표현 선택이라 버린다
RadarChart ComparisonTable P1 P2 옮길 것이 없다. 같은 것을 읽는다
RadarChart LineAreaChart P1 P2 옮길 것이 없다. 같은 것을 읽는다
RadarChart List P1 P2 P3 P4 matrix 의 행이 entities 가 된다, unit, value 가 items 로 간다, maxValue 는 표현 선택이라 버린다
ResumeCV List P1 P2 P3 children, period 가 items 로 간다
ResumeCV ListingTable P1 P2 P3 P6 children, period 가 items 로 간다, entities 가 matrix 의 행이 된다
ResumeCV ProfileCard P1 P2 P3 period, children
ResumeCV Timeline P1 P2 P3 P8 children, 15개를 넘으면 나머지를 마지막 항목에 접는다
SWOT List P1 P2 옮길 것이 없다. 같은 것을 읽는다
SWOT ListingTable P1 P2 P6 entities가 matrix의 행이 된다
SectionDivider HighlightedMessage P1 P2 P8 1개를 넘으면 나머지를 마지막 항목에 접는다
SectionDivider List P1 P2 옮길 것이 없다. 같은 것을 읽는다
StackedBarChart BarChart P1 P2 normalize 는 표현 선택이라 버린다
StackedBarChart LineAreaChart P1 P2 옮길 것이 없다. 같은 것을 읽는다
StackedBarChart List P1 P2 P3 P4 matrix 의 행이 entities 가 된다, unit, value 가 items 로 간다, normalize, orientation 는 표현 선택이라 버린다
Staircase List P1 P2 direction 는 표현 선택이라 버린다
Staircase Pyramid P1 P2 옮길 것이 없다. 같은 것을 읽는다
Staircase Steps P1 P2 direction 는 표현 선택이라 버린다
Staircase Timeline P1 P2 P8 15개를 넘으면 나머지를 마지막 항목에 접는다
Steps List P1 P2 marker, sequence 는 표현 선택이라 버린다
Steps Staircase P1 P2 P8 8개를 넘으면 나머지를 마지막 항목에 접는다
Steps Timeline P1 P2 P8 marker, sequence 는 표현 선택이라 버린다, 15개를 넘으면 나머지를 마지막 항목에 접는다
TableOfContents List P1 P2 옮길 것이 없다. 같은 것을 읽는다
TableOfContents ListingTable P1 P2 P6 entities가 matrix의 행이 된다
Timeline List P1 P2 P3 period 가 items 로 간다, orientation 는 표현 선택이라 버린다
Timeline Staircase P1 P2 P3 P8 period, 8개를 넘으면 나머지를 마지막 항목에 접는다
Timeline Steps P1 P2 P3 period 가 items 로 간다, orientation 는 표현 선택이라 버린다
Transformation List P1 P2 direction 는 표현 선택이라 버린다
Transformation ProblemSolution P1 P2 옮길 것이 없다. 같은 것을 읽는다
Transformation Steps P1 P2 옮길 것이 없다. 같은 것을 읽는다
Transformation Versus P1 P2 direction 는 표현 선택이라 버린다
TreeMap DonutChart P1 P2 P3 children
TreeMap List P1 P2 P3 P4 matrix 의 행이 entities 가 된다, unit, value 가 items 로 간다
TreeMap PieChart P1 P2 P3 children
VennDiagram ComparisonTable P1 P2 P6 P7 entities 가 matrix 의 행이 된다, 관계의 desc 가 items 로 간다
VennDiagram List P1 P2 P7 관계의 desc 가 items 로 간다
VennDiagram Versus P1 P2 P7 P8 관계의 desc가 items로 간다, 2개를 넘으면 나머지를 마지막 항목에 접는다
Versus ComparisonTable P1 P2 P6 P7 entities 가 matrix 의 행이 된다, 관계의 desc 가 items 로 간다
Versus List P1 P2 P7 관계의 desc 가 items 로 간다

I. 열린 항목 (2026-09-07 기준)#

본문 3절은 할 일 SSoT 의 절 구조와 절마다의 남은 건수만 실었다. 이 부록이 그 항목이다. 원문은 TODO.md 이고 각 항목의 근거 문단도 거기 있다. 상태가 [x](닫힘)인 항목은 뺐다. 문서 정합 백로그(H절)는 저장소 전체의 표기 통일 작업이라 이 부록에서 다루지 않는다.

B절은 2026-09-05 에 전량 닫혔다. 규약 열린 결정을 이 부록에 다시 나열하지 않는다.

A절. 규약 확정 (1건)

# 무엇을 정해야 하나 누가, 어떤 근거로
A-4 스마트 다이어그램과 스마트 오브젝트 데이터 모델 정합 확인 정원석

C절. 2단계 배포 (2건)

# 무엇을 정해야 하나 누가, 어떤 근거로
C-6 태깅 증량분 재태깅과 재전달 김수현, 박지우
C-7 증량분 반영 후 README.md 3절 컴포넌트 분포 재측정 정원석

D절. 스쿼드 차원에서 정할 것 (1건)

# 무엇을 정해야 하나 누가, 어떤 근거로
D-8 이종중첩 템플릿을 검색 풀에 되돌릴지 (D-7 부분 철회) 이석, 김수현, 정원석. 복합 구조 순서의 N1 과 N2 가 이 결정에 걸려 있다

E절. 7/24 회의에서 새로 받은 것 (3건)

# 무엇을 정해야 하나 누가, 어떤 근거로
E-1 시멘틱 컴포넌트 정의(규약)와 '문제 → 인과 분해 → 해결 방법 → 목표'의 링크를 문서로 정리해 공유 정원석
E-2 폴백(대치 가능) 카테고리 테이블 정리, 유형 간 관계도로 스코어링 가능성 검토 정원석
E-3 유효 컴포넌트 응집성 게이트와 node 건을 별도 트랙으로 잡기 정원석

F절. 마감 후로 미룬 것 (사후 검증) (1건)

# 무엇을 정해야 하나 누가, 어떤 근거로
F-2 생성기에 silent corruption 지표를 처음부터 내장 미정

I절. 내용 대치 contract (TODO 훑기 표 14건. I-12 만 닫힘)

# 무엇을 정해야 하나 누가, 어떤 근거로
I-1 semantic category 별 필요 속성 목록 만들기 → MOR-1932 정원석(초안) → 류민, 신영빈
I-2 세 가지를 하나로 정렬 → MOR-1933 류민, 신영빈, 이석
I-3 폴백 등급 층을 규약에 정의 정원석 → 이석, BE
I-4 구간 ①의 속성을 comp_signs 로 넘기는 컬럼과 경로 정의 → MOR-1935 정원석, 이석, 이강윤
I-5 comp-sign 실물을 capability-table.md 에 반영 정원석
I-6 rankCompCandidates 위계 가중치 A/B → MOR-1936 정원석, 이석
I-7 cross-type property projection (T-8) 규칙 초안. StackedBarChart isStacked 쓰기(I-15)를 포함 정원석
I-8 PoC 평가 pool 고정 및 사전 적재 정원석
I-9 T-8 property 기반 대치 PoC 정원석
I-10 속성 골든셋 사람 확정 UI 허재영, 황현진
I-11 복합 구조 fallback 오케스트레이션 정원석
I-13 복잡도 레벨 토글. K-4·K-5 로 화면과 프롬프트 조립은 구현됨. TODO 항목은 아직 열어 둠 정원석
I-14 type 필수와 어휘 좁힘의 코드 반영 정원석
I-15 StackedBarChart 대치 시 엔진 isStacked 세팅 정원석
I-16 대치 불가 카테고리를 생성기 어휘에서 제외 (generatable flag) 정원석

J절. 생성 모델 파인튜닝 (8건)

# 무엇을 정해야 하나 누가, 어떤 근거로
J-1 평가셋을 38 중분류로 확장 → MOR-1928 없음. 분할 찌꺼기를 먼저 걷어내야 한다 (아래)
J-2 few-shot 기준선 측정 (튜닝 없이 어디까지 되나) J-1, 스키마 확정
J-3 Steps 역생성 파일럿 스키마 확정, CORE/DESC/DECO/OUT 정의 확인 (I절)
J-4 round-trip 검증 파이프라인 J-3
J-5 카테고리 확대 (ListingTable, ComparisonTable) RLSC 대응 확인
J-6 라벨링 항목과 지침 확정 B-1, B-2 등급 논의, importance 필드 결정
J-7 SFT J-3, J-4
J-8 선호 학습 J-6, J-7

K절. Week 27 PoC (남은 2건)

# 무엇을 정해야 하나 누가, 어떤 근거로
K-9-1 노드 검색 정확도 실측. 두 엔진을 같은 입력에 돌려 사람이 판정 정원석
K-11 프로덕션 I.I.S. 추출. AI 세션에서는 프로덕션 쓰기를 못 함 쓰기 권한이 있는 사람

J. 자기정합 검사 14가지 전량#

자동 생성 도구를 저장소에 두지 않는다. 규약 문서, capability.json, 스키마 파일, YAML 예시가 서로 어긋나지 않는지는 작업할 때 스크립트를 짜서 돌린다. 2026-08-29 에 전량 다시 돌려 통과시켰고, 2026-09-05 에 fallback 엣지를 정책에 맞게 130개로 맞췄다. 원문은 spec/schema/README.md 다.

# 무엇을 보나
S1 규약 2.2절 JSON Schema 가 draft 2020-12 로 유효한가
S2 visual-summary-0.5.schema.json 이 그 2.2절과 같은 내용인가
S3 semanticCategory enum 이 결합값 45종이고 중복이 없으며 대분류 단독이 섞이지 않았는가
E1 문서의 전체 문서 YAML 예시가 전량 스키마를 통과하는가
E2 안티패턴 절의 예시는 스키마가 거부하는가
C1 capability 2절 45종이 스키마 결합값 45종과 같고 번호가 1부터 45까지 연속인가
C2 capability.json 이 capability 2절과 3.2절과 일치하는가
C3 예시가 자기가 선언한 카테고리의 성립 조건을 채우는가
F1 3.2절 후속 목록의 도착이 45종 안에 있는가
F2 폐기된 검사다. 두 칸 상한이 2026-08-17 에 없어졌다
F3 모든 행이 마지막 fallback 셋 중 하나를 거치거나 자신이 그 셋인가
F4 3.5절 엣지 표가 3.2절에서 유도한 그래프 엣지와 같고 중복이 없는가. 2026-09-05 기준 130개
F5 3.5절의 규칙이 capability.json 에서 계산한 것과 같은가
N1 본문이 말하는 수(45, 130, 아홉)가 표의 실제 수와 맞는가

F5 가 이 검사를 살아 있게 한다. capability.json 의 reads 로 엣지마다의 규칙(P1~P8)을 다시 계산해 표와 대조한다. 선택 내용과 속성의 구분이 틀리면 이 검사가 걸린다.

코드 쪽에는 같은 대조가 살아 있다. src/lib/visual-summary/capability.test.ts 가 spec/schema/capability.json 과 45종 전량을 대조한다. 다만 docs/ 가 비공개 submodule 이라 CI 체크아웃에 없어서 로컬에서만 돌고 CI 에서는 건너뛴다.

K. 꾸밈구조 형 정의와 해석 예시#

2절이 꾸밈구조의 설계 근거와 축, 값, 층을 설명한다. 이 부록은 실물 형과 해석 예시를 적는다. 원문은 design/decoration-model.md 5절이고 확정 표기는 규약 2.1절, 2.2절, 2.7절이 갖는다.

붙는 자리는 structure 의 형제다. params 는 카테고리별 표현 선택이고 꾸밈구조는 카테고리와 무관하다. structure 는 내용이고 꾸밈구조는 내용이 아니다. page 수준에도 같은 키를 연다.

ts
// 꾸밈 축에 담기는 값. 이 셋이 전부다
export type Preference = 'prefer' | 'indifferent' | 'avoid';

// 꾸밈구조. 모양은 하나다. 축 하나가 RLSC role 하나에 대응한다
// minimal 이나 rich 같은 프리셋 문자열은 받지 않는다
export interface Decoration {
  ornament?: Preference;    // Element.Decoration, LayoutContainer.Decoration, Element.Separator, LayoutContainer.Separator
  background?: Preference;  // Element.Background, LayoutContainer.Background
}

export interface VisualSummary {
  version: '0.5';
  title: string;
  components: Component[];
  relations?: Relation[];
  decoration?: Decoration;   // page 수준. 아래 컴포넌트의 기본값이 된다
}

export interface Component {
  id?: string;
  type?: SemanticCategory;
  alternatives?: SemanticCategory[];
  params?: Record<string, unknown>;
  structure: Structure;
  decoration?: Decoration;   // 이 컴포넌트에만 적용된다
}

JSON Schema 조각은 additionalProperties: false 를 쓴다. 없는 축 이름을 적으면 거부된다. 축을 늘릴 때 스키마를 함께 고쳐야 한다는 뜻이고 의도한 것이다. 오타로 적은 축이 조용히 무시되면 적었는데 적용되지 않는 사고가 난다.

json
{
  "decoration": {
    "type": "object",
    "additionalProperties": false,
    "minProperties": 1,
    "properties": {
      "ornament":   { "$ref": "#/$defs/preference" },
      "background": { "$ref": "#/$defs/preference" }
    }
  },
  "preference": {
    "type": "string",
    "enum": ["prefer", "indifferent", "avoid"],
    "default": "indifferent"
  }
}

예시 1. 아무것도 안 적은 경우다. 1단계의 기본 동작이다.

yaml
version: '0.5'
title: 분기 실적
components:
  - type: Enumeration.List
    structure:
      entities: [ { label: 신규 계약, items: [12건] } ]

decoration 이 없으므로 시스템 전역 기본값이 그대로 적용된다. 전역이 '꾸밈 많은 것 우선'이면 두 축이 prefer 로 해석된다.

예시 2. page 값을 컴포넌트가 축 단위로 덮는다.

yaml
decoration:                   # page 수준. 회의 용어로는 rich 에 해당한다
  ornament: prefer
  background: prefer
components:
  - type: Enumeration.List    # 물려받는다. 둘 다 prefer
    structure: { entities: [ ] }
  - type: ProcessFlow.Steps
    decoration:
      background: avoid       # 이 축만 덮는다
    structure: { entities: [ ] }

두 번째 컴포넌트의 해석 결과는 ornament: prefer(page 에서 물려받음)와 background: avoid(자기 값)다. ornament 를 다시 적지 않아도 유지된다. 병합이 축 단위인 것이 이 예시의 요점이다. 층 전체를 통째로 갈아치우면 이 모양을 표현할 수 없다.

예시 3. 물려받은 값을 취소한다.

yaml
decoration:                   # 회의 용어로는 minimal
  ornament: avoid
  background: avoid
components:
  - type: Enumeration.List
    decoration:
      background: indifferent # 배경만 중립으로 되돌린다
    structure: { entities: [ ] }

결과는 ornament: avoid, background: indifferent 다. indifferent 를 명시적 값으로 둔 이유가 이 자리다. 축을 빼면 page 의 avoid 를 그대로 물려받는다.

예시 4. 카테고리 기본값과 만난다.

yaml
components:
  - type: Enumeration.List    # 카테고리 기본값이 background: prefer 라고 가정
    structure: { entities: [ ] }
  - type: Enumeration.List
    decoration: { background: avoid }
    structure: { entities: [ ] }

첫째는 카테고리 기본값을 받아 background: prefer 이고, 둘째는 자기 값이 있으므로 avoid 를 우선한다. 45종의 카테고리 기본값은 아직 안 채웠다.

위 예시는 "적을 수 있다"를 보인 것이고 "생성기가 이렇게 쓴다"가 아니다. 1단계에서 이 필드는 optional 이고 생성기가 채우지 않는다. 값은 시스템 전역 기본값에서 온다. 선례가 둘 있다. entity.components[] 와 page 수준 relations 가 같은 방식으로 열려 있다.

↑ 목차로 · 7부 머리로

54장보고서 6 · 45종 데이터 구조 예시 파생파생물

원문 report/sections/06-examples.md GitHub 에서 보기 34 KB

부록: 카테고리 45종 데이터 구조 예시#

이 부록을 읽는 법#

앞 부록이 필드 목록을 표로 적었다면 이 부록은 같은 것을 실물 YAML 로 적는다. 45종 각각에 예시를 하나씩 두고, 그 카테고리가 선언한 params 를 전부 채우고 structure 도 선택 내용까지 채운 모양으로 적었다. 최소 예시가 아니라 최대 예시다. 실제 생성물은 이보다 훨씬 짧고, 선택 값을 생략하면 규약이 정한 기본값으로 그려진다.

번호는 앞 부록 B절, C절과 같고 spec/capability-table.md 2절의 행 번호와도 같다.

params 를 못 채운 자리가 20종이다. 45종 중 25종만 params 키를 선언한다. 나머지 20종(params 를 선언하지 않는 16종과 페이지 역할 4종)은 채울 키가 없어서 예시에도 params 가 없다. 선언하지 않은 키를 적으면 그 카테고리에서는 뜻이 없고 fallback 때 P2 로 버려진다.

모든 예시는 검증을 통과한 값이다. spec/schema-v0.5.md 2.2절의 JSON Schema 로 스키마 검증을 돌렸고, spec/schema/capability.json 과 대조해 개수 조건, dataset 모양, 선언한 params 키 전량 포함 셋을 함께 확인했다.

찾는 것 절
나열과 비교 (1번부터 8번) 1
순서와 흐름 (9번부터 16번) 2
계층과 범위 (17번부터 22번) 3
수치 (23번부터 34번) 4
텍스트와 사람 (35번부터 41번) 5
페이지 역할 (42번부터 45번) 6
컴포넌트가 둘 이상일 때: 병치와 포함 7

예시는 components[] 안의 컴포넌트 한 벌만 적는다. 실제 문서는 version, title 이 최상위에 붙고 components[] 가 배열이다. 7절이 그 온전한 모양을 보여준다.


1. 나열과 비교#

1. Enumeration.List 선언한 params 가 없다. 선택 내용은 items 하나다.

yaml
- type: Enumeration.List
  structure:
    entities:
      - label: 콘텐츠 제작
        items: ["템플릿 8,200종", 월 신규 300종]
      - label: 협업
        items: [실시간 공동 편집, 댓글과 승인]
      - label: 배포
        items: [웹 링크, PDF, 이미지]

2. Enumeration.ListingTable 행이 항목이고 열이 그 항목의 속성이다.

yaml
- type: Enumeration.ListingTable
  params:
    header: true
  structure:
    dataset:
      shape: matrix
      columns:
        - { label: 플랜, role: category }
        - { label: 월 요금, role: value, unit: 원 }
        - { label: 저장 용량, role: value, unit: GB }
      rows:
        - [무료, 0, 2]
        - [프로, 12900, 100]
        - [팀, 24900, 1000]

3. AttributeComparison.Versus 정확히 2개다. compares 관계가 가운데 대비 문구를 담는다.

yaml
- type: AttributeComparison.Versus
  structure:
    entities:
      - label: 사내 구축
        items: [초기 비용 높음, 데이터 통제권 확보, 운영 인력 필요]
      - label: 클라우드 구독
        items: [초기 비용 낮음, 확장이 즉시, 월 비용 지속]
    relations:
      - type: compares
        of: [사내 구축, 클라우드 구독]
        desc: 3년 총소유비용은 사용 규모 200석을 넘어서면 역전된다

4. AttributeComparison.ComparisonTable 행이 지표이고 열이 대상이다.

yaml
- type: AttributeComparison.ComparisonTable
  params:
    header: true
    axis: true
  structure:
    dataset:
      shape: matrix
      columns:
        - { label: 평가 항목, role: category }
        - { label: A 사, role: value }
        - { label: B 사, role: value }
        - { label: C 사, role: value }
      rows:
        - [도입 기간, 2주, 6주, 4주]
        - [연 비용, 1200, 2400, 1800]
        - [한국어 지원, 있음, 없음, 있음]

4-1. 병합 헤더가 있는 표는 matrix 대신 entities 와 columns 를 쓴다. ListingTable 과 ComparisonTable 두 카테고리만 이 경로를 받는다(2026-08-06 추가). entity.label 이 첫 열이고 entity.values[] 가 나머지 열이며, 상위 범주가 하위 행을 거느리는 병합은 entity.children[] 로 적는다.

yaml
- type: AttributeComparison.ComparisonTable
  params:
    header: true
    axis: true
  structure:
    columns:
      - { label: 항목 }
      - { label: 1Q }
      - { label: 2Q }
      - { label: 3Q }
    entities:
      - label: PROJECT 01
        children:
          - { label: 성장률, values: ["35%", "67%", "55%"] }
          - { label: 매출, values: [14000, 25000, 18000] }
      - label: PROJECT 02
        children:
          - { label: 성장률, values: ["12%", "20%", "31%"] }
          - { label: 매출, values: [8000, 9500, 12400] }

5. RelationalComparison.SWOT 정확히 4개다.

yaml
- type: RelationalComparison.SWOT
  params:
    order: [strength, weakness, opportunity, threat]
  structure:
    entities:
      - label: 강점
        items: [국내 점유율 1위, 템플릿 자산 8,200종]
      - label: 약점
        items: [해외 인지도 낮음, 영어 콘텐츠 부족]
      - label: 기회
        items: [생성 AI 수요 확대, 원격 근무 정착]
      - label: 위협
        items: [글로벌 경쟁사 가격 인하, 오픈소스 대안 등장]

6. RelationalComparison.VennDiagram 2개 이상 3개 이하다(실측). intersects 관계가 교집합 영역의 문구를 담는다.

yaml
- type: RelationalComparison.VennDiagram
  structure:
    entities:
      - label: 전통
        items: [유산, 정체성, 신뢰]
      - label: 변화
        items: [혁신, 적응, 미래]
    relations:
      - type: intersects
        of: [전통, 변화]
        desc: 두 요소를 조화롭게 관리하는 것이 장수기업 HR 의 핵심이다

7. RelationalComparison.Quadrant matrix 가 필수이고 항목마다 x 와 y 가 있어야 한다.

yaml
- type: RelationalComparison.Quadrant
  params:
    axisLabels: { x: 시장 성장률, y: 상대적 점유율 }
  structure:
    dataset:
      shape: matrix
      columns:
        - { label: 제품, role: category }
        - { label: 성장률, role: x, unit: "%" }
        - { label: 점유율, role: y, unit: "%" }
      rows:
        - [디자인 편집기, 24, 41]
        - [프레젠테이션, 31, 18]
        - [인쇄 주문, 4, 52]
        - [영상 편집, 38, 6]

8. Transformation.Transformation 정확히 2개이고 앞이 전, 뒤가 후다.

yaml
- type: Transformation.Transformation
  params:
    direction: forward
  structure:
    entities:
      - label: AS-IS
        items: [수작업 배치, 페이지당 12분, 오탈자 검수 별도]
      - label: TO-BE
        items: [자동 배치, 페이지당 40초, 검수 규칙 내장]

2. 순서와 흐름#

9. ProcessFlow.Steps 2개 이상이다.

yaml
- type: ProcessFlow.Steps
  params:
    sequence: [1, 2, 3, 4]
    marker: number
  structure:
    entities:
      - label: 요구 정리
        items: [담당자 인터뷰, 기존 문서 수집]
      - label: 초안 생성
        items: [자동 배치, 이미지 후보 제시]
      - label: 검수
        items: [사내 리뷰, 수정 반영]
      - label: 배포
        items: [링크 공유, PDF 내보내기]

10. TimelineSchedule.Timeline 2개 이상 15개 이하다(실측). period 가 선택 내용이다.

yaml
- type: TimelineSchedule.Timeline
  params:
    orientation: horizontal
  structure:
    entities:
      - { label: 창업, period: "2010", items: [사무실 1곳, 구성원 4명] }
      - { label: 첫 유료 고객, period: "2013", items: [연 매출 3억원] }
      - { label: 해외 진출, period: "2018", items: [일본, 대만 동시 출시] }
      - { label: 사용자 1천만, period: "2024", items: [누적 가입 기준] }

11. Progression.Staircase 2개 이상 8개 이하다(실측).

yaml
- type: Progression.Staircase
  params:
    direction: ascending
  structure:
    entities:
      - label: 인지
        items: [광고 노출, 검색 유입]
      - label: 체험
        items: [무료 가입, 템플릿 1건 사용]
      - label: 구독
        items: [프로 전환]
      - label: 확산
        items: [팀 초대, 사내 표준 채택]

12. Cycle.Cycle 2개 이상 8개 이하다(실측).

yaml
- type: Cycle.Cycle
  params:
    center: 품질 개선 루프
    rotation: clockwise
  structure:
    entities:
      - label: 측정
        items: [지표 수집]
      - label: 분석
        items: [원인 후보 도출]
      - label: 개선
        items: [배포]
      - label: 검증
        items: [재측정]

13. LogicFlow.FlowChart 선언한 params 가 없다. 분기 라벨이 선택 내용이다.

yaml
- type: LogicFlow.FlowChart
  structure:
    entities:
      - label: 결제 요청 수신
        items: [카드 정보 검증]
      - label: 한도 확인
        items: ["초과: 대체 수단 안내", "정상: 승인 진행"]
      - label: 승인
        items: [영수증 발행]
      - label: 대체 수단 안내
        items: [계좌 이체, 다른 카드]

14. Conversion.Funnel matrix 는 선택이고, 넣으면 단계마다 수치가 붙는다.

yaml
- type: Conversion.Funnel
  params:
    conversionRate: true
  structure:
    dataset:
      shape: matrix
      columns:
        - { label: 단계, role: category }
        - { label: 인원, role: value, unit: 명 }
      rows:
        - [방문, 48000]
        - [가입, 12400]
        - [무료 체험, 5100]
        - [유료 전환, 860]

15. TimelineSchedule.GanttChart matrix 가 필수이고 작업마다 시작과 끝이 있어야 한다.

yaml
- type: TimelineSchedule.GanttChart
  params:
    unit: month
  structure:
    dataset:
      shape: matrix
      columns:
        - { label: 작업, role: category }
        - { label: 시작, role: start }
        - { label: 종료, role: end }
      rows:
        - [요구 정리, "2026-09-01", "2026-09-20"]
        - [설계, "2026-09-15", "2026-10-31"]
        - [구현, "2026-10-20", "2027-01-15"]
        - [검증, "2027-01-05", "2027-02-10"]

16. TimelineSchedule.Calendar matrix 가 필수이고 날짜와 일정이 있어야 한다.

yaml
- type: TimelineSchedule.Calendar
  params:
    weekStart: Mon
  structure:
    dataset:
      shape: matrix
      columns:
        - { label: 일정, role: category }
        - { label: 날짜, role: date }
      rows:
        - [스프린트 시작, "2026-09-01"]
        - [중간 점검, "2026-09-10"]
        - [코드 동결, "2026-09-24"]
        - [배포, "2026-09-28"]

3. 계층과 범위#

17. Hierarchy.HierarchicalTree children 이 필수다. 깊이 상한은 없다.

yaml
- type: Hierarchy.HierarchicalTree
  params:
    orientation: vertical
  structure:
    entities:
      - label: 대표이사
        children:
          - label: 제품본부
            children:
              - { label: 디자인팀 }
              - { label: 개발팀 }
          - label: 사업본부
            children:
              - { label: 영업팀 }
              - { label: 마케팅팀 }

18. Hierarchy.Pyramid 3개 이상이다. weight 가 층 높이를 정하는 선택 내용이다.

yaml
- type: Hierarchy.Pyramid
  params:
    apex: last
  structure:
    entities:
      - { label: 생리적 욕구, weight: 5, items: [수면, 식사] }
      - { label: 안전 욕구, weight: 4, items: [주거, 고용] }
      - { label: 사회적 욕구, weight: 3, items: [소속, 우정] }
      - { label: 존경 욕구, weight: 2, items: [인정, 지위] }
      - { label: 자아실현, weight: 1, items: [성장, 창조] }

19. ScopeCoverage.NestedScope (2026-09-04 B-34 갱신) children 을 안 쓴다. entities 배열 순서 자체가 안쪽→바깥쪽이고, direction 으로 뒤집는다.

yaml
- type: ScopeCoverage.NestedScope
  params:
    direction: inward
    shape: circle
  structure:
    entities:
      - { label: 수익 시장, items: ["SOM 연 3,100억원"] }
      - { label: 유효 시장, items: [SAM 연 2.4조원] }
      - { label: 전체 시장, items: [TAM 연 12조원] }

갱신 전 예시는 시장 둘(국내·해외)을 나란히 비교하는 사슬 2개였다. 지금 설계는 사슬 하나만 담아 이 비교를 표현할 방법이 없다 — TODO.md B-34 에 후속 과제로 남겼다.

20. ReasoningStructure.Fishbone 선언한 params 가 없다. children 이 원인의 하위 갈래다.

yaml
- type: ReasoningStructure.Fishbone
  structure:
    entities:
      - label: 사람
        children:
          - { label: 신규 인력 교육 부족 }
          - { label: 검수 담당 부재 }
      - label: 공정
        children:
          - { label: 배치 규칙이 문서로만 존재 }
          - { label: 승인 단계 누락 }
      - label: 도구
        children:
          - { label: 폰트 동기화 실패 }

21. ReasoningStructure.CombinedResult 결과 라벨이 선택 내용이다.

yaml
- type: ReasoningStructure.CombinedResult
  structure:
    entities:
      - label: 자체 템플릿 자산
        items: ["8,200종"]
      - label: 생성 모델
        items: [사내 파인튜닝]
      - label: 배치 엔진
        items: [규칙 기반 검증]
      - label: 결과
        items: [입력 한 줄에서 완성 발표자료 10쪽]

22. ReasoningStructure.ProblemSolution label 이 문제, items 가 해결이다.

yaml
- type: ReasoningStructure.ProblemSolution
  structure:
    entities:
      - label: 초안 작성에 시간이 오래 걸린다
        items: ["해결: 자동 생성", "기대: 페이지당 12분에서 40초"]
      - label: 사내 표기 규칙이 지켜지지 않는다
        items: ["해결: 규칙을 템플릿에 내장", "기대: 검수 반려 60% 감소"]

4. 수치#

23. KPIKeyMetric.BigNumber value 가 필수다. dataset: measure 도 선택으로 쓸 수 있지만, 항목이 여럿이면 entity 쪽에 적는다. delta(증감)는 entity 에 적을 수 없다. capability.json 은 BigNumber 의 선택 내용에 delta 를 넣어 뒀는데 스키마의 entity 에는 그 필드가 없고 measure 에만 있다. 그래서 항목이 여럿이면 증감을 적을 자리가 없고, 증감을 적으려면 항목 하나짜리 measure 로 쓴다. 앞 부록 F절 5번이다.

yaml
- type: KPIKeyMetric.BigNumber
  structure:
    entities:
      - { label: 연 매출, value: 152, unit: 억원, emphasis: true }
      - { label: 신규 고객, value: 320, unit: 건 }
      - { label: 재구매율, value: 68, unit: "%" }

24. KPIKeyMetric.Gauge measure.value 가 필수다. 선언한 params 는 없고 선택 값은 전부 measure 안에 있다.

yaml
- type: KPIKeyMetric.Gauge
  structure:
    entities:
      - { label: 연간 목표 달성률 }
    dataset:
      shape: measure
      value: 78
      unit: "%"
      min: 0
      max: 100
      target: 90
      delta: 12
      thresholds:
        - { at: 50, label: 주의, state: warn }
        - { at: 80, label: 안정, state: safe }

25. MagnitudeRanking.BarChart matrix 가 필수다.

yaml
- type: MagnitudeRanking.BarChart
  params:
    orientation: vertical
    sort: desc
  structure:
    dataset:
      shape: matrix
      columns:
        - { label: 채널, role: category }
        - { label: 매출, role: value, unit: 억원 }
      rows:
        - [직영몰, 62]
        - [오픈마켓, 41]
        - [대리점, 28]
        - [해외, 21]

26. ProportionComposition.StackedBarChart 계열이 2개 이상이어야 한다. normalize 는 false 고정이다.

yaml
- type: ProportionComposition.StackedBarChart
  params:
    orientation: vertical
    normalize: false
  structure:
    dataset:
      shape: matrix
      columns:
        - { label: 분기, role: category }
        - { label: 구독, role: value, unit: 억원 }
        - { label: 광고, role: value, unit: 억원 }
        - { label: 기타, role: value, unit: 억원 }
      rows:
        - [1Q, 28, 9, 3]
        - [2Q, 31, 11, 4]
        - [3Q, 35, 10, 4]
        - [4Q, 40, 14, 5]

27. ProportionComposition.PieChart 조각이 2개 이상이어야 한다.

yaml
- type: ProportionComposition.PieChart
  params:
    startAngle: 0
    labelPosition: outside
  structure:
    dataset:
      shape: matrix
      columns:
        - { label: 기기, role: category }
        - { label: 비중, role: value, unit: "%" }
      rows:
        - [데스크톱, 54]
        - [모바일, 38]
        - [태블릿, 8]

28. ProportionComposition.DonutChart centerLabel 이 가운데 공간을 쓴다.

yaml
- type: ProportionComposition.DonutChart
  params:
    centerLabel: 총 12,400명
    startAngle: 90
    labelPosition: outside
  structure:
    dataset:
      shape: matrix
      columns:
        - { label: 플랜, role: category }
        - { label: 인원, role: value, unit: 명 }
      rows:
        - [무료, 8200]
        - [프로, 3100]
        - [팀, 1100]

29. ProportionComposition.TreeMap 선언한 params 가 없다. matrix 가 필수이고, 2단으로 그리려면 entities 쪽에 children 을 함께 적는다.

yaml
- type: ProportionComposition.TreeMap
  structure:
    dataset:
      shape: matrix
      columns:
        - { label: 사업부, role: category }
        - { label: 매출, role: value, unit: 억원 }
      rows:
        - [디자인, 62]
        - [프레젠테이션, 41]
        - [인쇄, 28]
        - [영상, 21]

30. ProportionComposition.PictogramChart 아이콘 하나가 나타내는 수량을 unitPerIcon 이 정한다.

yaml
- type: ProportionComposition.PictogramChart
  params:
    icon: person
    unitPerIcon: 100
  structure:
    dataset:
      shape: matrix
      columns:
        - { label: 부서, role: category }
        - { label: 인원, role: value, unit: 명 }
      rows:
        - [개발, 800]
        - [디자인, 400]
        - [영업, 300]

31. Trend.LineAreaChart 계열 1개 이상, 축 2개 이상이다. fill 이 참이면 면적으로 그린다.

yaml
- type: Trend.LineAreaChart
  params:
    fill: true
  structure:
    dataset:
      shape: matrix
      columns:
        - { label: 월, role: category }
        - { label: 방문자, role: value, unit: 명 }
      rows:
        - [1월, 42000]
        - [2월, 45800]
        - [3월, 51200]
        - [4월, 49700]
        - [5월, 58300]

32. Trend.ComboChart 계열 type 이 2종 이상이거나 axis 가 2종 이상이어야 성립한다. 이 둘은 params 가 아니라 dataset.columns[] 에 적는다.

yaml
- type: Trend.ComboChart
  structure:
    dataset:
      shape: matrix
      columns:
        - { label: 분기, role: category }
        - { label: 매출, role: value, unit: 억원, type: bar, axis: left }
        - { label: 영업이익률, role: value, unit: "%", type: line, axis: right }
      rows:
        - [1Q, 40, 12]
        - [2Q, 46, 14]
        - [3Q, 49, 13]
        - [4Q, 59, 17]

33. AttributeComparison.RadarChart 축이 3개 이상이어야 한다.

yaml
- type: AttributeComparison.RadarChart
  params:
    maxValue: 100
  structure:
    dataset:
      shape: matrix
      columns:
        - { label: 평가 축, role: category }
        - { label: 자사, role: value }
        - { label: 경쟁사, role: value }
      rows:
        - [속도, 82, 64]
        - [정확도, 76, 81]
        - [사용성, 91, 70]
        - [가격, 68, 88]
        - [지원, 74, 59]

34. RelationalComparison.BubbleChart 항목마다 x, y, 크기가 있어야 한다.

yaml
- type: RelationalComparison.BubbleChart
  params:
    sizeScale: linear
  structure:
    dataset:
      shape: matrix
      columns:
        - { label: 시장, role: category }
        - { label: 성장률, role: x, unit: "%" }
        - { label: 수익률, role: y, unit: "%" }
        - { label: 규모, role: size, unit: 억원 }
      rows:
        - [국내, 12, 21, 620]
        - [일본, 24, 14, 310]
        - [동남아, 38, 6, 140]

5. 텍스트와 사람#

35. TextBasedCommunication.HighlightedMessage 정확히 1개다. items 가 선택이다.

yaml
- type: TextBasedCommunication.HighlightedMessage
  structure:
    entities:
      - label: 초안 작성 시간을 페이지당 12분에서 40초로 줄인다
        items: [2026년 3분기 사내 실측 기준]

36. TextBasedCommunication.LongFormTextLayout title 이 유일한 params 다.

yaml
- type: TextBasedCommunication.LongFormTextLayout
  params:
    title: 우리가 이 문제를 푸는 이유
  structure:
    entities:
      - label: 발표자료 제작은 여전히 손으로 하는 일이다
        items:
          - 사내 설문에서 응답자 412명 중 68%가 초안 배치에 가장 오래 걸린다고 답했다.
          - 같은 응답자의 74%는 사내 표기 규칙을 매번 다시 확인한다고 답했다.
          - 규칙을 템플릿에 내장하면 이 두 시간이 함께 줄어든다.

37. QnACard.QnACard label 이 질문, items 가 답변이다.

yaml
- type: QnACard.QnACard
  structure:
    entities:
      - label: 무료 플랜에서도 PDF 로 내보낼 수 있나
        items: [워터마크가 붙은 상태로 가능하다]
      - label: 팀 플랜은 몇 명부터 쓸 수 있나
        items: [3명부터이고 좌석 단위로 늘린다]

38. Utterance.DialogueChat 2개 이상이다. speaker 가 선택 내용이고, 없으면 번갈아 배치한다.

yaml
- type: Utterance.DialogueChat
  structure:
    entities:
      - { label: 이번 분기 자료 초안 언제 되나요, speaker: 팀장 }
      - { label: 오늘 오후에 자동 생성으로 1차 뽑아 두겠습니다, speaker: 담당자 }
      - { label: 표기 규칙은 반영되나요, speaker: 팀장 }
      - { label: 템플릿에 들어 있어서 자동으로 맞춰집니다, speaker: 담당자 }

39. Utterance.QuoteTestimonial author 와 source 가 선택 내용이다.

yaml
- type: Utterance.QuoteTestimonial
  structure:
    entities:
      - label: 예전에는 반나절 걸리던 월간 보고서를 이제 회의 직전에 만든다
        author: 김OO
        source: 제조업 기획팀, 2026-07 인터뷰
      - label: 표기 규칙을 외우지 않아도 되는 것이 가장 크다
        author: 이OO
        source: 금융업 마케팅팀, 2026-07 인터뷰

40. PortraitProfile.ProfileCard avatar 와 organization 이 선택 내용이다.

yaml
- type: PortraitProfile.ProfileCard
  structure:
    entities:
      - label: 정원석
        items: [Visual Summary 규약, 검색 대치]
        avatar: https://cdn.example.com/avatar/wsjeong.png
        organization: MORDOR 스쿼드
      - label: 황현진
        items: [의미 추출 모듈]
        avatar: https://cdn.example.com/avatar/hjhwang.png
        organization: MORDOR 스쿼드

41. PortraitProfile.ResumeCV period 와 children 이 선택 내용이다.

yaml
- type: PortraitProfile.ResumeCV
  structure:
    entities:
      - label: 경력
        children:
          - { label: 미리디 제품개발, period: "2021-2026", items: [발표자료 자동 생성] }
          - { label: 이전 직장 플랫폼팀, period: "2017-2021", items: [검색 색인 운영] }
      - label: 학력
        children:
          - { label: OO대학교 컴퓨터공학, period: "2013-2017" }

6. 페이지 역할#

네 종 모두 선언한 params 가 없고 dataset 도 쓰지 않는다. 규약이 entities 나 dataset 중 하나를 요구하므로 entities 로 적는다. 성립 조건은 2026-08-27 에 채워졌다.

아래 넷은 2026-08-29 부터 스키마 검증을 통과한다. 어휘 편입은 2026-08-06 에 정해졌는데 규약 semanticCategory enum 만 안 따라오고 있었고(앞 부록 F절 2번), 0.5.3 에서 결합값 45종으로 맞추며 닫혔다.

42. PageNavigation.Opening 정확히 1개이고, 제목과 함께 발표자, 날짜, 소속 중 하나 이상이 온다.

yaml
- type: PageNavigation.Opening
  structure:
    entities:
      - label: 2026년 하반기 제품 로드맵
        items: [부제 - 생성에서 배치까지, 정원석, "2026-09-01", MORDOR 스쿼드]

43. PageNavigation.TableOfContents 2개 이상이다. 흐름이나 단계 표현은 쓰지 않는다.

yaml
- type: PageNavigation.TableOfContents
  structure:
    entities:
      - { label: "01 시장 현황", items: [경쟁 구도와 수요 변화] }
      - { label: "02 제품 전략", items: [올해 집중할 셋] }
      - { label: "03 실행 계획", items: [분기별 마일스톤] }
      - { label: "04 예산", items: [항목별 배분] }

44. PageNavigation.SectionDivider 정확히 1개이고 본문 콘텐츠가 없다.

yaml
- type: PageNavigation.SectionDivider
  structure:
    entities:
      - label: "02 제품 전략"
        items: [올해 집중할 셋]

45. PageNavigation.Ending 정확히 1개이고 종료를 알리는 단일 메시지만 담는다.

yaml
- type: PageNavigation.Ending
  structure:
    entities:
      - label: 감사합니다

7. 병치와 포함: 이종중첩 컴포넌트#

이종중첩 컴포넌트(heterogeneous nested component)는 하나의 컴포넌트 경계 안에 서로 다른 semantic category 가 함께 있는 경우다. 위상 관계(topological relation)로 셋을 구분하고, 그중 둘만 실제로 나타난다.

위상 정의 실측
포함 (Containment) 한 카테고리의 시각 영역이 다른 카테고리의 영역 안에 완전히 들어간다 예: List 항목마다 안에 BigNumber 가 들어간 모양
병치 (Juxtaposition) 서로 다른 카테고리의 영역이 겹치지 않고 나란히 놓인다 2026-08-13 표본에서 관측된 11건 전부가 이 경우였다
중첩 (Overlap) 영역이 부분적으로 겹친다 관측되지 않는다. RLSC 트리에서 한 노드는 부모 하나에만 속하므로 구조적으로 생기지 않는다

규모는 실측돼 있다. 전체 183,052개 컴포넌트에서 1,000개를 뽑아 판정한 결과 이종중첩은 11건(1.10%, 95% 신뢰구간 0.62%부터 1.96%, 전체 환산 약 2,014개)이었다. 조합은 BigNumber + List 4건, HighlightedMessage + List 2건, 나머지 다섯 조합이 각 1건이었다.

아래 예시는 전부 지금 생성되지 않는 값이다. 스키마는 entity.components[] 를 v0.4 때부터 열어 뒀지만 1단계 생성기는 이 필드를 채우지 않는다. 표현 가능성만 열어 두고 켜는 시점은 생성기 정책이 정한다.

아래 그림 셋을 읽는 법. 굵은 검정 테두리가 컴포넌트 경계 하나다. 파란 테두리는 그 안에서 한 카테고리가 차지하는 영역, 주황 테두리는 그와 겹치지 않고 놓인 다른 카테고리의 영역, 회색 테두리는 개별 요소다. 그림 2와 그림 3은 실측 컴포넌트의 썸네일 위에 덧그렸고, 번호는 바로 아래 표의 structure 자리와 대응한다.

7.1 포함 위상#

부모 컴포넌트의 항목 하나하나가 자기 안에 다른 카테고리를 품는다. entity.components[] 는 항목 하나에 정확히 1개만 매단다. entity 는 자기 label 과 items 를 그대로 갖고, 그 밑에 하위 컴포넌트를 하나 얹는다.

yaml
version: "0.5.4"
title: 분기별 사업 현황
components:
  - type: Enumeration.List
    structure:
      entities:
        - label: 1분기
          items: [신규 계약 12건]
          components:
            - type: KPIKeyMetric.BigNumber
              structure:
                entities:
                  - { label: 매출, value: 12, unit: 억원 }
        - label: 2분기
          items: [신규 계약 15건]
          components:
            - type: KPIKeyMetric.BigNumber
              structure:
                entities:
                  - { label: 매출, value: 15, unit: 억원 }

[그림: 포함 위상의 경계와 structure 대응] ↗

그림 1. 포함 위상. 위 YAML 을 화면 영역으로 옮긴 것이다. 바깥 경계 하나가 Enumeration.List 이고, 항목 영역 둘이 각각 structure.entities[0] 과 [1] 이다. 항목마다 제목과 본문이 label 과 items[0] 으로 들어가고, 그 안쪽에 파란 경계로 KPIKeyMetric.BigNumber 가 하나씩 들어앉아 entity.components[0] 자리를 차지한다. 이 모양은 2026-08-13 표본 11건에는 없었다. 11건이 전부 병치였기 때문이다. 그림은 규약 8.7절이 정본으로 든 예시를 옮긴 것이다.

중첩이 매달리는 자리는 entity 전체이지 entity 안의 값 필드가 아니다. label 은 항상 문자열이고 items 는 항상 문자열 배열이라서, 그 값 자리에 컴포넌트를 직접 넣으면 스키마가 거부한다. 유일한 중첩 통로는 components 라는 별도 배열 필드다.

포함이 실패했을 때 갈래가 둘이다. 이 구조를 그대로 담은 후보를 색인에서 못 찾으면 평탄화하거나 떼어낸다.

평탄화(구조 강등). 규약 1절 "중첩은 평탄화된다"가 정한 유일한 확정 기법이다. 자식의 entities 를 부모의 items 로 편다.

yaml
version: "0.5.4"
title: 분기별 사업 현황
components:
  - type: Enumeration.List
    structure:
      entities:
        - { label: 1분기, items: [신규 계약 12건, 매출 12억원] }
        - { label: 2분기, items: [신규 계약 15건, 매출 15억원] }

매출 12억원 이 큰 숫자로 강조되던 BigNumber 의 정체성을 잃고 신규 계약 12건 과 같은 글자 한 줄이 됐다. 구조는 안 잃지만 표현을 잃는 강등이다.

떼어내기(승격). 자식을 부모에서 분리해 최상위 컴포넌트로 올린다. 검색 쪽 재료(T-5 노드 태그 검색)는 이미 있고 오케스트레이션 코드가 아직 없다.

yaml
version: "0.5.4"
title: 분기별 사업 현황
components:
  - type: Enumeration.List
    structure:
      entities:
        - { label: 1분기, items: [신규 계약 12건] }
        - { label: 2분기, items: [신규 계약 15건] }
  - type: KPIKeyMetric.BigNumber
    structure:
      entities:
        - { label: 1분기 매출, value: 12, unit: 억원 }
        - { label: 2분기 매출, value: 15, unit: 억원 }

BigNumber 의 강조 표현은 살지만 "이 매출이 1분기 항목 바로 옆에 붙어 있다"는 공간적 연결이 사라진다. 둘의 순서는 2026-08-28에 정해졌다. 둘 다 텍스트를 하나도 버리지 않는데 떼어내기가 카테고리 표현을 더 남기므로 떼어내기를 먼저 시도하고 평탄화는 마지막에만 한다(7.5절).

7.2 병치 위상#

병치에는 자연스러운 모양이 둘이다. 새 필드나 wrapper 는 필요 없다.

[그림: 병치 위상을 실측 컴포넌트 위에 표시한 것] ↗

그림 2. 병치 위상. 2026-08-13 표본에서 실제로 걸린 컴포넌트 8244468(레이아웃 626391)의 썸네일 위에 경계를 덧그린 것이다. 검은 테두리가 컴포넌트 경계 하나이고, 그 안에서 파란 테두리(Enumeration.List)와 주황 테두리(RelationalComparison.VennDiagram)가 겹치지 않고 위아래로 놓여 있다. 이것이 병치다.

# 화면 요소 들어가는 자리
1 제목 띠 "스쿼드 간 업무 중복" 적을 자리가 없다. title 은 page 수준에만 있고 컴포넌트 수준 제목 필드가 규약에 없다
2 소제목 "신제품 기획 단계 중복 수행" components[0].structure.entities[0].label
3 불릿 2줄 components[0].structure.entities[0].items[]
4 소제목 "출시 준비 프로세스 이중 진행" components[0].structure.entities[1].label
5 불릿 2줄 components[0].structure.entities[1].items[]
6 왼쪽 타원 "A팀 업무 40%" components[1].structure.entities[0].label
7 가운데 겹친 영역 "중복 업무 30%" components[1].structure.relations[0].desc
8 오른쪽 타원 "B팀 업무 30%" components[1].structure.entities[1].label

7번이 관계가 왜 필요한지를 보여준다. 겹친 영역의 문구는 A팀에도 B팀에도 속하지 않는다. 그래서 entity 가 아니라 relations[0].desc 로 간다. 이 컴포넌트는 경계 하나에 카테고리가 둘이므로 색인 태깅에서 뿌리가 Unclassified 가 되고, D-7 에 따라 검색 풀에서 빠진다.

1번은 규약의 빈자리다. 컴포넌트마다 제목 띠가 붙는 디자인이 흔한데 Component 에는 제목 필드가 없다. 지금은 첫 entity 의 label 로 접거나 버리는 수밖에 없고, 어느 쪽인지 규약이 정한 바 없다.

뿌리가 없는 병치는 처음부터 다중 컴포넌트다. 서로 다른 카테고리가 다른 무엇에도 속하지 않고 나란히 있으면 최상위 components[] 를 늘리면 된다. 생성기가 이미 쓰는 모양이다.

yaml
version: "0.5.4"
title: 이번 분기 하이라이트
components:
  - type: KPIKeyMetric.BigNumber
    structure:
      entities:
        - { label: 총매출, value: 152, unit: 억원 }
  - type: Enumeration.List
    structure:
      entities:
        - { label: 신규 고객 320건 }
        - { label: 재구매율 68% }
        - { label: 해외 매출 비중 22% }

뿌리가 있는 병치는 항목마다 컴포넌트를 하나씩 매단다. 부모의 entities[] 를 늘려서 항목마다 자기 컴포넌트를 하나씩 갖게 한다. entity 하나에 서로 다른 카테고리 둘을 몰아넣는 전례는 없다.

yaml
version: "0.5.4"
title: 분기별 핵심 지표
components:
  - type: Enumeration.List
    structure:
      entities:
        - label: 총매출
          components:
            - type: KPIKeyMetric.BigNumber
              structure:
                entities:
                  - { label: 총매출, value: 152, unit: 억원 }
        - label: 핵심 성과
          components:
            - type: Enumeration.List
              structure:
                entities:
                  - { label: 신규 고객 320건 }
                  - { label: 재구매율 68% }
                  - { label: 해외 매출 비중 22% }

뿌리가 있는 병치가 실패하면 조각마다 따로 검색해 최상위로 편다. 부모가 두 항목 말고 자기 고유 항목이 없으면 부모 자체를 버린다. 결과는 뿌리 없는 병치와 똑같은 모양이 된다. 계기만 다르다. 뿌리 없는 병치는 처음부터 이 모양이 정답이었고, 뿌리가 있던 병치는 묶어서 찾다 실패해 이 모양으로 물러난 것이다.

7.3 둘이 함께 있는 경우#

[그림: 병치와 포함이 함께 있는 경계를 실측 컴포넌트 위에 표시한 것] ↗

그림 3. 병치와 포함이 한 경계 안에 함께 있는 모양. 표본에서 걸린 컴포넌트 8183278(레이아웃 731419)이다. 판정 근거는 "A/B/C Group 별로 30%, 40%, 30% 수치가 큰 폰트로 강조되고, 그 아래에는 연령대, 성별, 특징, 비중 항목이 나열된 상세정보 박스가 별도로 있다" 였고, 태그는 BigNumber 와 List 둘이 붙었다.

검은 테두리가 컴포넌트 경계이고, 파란 테두리 셋이 A, B, C 그룹 영역이다. 그룹 영역 안에 주황 테두리 둘이 들어 있다. 아래는 A 그룹만 번호를 붙였고 B, C 는 같은 모양이 반복된다.

# 화면 요소 들어가는 자리
1 그룹 제목 "[A Group 프로모션 전략]" structure.entities[0].label
2 대형 수치 "30%" entities[0].components[] 의 KPIKeyMetric.BigNumber
3 설명 문단 4줄 structure.entities[0].items[]
4 상세정보 박스 (연령대, 성별, 특징, 비중) entities[0].components[] 의 Enumeration.ListingTable

두 위상이 층을 달리해 함께 있다. 그룹 영역이 대형 수치와 상세정보 박스를 자기 안에 담고 있으므로 그 둘은 그룹에 대해 포함이다. 그리고 대형 수치와 상세정보 박스는 서로 겹치지 않고 위아래로 놓여 있으므로 둘 사이는 병치다. 위상은 부모자식 한 쌍마다 따로 정해지는 것이라 한 컴포넌트 안에서 이렇게 섞인다.

이 컴포넌트는 지금 표기로 깔끔하게 적히지 않는다. entity 하나(A 그룹)가 자기 안에 서로 다른 카테고리 둘을 담고 있는데, 규약 8.7절의 전례는 entity 하나에 컴포넌트를 정확히 1개 매다는 모양뿐이다. 스키마의 entity.components[] 는 배열이라 둘을 넣어도 검증은 통과하지만, 전례가 없어서 검색이 이것을 어떻게 다룰지 정해진 바가 없다. 아래는 검증만 통과시킨 모양이고, 표기로 확정된 것이 아니다.

yaml
version: "0.5.4"
title: 그룹별 프로모션 전략
components:
  - type: Enumeration.List
    structure:
      entities:
        - label: "[A Group 프로모션 전략]"
          items:
            - 타깃의 소비 패턴에 따른 프로모션의 전략을 세우세요.
            - 타깃이 관심을 가질 수 있는 이벤트를 기획해보세요.
          components:
            - type: KPIKeyMetric.BigNumber
              structure:
                entities:
                  - { label: 비중, value: 30, unit: "%" }
            - type: Enumeration.ListingTable
              params:
                header: false
              structure:
                dataset:
                  shape: matrix
                  columns:
                    - { label: 항목, role: category }
                    - { label: 내용, role: value }
                  rows:
                    - [연령대, 30세 ~ 39세]
                    - [성별, 남성]
                    - [특징, 타깃이 가진 고유한 특징을 입력하세요.]
                    - [비중, "30%"]
        # B Group, C Group 도 같은 모양이 반복된다

대치가 실패하면 층마다 다른 갈래를 탄다. 그룹 안의 포함은 7.1절대로 평탄화하거나 떼어내고, 떼어낸 조각 둘 사이의 병치는 7.2절대로 각자 따로 검색해 대치한다. 두 갈래가 한 컴포넌트 안에서 함께 필요하다는 것이 이 경우의 특징이고, 그 오케스트레이션이 아직 없다는 것이 7.4절 셋째 줄이다.

7.5 복합 구조 fallback 을 한 단계씩 따라간다#

앞의 7.1 부터 7.3 은 모양을 보여줬고, 이 절은 그 모양이 색인에서 안 걸릴 때 무엇이 순서대로 벗겨지는지를 끝까지 따라간다. 순서의 SSoT 는 spec/capability-table.md 3.6절이고 2절의 '복합 구조 fallback' 절이 규칙 전량을 요약한다. 이 질의는 실측이 아니라 규약이 표현할 수 있는 모양으로 만든 것이다. 1단계 생성기가 entity.components[] 를 아직 채우지 않으므로 지금 나올 수 있는 값이 아니다. 후보의 유무도 실측이 아니라 D-7 정책에서 따라 나오는 것이다.

출발 질의. 최상위에 병치가 있고(A 와 B), A 의 항목마다 안에 포함이 있고, 그 포함이 서로 다른 카테고리 둘을 담는다. 7.3절 실측 사례(컴포넌트 8183278)와 같은 모양이다.

yaml
version: "0.5.4"
title: 2026 상반기 사업 리뷰
components:
  # A. 분기 4개. 항목마다 대형 수치와 서술 요약이 함께 들어간다
  - type: Enumeration.List
    structure:
      entities:
        - label: 1분기
          items: [신규 계약 12건]
          components:
            - type: KPIKeyMetric.BigNumber
              structure:
                entities:
                  - { label: 매출, value: 12, unit: 억원 }
            - type: TextBasedCommunication.LongFormTextLayout
              structure:
                entities:
                  - label: 요약
                    items:
                      - 신규 시장 세 곳에 진입해 계약 12건을 확보했다.
                      - 단가 인하 없이 확보한 계약이라 마진이 유지됐다.
        - label: 2분기
          items: [신규 계약 15건]
          components:
            - type: KPIKeyMetric.BigNumber
              structure:
                entities:
                  - { label: 매출, value: 15, unit: 억원 }
            - type: TextBasedCommunication.LongFormTextLayout
              structure:
                entities:
                  - label: 요약
                    items:
                      - 기존 고객 재계약이 늘어 계약 건수가 증가했다.
        # 3분기, 4분기도 같은 모양이 반복된다
  # B. 하반기 실행 계획 3단계
  - type: ProcessFlow.Steps
    structure:
      entities:
        - { label: 채널 정비 }
        - { label: 가격 개편 }
        - { label: 해외 확장 }

노드는 이렇게 센다. 최상위 2개, A 의 항목마다의 묶음 4개, 그 묶음 안의 단일 카테고리 8개다. 항목마다의 묶음에는 붙일 카테고리 이름이 없다. 항목 하나가 서로 다른 카테고리 둘을 담고 있어서다. 색인 태깅 어휘에는 이 자리를 가리키는 Unclassified 가 있지만 질의 type enum 에는 대응 값이 없고 검증이 거부한다. 그래서 N-R3 이 이 자리를 카테고리로 검색하지 않고 부모의 슬롯 경계로만 쓰기로 정했다.

최상위 둘은 서로 독립으로 진행한다. B 는 ProcessFlow.Steps 단일 카테고리이고 성립 조건이 entities 2개 이상이라 N1 에서 바로 후보가 걸린다. 아래는 A 만 따라간다.

단계 무엇을 시도했나 결과 근거
N1 A 의 트리 전체를 그대로 담은 후보 후보 0 뿌리가 Unclassified 인 템플릿이 D-7 로 검색 풀에서 빠져 있다
N2 모양을 고정한 채 노드마다 카테고리를 내린다. N-R6 순서로 합 1인 조합 셋(BigNumber → Gauge, LongFormTextLayout → List, List → ListingTable), 그다음 합 2 이상 후보 0 모양이 이종중첩인 채로 남으므로 어느 조합도 같은 이유로 후보가 없다
N3 A 의 뿌리 Enumeration.List 를 벗겨 항목 4개를 최상위로 올린다. N-R2 로 [4], [3,1], [2,2], [2,1,1], [1,1,1,1] 후보 0 조각 하나하나가 여전히 BigNumber 와 LongFormTextLayout 의 이종 묶음이다
N4 조각마다 Unclassified 경계를 벗겨 단일 카테고리 8개로 만든다. N-R2 로 [8] 부터 줄여 간다 [1,1,1,1,1,1,1,1] 에서 후보가 걸린다 아래
N5 여기까지 전부 실패했을 때만 평탄화한다 반드시 성립 마지막 fallback 셋

N4 에서 큰 묶음이 왜 전부 실패하나. 벗긴 뒤 형제 순서는 BigNumber, LongFormTextLayout, BigNumber, LongFormTextLayout 이 반복되는 모양이다. N-R2 는 연속한 형제만 묶고 순서를 바꾸지 않으므로, 크기 2 이상의 묶음은 어떻게 잘라도 이종 묶음이 된다. 그래서 크기 1까지 내려간다.

순서를 바꿔 같은 카테고리끼리 모으는 것은 허용하지 않는다. BigNumber 4개를 한 컴포넌트로, LongFormTextLayout 4개를 다른 컴포넌트로 모으면 각각 동종이라 N4 가 [4,4] 에서 끝난다. 그런데 읽는 순서가 1분기 매출, 1분기 요약, 2분기 매출 순에서 매출 넷, 요약 넷 순으로 바뀌고, 그러면 어느 요약이 어느 분기의 것인지가 배치로 드러나지 않는다. 이 제약을 풀어야 한다는 실측이 나오면 N-R2 를 고친다.

N4 통과 시점의 결과. 최상위 컴포넌트가 9개가 된다. A 에서 나온 8개와 B 1개다. 아래는 1분기 조각 둘만 적었다.

yaml
version: "0.5.4"
title: 2026 상반기 사업 리뷰
components:
  - type: KPIKeyMetric.BigNumber
    structure:
      entities:
        - { label: 1분기 매출, value: 12, unit: 억원 }
  - type: TextBasedCommunication.LongFormTextLayout
    structure:
      entities:
        - label: 1분기 요약
          items:
            - 신규 시장 세 곳에 진입해 계약 12건을 확보했다.
            - 단가 인하 없이 확보한 계약이라 마진이 유지됐다.
            - 신규 계약 12건
  # 2분기, 3분기, 4분기 조각 여섯이 같은 모양으로 이어진다
  - type: ProcessFlow.Steps
    structure:
      entities:
        - { label: 채널 정비 }
        - { label: 가격 개편 }
        - { label: 해외 확장 }

N-R1 이 벗긴 부모의 내용을 어디로 보냈는지 보라. 뿌리 Enumeration.List 가 갖고 있던 항목 label("1분기")과 items("신규 계약 12건")는 버려지지 않았다. label 은 승격된 조각의 label 앞에 붙어 "1분기 매출", "1분기 요약"이 됐고, items 는 승격된 첫 조각의 첫 entity items 맨 뒤로 갔다.

N5 까지 갔을 때의 결과. 비교를 위해 적어 둔다.

yaml
version: "0.5.4"
title: 2026 상반기 사업 리뷰
components:
  - type: Enumeration.List
    structure:
      entities:
        - label: 1분기
          items:
            - 신규 계약 12건
            - "매출: 12억원"
            - 신규 시장 세 곳에 진입해 계약 12건을 확보했다.
            - 단가 인하 없이 확보한 계약이라 마진이 유지됐다.
        - label: 2분기
          items:
            - 신규 계약 15건
            - "매출: 15억원"
            - 기존 고객 재계약이 늘어 계약 건수가 증가했다.
  - type: ProcessFlow.Steps
    structure:
      entities:
        - { label: 채널 정비 }
        - { label: 가격 개편 }
        - { label: 해외 확장 }

두 결과를 나란히 놓고 보면 무엇을 걸고 무엇을 얻는지가 드러난다.

N4 통과 (떼어내기) N5 (평탄화)
텍스트 전량 보존 전량 보존
BigNumber 의 대형 수치 강조 살아 있다 사라진다. 글머리표 한 줄이 된다
분기와 매출, 요약의 공간적 짝 사라진다 살아 있다
분기 넷을 하나의 나열로 읽게 하던 배치 사라진다 살아 있다
최상위 컴포넌트 수 9개 2개

그래서 N4 가 N5 보다 앞이다. 둘 다 텍스트를 안 버리는데 N4 쪽이 카테고리 표현을 더 남긴다. 표현을 더 많이 남기는 쪽을 먼저 두는 것이 확정된 순서다. 다만 N4 가 언제나 더 나은 결과라는 뜻은 아니다. 위 표에서 보듯 공간적 짝이 내용의 핵심인 질의에서는 N5 가 나을 수 있고, 그 판정 기준은 아직 없다. N-R5 가 계층 대분류에 대해서만 그 예외를 제안하고 있고 미확정이다(B-31).

이 예시가 드러낸 것 셋. 첫째, D-7 이 유지되는 동안 이종 질의는 실질적으로 N3 에서 시작한다. N1 과 N2 가 둘 다 이종중첩인 모양 그대로 후보를 찾기 때문이다. 둘째, 이종 질의에서 N3 한 번으로는 끝나지 않는 경우가 흔하다. 뿌리 아래에 Unclassified 경계가 한 층 더 있으면 N4 가 필요하고, 7.3절 실측 사례가 그 모양이다. 셋째, 반복하면 최상위 컴포넌트 수가 늘어난다. 1개가 9개가 됐고, 규약 8.6절이 최상위 원소 수에 상한을 두지 않았으므로 규약 위반은 아니지만 한 페이지에 9개를 배치할 수 있는지는 확인한 바 없다(B-32).

7.4 아직 정해지지 않은 것#
무엇 상태
질의 쪽 포함 위상을 단일 type 으로 평탄화한 뒤 fallback 할지, entity.components[] 를 유지한 채 노드 태그로 대치 범위를 자를지 닫혔다 (2026-08-28, I-12). 유지한다. 구조를 그대로 두고 노드마다 카테고리를 내려 보고(N2), 실패하면 최상위 부모부터 벗기고(N3, N4), 평탄화(N5)는 마지막에만 한다. 순서는 7.5절과 2절의 '복합 구조 fallback' 절이다
색인 태깅 어휘에는 이종 병치 뿌리를 가리키는 Unclassified 가 있는데, 질의 스키마 type enum(결합값 45종)에는 대응 값이 없다 열려 있다. type: Unclassified 는 지금 검증에서 거부된다
병치를 쪼개야 한다고 인식하고, 조각마다 검색을 호출하고, 결과를 다시 조립하는 코드 없다. 검색(T-5)만 있고 메인 파이프라인에도 아직 안 얹혔다. 무엇을 어떤 순서로 호출해야 하는지는 2026-08-28에 정해졌다(I-11)
entity 하나가 서로 다른 카테고리 둘 이상을 담는 표기 스키마는 통과하지만 전례가 없다. 규약 8.7절은 entity 하나에 정확히 1개를 매다는 모양만 적어 뒀다(7.3절 실측 사례가 이 경우다)
컴포넌트 수준 제목 필드 없다. title 은 page 수준에만 있다(7.2절 1번)
이종중첩으로 태깅된 색인 템플릿 검색 풀에서 뺐다(D-7, 2026-08-11). 뿌리가 Unclassified 인 템플릿은 어느 질의에도 후보로 안 걸린다
이종중첩 템플릿을 검색 풀에 되돌릴지 열려 있다(D-8, 2026-08-28 신설). D-7 이 유지되는 동안 복합 구조 순서의 N1 과 N2 는 이종 질의에서 언제나 후보가 0이다
떼어내기(N3)와 평탄화(N5) 중 어느 쪽이 내용을 더 잘 보존하는지 닫혔다 (2026-08-28). 둘 다 텍스트를 안 버리지만 떼어내기가 카테고리 표현을 더 남기므로 떼어내기가 먼저다

↑ 목차로 · 7부 머리로

8부

발표 — slides/

스쿼드 공유용 Marp 덱. 규약을 처음 설명한 자리의 기록이다.

55장2026-08-04 스쿼드 공유 덱 24장 파생파생물

원문 slides/2026-08-04-visual-summary.md GitHub 에서 보기 17 KB


Visual Summary 규약 v0.5#

내용시각화 Summary 고도화


2026-08-04 MORDOR 체크인 직후 세션

정원석 (MORDOR)

이 덱은 파생물이다. SSoT 는 mordor-wiki / projects/mordor/visual-summary/ 다.
할 일은 TODO.md, 규약은 spec/schema-v0.5.md 가 갖는다.


이 세션이 하려는 것#

여기서 할 것

규약은 2026-08-02 에 확정됐다.

규약이 닿지 않는 구간을 각자 소관에서 채운다.

  • 색인 쪽에 무엇이 필요한가
  • 추출 쪽 경로가 있나
  • 언제 누가 하나

여기서 안 할 것

확정된 결정을 처음부터 되묻기.

닫힌 것과 열린 것은 슬라이드에 표시했다.

닫힌 것은 TODO.md G절, 열린 것은 B절이 SSoT 다.

1분 요약 사용자 입력을 컴포넌트로 바꿀 때 구조가 뭉개지는 것이 지금 품질 문제의 핵심이다. Summary 를 Markdown 에서 구조화 형식으로 바꿔 슬롯을 보존하고, 컴포넌트 쪽에는 의미 태그를 붙여 대조한다.


먼저 오해 하나를 지운다#

'사용자 입력이 RLSC 로 변환된다' 아니다. 경로가 둘이고 별개로 진행된다.

질의 쪽 (query side)

사용자 입력을 검색 조건으로 바꾼다

Summary, structure

색인 쪽 (index side)

이미 있는 템플릿 컴포넌트를 미리 검색 가능한 형태로 뽑아 둔다

RLSC, Search Signature v2

질의 쪽 조건
→
BE 검색 모듈에서 합류
←
색인 쪽 파생물

structure 에서 RLSC 로 가는 화살표는 변환이 아니라 capability 대조다. 그 규약이 capability-table.md 다.


왜 하나: BAD 의 85% 가 매칭 문제다#

렌더링 문제가 아니다

2026-07-26 BAD 원인 분석

judge 채점 16,819행 집계 + BAD 200건 직접 분류

→ 85% 가 컴포넌트와 내용이 안 맞는 문제

세부 원인 넷

  • 텍스트 용량 초과
  • 매핑과 위계 오류
  • 슬롯 수 불일치
  • 대치 실패

검색이 컴포넌트를 잘 골라도 그 다음이 정의돼 있지 않아 결과가 나빠지고 있다.

2026-08-03 방향 1.5개월 이내에 구조 불일치를 해결한다. 체감 BAD 50% → 20% 이하가 기대치다.


무엇이 뭉개지나: 같은 입력, 두 결과#

② Markdown Summary (현행)

markdown
# 2025 사업 성과
* 총매출 152억
* 온라인 매출 22억

값과 단위, 계열이 문자열로만 남는다.

'152억'이 숫자인지 텍스트인지 구분이 없다.

②' Visual Summary (YAML)

yaml
components:
  - type: KPIKeyMetric.BigNumber
    structure:
      entities:
        - { label: 총매출,
            value: 152, unit: 억원 }

슬롯이 그대로 남는다.

① 사용자 입력
→
② Summary
→
③ 검색 조건
→
④ 후보 컴포넌트
→
⑤ 대치

로드맵: 단계마다 넷이 바뀐다#

단계 Summary 검색 태그 범위 태그가 가리키는 것
1 현행 Markdown 임베딩 유사도 안 단다 -
2 목표 2026-08-18 Markdown semantic tag 컴포넌트 하나 이 컴포넌트 어딘가에 그 타입이 있다
3 YAML tag + structure 컴포넌트 하나 2단계와 같음
4 YAML tag + node 태그 컴포넌트 안 node 마다 어느 node 인지 특정된다
5 미정 YAML 4단계와 같음 node 마다 4단계 + 카테고리별 property

3단계는 실행하지 않는다

3단계에서 만들 대치 로직이 4단계에서 버려질 수 있다. 정보가 확실한 4단계로 바로 간다 (7/24 회의)

배포 일정은 다시 봐야 한다

2026-08-03 에 제품 배포가 연기됐다. 2단계 마감 8/18 이 그 연기에 포함되는지 티켓 반영이 없다


왜 단계를 나누나: 태그의 모호성#

컴포넌트 하나에 태그를 다는 한, 두 경우를 구분할 수 없다.

경우 A

root 자체가 그 타입이라 붙었다

경우 B

하위 어딘가에 그 타입 node 가 있어서 컴포넌트 전체가 받았다

실데이터가 B 의 존재를 증명한다. design_object_id 4757227 에 ComparisonTable, HierarchicalTree, List, HighlightedMessage 넷이 함께 붙어 있다. root 가 넷을 동시에 가질 수는 없다.

복수 태그는 83,771건 중 260건(0.3%) 이고 최대 4개까지 관측된다.

4단계에서 node 단위로 태그를 달면 해소된다. 그 일을 I.I.S. 가 맡는다.


설계의 실증 근거: 검색 풀이 리스트에 쏠려 있다#

색인 쪽 (붙어 있는 태그)

모수 83,771 design object 2026-07-22 적재

카테고리 비율
List 81.1%
BigNumber 3.6%
Steps 2.4%
ListingTable 0.5%

상위 8종이 94.5%

질의 쪽 (사용자가 원하는 것)

카테고리 비율
List 31.96%
ListingTable 15.08%
Steps 10.92%
ComparisonTable 7.24%

수요는 다양한데 풀이 쏠려 있다

이 분포가 설계를 정했다. VennDiagram, Tree, NestedScope, Pyramid 는 각 164건 이하, 데이터 차트는 각 51건 이하다. 성립 조건을 엄격하게 걸면 후보가 0이 되어 전부 List 로 강등된다.


v0.5 가 정한 것 (2026-08-02 확정)#

항목 결정
최상위 형태 항상 components[]. 하나면 길이 1 배열
type 표기 문자열 하나로 대분류.중분류. v7 단어장 49종 중 41종
confidence 제거. 생성기가 채우지 않는다
alternatives[] 신설. fallback 순서보다 먼저 본다
entity.id 생성물에서 뺀다. 파싱 뒤 배열 순서로 매긴다
dataset measure 와 matrix 두 모양. 표 계열도 matrix
relation.type intersects 와 compares 둘
생성 방식 LLM 이 YAML 을 직접 쓴다. JSON 변환 후 스키마 검증
원소 수 상한 두지 않는다. 원소 간 관계는 자리만 예약

검증 실측 스키마 Draft 2020-12 유효성 통과, 본문 예시 18종 전량 통과, 부정 사례 12종 의도대로 거부


type: 단어장 49종 중 41종#

v7 단어장 49종
−
페이지 역할 4종
Opening, TableOfContents,
SectionDivider, Ending
−
지도 전용 4종
Distribution, Location,
Highlight, Geo_Flow
=
41종
capability 대상

왜 뺐나

페이지 역할은 의미 구조가 아니라 페이지에서의 자리다

지도 전용은 지도 데이터가 있어야 성립해 structure 가 표현할 대상이 아니다

다시 열린 질문 (B-24)

49종 전량으로 열어야 하지 않나

enum 은 capability 의 파생물인데 capability 자체가 41종이라 파생 이전에 잘려 있다

페이지 역할은 색인에 실재한다 (v3.5)

허재영님 지적 2026-08-03. 함께 볼 것: 대분류를 없애고 중분류만 남겨도 되는가 (B-25)


capability 테이블: 셋을 선언한다#

선언 무엇인가 못 채우면
성립 조건 이게 없으면 그 종류로 배치가 불가능한 것 그 종류가 후보에서 빠진다
선택 속성 안 적어도 되는 것. 규약이 기본값을 정해 둔다 기본값으로 그린다
fallback 순서 그 종류가 색인에 없을 때 물러날 곳 순서를 따라 내려가고 마지막은 항상 List

성립 조건을 최소로 줄였다

내용을 요구하는 것은 다섯뿐이다

BigNumber 과 Gauge 의 수치, Tree 와 NestedScope 의 children, ComboChart 의 혼합 조건

나머지는 개수와 matrix 유무만 본다

기본값 규칙

기본값은 표현 선택에만 준다. 내용에는 주지 않는다.

apex(어느 쪽이 꼭대기) → 기본값 OK

value(숫자가 얼마) → 없는 데이터를 지어내는 것이므로 성립 조건


fallback: 빈 결과가 사용자에게 가지 않는다#

Gauge
→
BigNumber
→
List

종단이 셋이다

List, LongFormTextLayout, HighlightedMessage

셋 다 개수 조건만 보므로 언제나 하나는 성립한다

→ 대치 실패 정책을 따로 두지 않았다

엣지 75개에 projection 을 정의했다

2026-08-04

손으로 쓰지 않고 규칙 여덟 개를 세운 뒤 각 엣지에 어느 규칙이 걸리는지를 capability 에서 계산했다

→ fallback 을 그래프로 못 박아 다시 계산한 판이다

계산 중에 결함 하나가 드러났다. '선택 속성'열이 내용 필드(period, speaker)와 표현 선택(orientation, apex)을 섞어 담고 있었다. 그대로 계산하면 Timeline → Steps 에서 시각(period)을 버리게 된다. 갈라서 내용은 옮기고 표현만 버리도록 고쳤다.


새로 정한 것: 꾸밈구조#

2026-08-03. 어제까지의 결정을 뒤집었다.


무엇이 뒤집혔나#

항목 2026-08-02 2026-08-03
계층 structure 에 선언하지 않는다 컴포넌트를 nested 구조로 두어 해결한다
꾸밈 질의 쪽 필드를 두지 않는다 꾸밈구조를 둔다. 다만 1단계는 전역 기준만

뒤집은 이유 1

8/2 결정이 판정 책임을 색인 쪽으로 넘겼는데, 색인 쪽에 그것을 받을 선언이 없었다

capability-table.md 에 꾸밈 언급이 0건

뒤집은 이유 2

그 결정의 근거표가 보존 전용 문서에만 있었다

허브가 "v0.5 는 이 철학을 따르지 않는다"고 적어 둔 문서다

SSoT: design/decoration-model.md, 할 일: TODO.md B-2, B-3, B-18~B-25


왜 등급 하나로는 안 되나#

7/24 회의의 N / N+α / N+β 는 누적 등급이다. N ⊂ N+α ⊂ N+β

1. 다른 것이 섞인다

N+β 가 '요소 배치와 장식'이다

배치가 정교한데 그림이 없는 컴포넌트가 있다

2. 조합을 못 적는다

'배경은 원하지만 일러스트는 싫다'에 이름이 없다

3. 끼울 자리가 없다

배경, 테두리, 아이콘이 생기면 'N+β 안쪽'이라 할 수밖에 없다

4. N 은 애초에 꾸밈 축이 아니다

N 의 '최소 필요 조건'은 capability 성립 조건과 같은 대상이다. 등급 최하단으로 두면 같은 사실이 두 곳에 생긴다.


그래서 축 집합으로 둔다#

yaml
decoration:            # 꾸밈구조 하나 (component.decoration)
  ornament: prefer     # 꾸밈 축 하나 + 선호값 하나
  background: avoid    # 꾸밈 축 하나 + 선호값 하나

축은 RLSC role 에 대응한다

축 RLSC role
ornament Element.Decoration
background Element.Background

축이 role 하나를 가리키면 추출이 이미 붙인 표식을 쓸 수 있다

선호값은 셋이다

값 뜻
prefer 있으면 가점
indifferent 보지 않는다 (기본값)
avoid 있으면 감점

require 를 두지 않는다. 꾸밈을 필수로 걸면 후보가 0이 된다

minimal(N) 과 rich(N+β) 는 문서와 회의에서 부르는 별명이고 스키마 값이 아니다. 축이 늘 때 뜻이 바뀌기 때문이다.


판정 기준 하나로 축이 갈린다#

대치가 내용으로 채우는 노드는 선호가 필요 없다. 내용이 이미 답이다. 대치가 건드리지 않는 노드만 선호가 필요하다.

RLSC role 대치가 채우나 그래서
Element.Title · Subtitle · Description 채운다 내용이다. entity.label 과 entity.components[] 가 담는다 → 축 아님
Element.Highlight 채운다 내용이고 entity.emphasis 가 이미 있다 → 축 아님
Element.Decoration 안 채운다 role 정의: "내용 대치 시 변경 불필요" 입력에 대응물이 없다 → 축 필요
Element.Background 안 채운다 위와 같다 → 축 필요

부수 효과 N+α(타이틀과 디스크립션 등급)는 개념 자체가 사라졌다. 표현 밀도가 아니라 내용 유무다. 그리고 계층과 DESC 가 entity.components[] 한 수단을 쓰게 됐다.


1단계 범위: 전역 기준만 잡는다#

층 1단계 (지금) 요소 대치 착수 후
Summary (질의) 생성하지 않는다. 스키마에 optional 로 자리만 필요하면 생성기가 적는다
카테고리 기본 미정 41종에 채운다
시스템 전역 기본 여기만 잡는다 예: 꾸밈 많은 것을 우선 유지

왜 미루나

  • 지금 소비할 주체가 없다. 요소 대치가 아직 동작하지 않는다
  • 색인이 대조할 대상을 못 받는다
  • 지금 필요한 것은 전역 기준 하나로 된다

선례가 있다

entity.components[] 와 page relations 가 모두 '스키마에 열어 두고 생성기는 채우지 않는다'

decoration 도 같은 방식으로 연다

내용 대치는 꾸밈에 관여하지 않는다. 꾸밈구조는 앞으로 요소 대치가 참고하는 값이다.


요소 대치: 꾸밈구조를 쓰는 곳#

신동한님 쪽 모듈(AI 꾸미기)을 참고해 구현하기로 했다 2026-07-23 회의

① reference design
template / page / component
→
② 교체 target
AI 가 식별
→
③ query 와 embedding
교체 대상 서술
→
④ 확정 제약
size 등

난점은 ②와 ③이다

7/23 회의 원문: "비슷한 요소 찾기 사용. 다만 교체 타겟 결정 및 타겟별 query 생성이 중요"

검색 자체보다 무엇을 바꿀지 정하고 어떻게 서술할지가 문제다

정하지 않은 것

reference design 이 꾸밈 수준을 이미 정해 버리면 꾸밈구조는 요소 대치에 안 쓰이고 검색 단계에만 쓰인다

7/23 기록은 "장식 요소는 유지하고 내용 관련 요소만 대치"다


다섯 구간이 모두 대응해야 동작한다#

구간 무엇을 하나 지금 상태
추출 노드에 Decoration·Background role 을 붙인다 role 은 이미 붙는다
색인 그 role 을 얼마나 가졌는지 색인에 담는다 경로가 없다 text_map·text_node_roles·semantic_tags 는 있는데 노드 분류 자리가 없다
검색 기본값과 선호를 대조해 순위를 움직인다 capability 에 꾸밈 축 열이 없다
Visual Summary 그 입력의 선호를 적는다 표기 미정 (B-18)
대치 선호에 맞춰 role 노드를 지우거나 남긴다 요소 대치의 일. 아직 동작하지 않는다

색인 경로가 먼저 풀려야 나머지가 동작한다. 추출이 role 을 붙여도 색인이 못 받으면 검색이 대조할 대상이 없다. semantic_tags denormalize 방식이 선례다. → TODO.md I-4, B-22


누가 무엇을 소유하나#

구간 담당
단어장 v7 정의와 유지 김수현
Summary 생성기 스키마 정원석 · 생성 실무 허재영, 황현진
시멘틱 쿼리 추출 LLM · 컴포넌트 태깅 황현진
태깅 결과 수신·검증·적재 류민 → 박지우
노드 분류와 카테고리별 속성 신영빈 · 류민
I.I.S. (4단계) 류민 2026년 6월 중순부터 정지
시그니처·랭킹·대치 계산 (comp-sign) 이석
Visual Summary 규약 정원석
검색과 대치 실행 최운식 (BE)

layouts 태깅은 BE 로 가지 않는다. RAT pod 내부용이고 검색에는 컴포넌트 태깅만 쓴다.


오늘 각자에게 물을 것#

강창석님

  • dataset 을 DataViz Matrix 로 바로 넘길 수 있나 (어댑터 실측)
  • nested_scope 를 concentric_rectangles 로 볼 수 있나
  • 스마트 다이어그램 32종 중 2단계에 열 것이 있나

이석님

  • semantic 만 빼고 검색과 대치가 되나. toggle 이 있나
  • 세 축 정렬을 언제 누구와
  • rankCompCandidates 위계 가중치 A/B
  • 41종 fallback 순서 검토

류민님

  • I.I.S. 재개 시점
  • tree children → Graph relations 규칙
  • 카테고리별 속성 목록 초안 리뷰

허재영님

  • Venn 라벨 오류 프롬프트 225건 중 148건이 오라벨
  • entity.components[] 생성 시점
  • 평가셋 38 중분류 확장

급한 순서와 오늘 정할 것#

급한 순서 2026-08-02 기준

  1. C-1 모듈 버전 2.0 BE 정책 협의
  2. C-2 semantic tag 데이터 흐름 공유
  3. C-6 태깅 증량분 재전달 9만 → 16만
  4. C-7 컴포넌트 분포 재측정
  5. I-2 세 축 정렬
  6. I-6 위계 가중치 A/B

오늘 정할 것

  • 다음 주요 작업 3축 분담 서머리 생성 / 스마트시리즈 / 조합
  • 2단계 마감 8/18 유지 여부
  • 검색풀 17만 증량분에 judge 를 돌릴지
  • 꾸밈구조 표기와 entity.components[] 생성 시점
  • 41종 vs 49종 (B-24), 대분류 제거 (B-25)
  • List 편중 81% 를 두고 2단계를 낼지

참고#

먼저 볼 것 둘

spec/schema-v0.5.md 규약

TODO.md 할 일 SSoT

이 덱의 근거

spec/capability-table.md 41종 design/decoration-model.md 꾸밈구조 design/pipeline-flow.md 전체 흐름 design/content-binding-contract.md reference/semantic-taxonomy-v7.md README.md 로드맵과 분포

저장 위치 github.com/miridih/mordor-wiki → projects/mordor/visual-summary/

GitHub 계정이 없으면 Confluence 발행본으로 공유한다 (TODO.md C-8)

↑ 목차로 · 8부 머리로

9부

보존 — archive/

대체된 판. 지금 규약은 이것을 따르지 않는다. 왜 바뀌었는지 볼 때만 연다.

56장스키마 규약 v0.4 보존보존 전용 · 장식과 계층 원 논의

원문 archive/schema-v0.4.md GitHub 에서 보기 24 KB 원문 제목 「Summary 와 RLSC 공용 스키마 규약 v0.4 (보존)」

SSoT는 여기가 아니다. SSoT: schema-v0.5.md

이 문서는 2026-08-02 에 schema-v0.5.md 로 대체됐다. 갱신을 멈추고 기록으로 보존한다. v0.5 에서 무엇이 바뀌었는지는 그 문서 0절에 있다. 이 문서를 참조하는 다른 문서의 절 번호는 v0.4 기준이므로, 지금 값을 볼 때는 v0.5 를 봐야 한다.

여기에만 있는 것 4-1절의 장식과 계층 논의(N, N+α, N+β 정의와 7/24 회의 인용)는 v0.5 로 옮기지 않았다. 그 논의의 결론이 "질의 쪽에 선언 필드를 두지 않는다"였고, 결론과 근거는 projection-model.md 6절에 있다. 원 논의 맥락이 필요하면 이 문서 4-1절을 본다.

오너 정원석 (MORDOR) · 상태 보존 · 대체된 날 2026-08-02 · 티켓 MOR-1773


이 문서의 역할 (당시) Summary 의 structure 와 RLSC 를 잇는 공용 스키마 규약이다. Visual Summary v0.2와 v0.3의 2층 구조와 불변식을 그대로 계승하고, 여기에 RLSC 매핑 계층을 정식으로 추가한다.

예시 모음은 yaml-examples.md, 프로젝트 맥락과 로드맵은 README.md 에 있다.


0. 왜 이 문서가 필요한가#

v0.2 와 v0.3 은 structure(컴포넌트 무관 의미 구조)와 component(선호 컴포넌트, v0.3 까지의 이름은 hint)까지는 정의했지만, 그 컴포넌트가 실제 RLSC 어휘의 무엇인지는 정의하지 않았다. 이 enum(list, venn, pyramid 등 11종)은 RLSC 의 LayoutElementKey(VennDiagram, LayeredDiagram, Chart 와 chartType 조합 등)와 1:1이 아니다. 이 불일치를 메우지 않으면 검색 모듈이 'structure 가 이 컴포넌트에 대치 가능한가'를 판정할 근거가 없다.

이 규약은 그 매핑을 capability 선언 테이블로 고정한다. structure 는 손대지 않는다(컴포넌트 무관을 유지). 대신 RLSC 쪽이 '나는 이 요구를 소화할 수 있다'를 선언하고, 그 선언이 검색 모듈 스코어링의 SSoT 가 된다.

1. 설계 원칙 (v0.2 계승)#

  • 2층 구조 Summary 는 component(soft, tie-breaker)와 structure(검색 모듈이 RLSC 와 매칭하는 대상)로 생성한다.
  • 불변식 A, 평탄화 가능성 어떤 구조든 항상 리스트로 읽히는 entities(또는 dataset.series 에서 파생)를 가진다. 나열형(list)은 언제나 성립하는 floor 이다.
  • 불변식 B, 관계는 가산적 relations, 슬롯, dataset 은 추가 정보인다. 소화 못 하는 컴포넌트는 무시한다.
  • 불변식 C, 중첩은 가산적 entity.components 는 추가 정보인다. 중첩을 소화 못 하는 컴포넌트는 자식 컴포넌트의 entities 를 부모 entity 의 items 로 평탄화한다. 불변식 A 가 여기에도 걸리므로 깊이가 얼마든 결국 list 로 읽힌다.
  • 컴포넌트 결정을 데이터에 박지 않는다 벤이든 나열형이든 같은 structure 에서 투영한다.
  • RLSC 는 규약이지 선택 주체가 아니다 RLSC 는 컴포넌트의 구조를 기술하는 어휘다. 최종 컴포넌트 확정은 검색 모듈이 아래 capability 테이블과 component 로 수행한다.

2. Core Schema v0.4#

v0.2 와 v0.3 을 하나로 병합한 검증 스키마다. v0.3 과의 델타는 셋이다(근거는 모두 TODO.md G절).

  1. version const 가 "0.4" 다.
  2. 판별자 키를 type 하나로 통일했다. hint → component, 그 안의 component → type, relation.kind → relation.type, dataset.kind → dataset.type. series.type 은 원래 type 이었다. 다중 래퍼는 blocks[] 에서 components[] 로 바꿨다(8절).
  3. entity.components[] 를 추가했다. 중첩 컴포넌트를 표현할 자리다. 지금은 생성기가 채우지 않는다(8-1절).

relation, dataset, series 의 필드 구성과 entity 의 나머지 필드는 v0.3 과 동일하고, 바뀐 것은 판별자 키 이름뿐이다. structure 와 component 는 $defs 로 뽑아 componentBlock 이 같은 정의를 재사용하게 했고, 내용은 그대로다. 이 문서는 스키마를 새로 만들지 않고 매핑 계층만 추가한다. (Confluence 판에서는 $defs 를 v0.3 링크로 대신했는데, repo 판에서는 이 파일만 보고도 검증기를 짤 수 있도록 전문을 싣는다.)

json
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://miridih.com/schemas/visual-summary/0.4",
  "title": "VisualSummary",
  "type": "object",
  "required": ["version", "title", "structure"],
  "additionalProperties": false,
  "properties": {
    "version": { "const": "0.4" },
    "title":   { "type": "string", "minLength": 1 },
    "component": { "$ref": "#/$defs/component" },
    "structure": { "$ref": "#/$defs/structure" }
  },
  "$defs": {
    "component": {
      "type": "object",
      "additionalProperties": false,
      "properties": {
        "type":       { "$ref": "#/$defs/componentType" },
        "confidence": { "type": "number", "minimum": 0, "maximum": 1 },
        "params":     { "$ref": "#/$defs/componentParams" }
      }
    },
    "componentType": {
      "enum": ["list","big_number","venn","nested_scope","tree","pyramid",
               "quote","dialogue","gauge","stacked_bar","combo"]
    },
    "componentParams": {
      "type": "object",
      "additionalProperties": false,
      "properties": { "apex": { "enum": ["first","last"] } }
    },
    "structure": {
      "type": "object",
      "additionalProperties": false,
      "anyOf": [ { "required": ["entities"] }, { "required": ["dataset"] } ],
      "properties": {
        "entities":  { "type": "array", "minItems": 1, "items": { "$ref": "#/$defs/entity" } },
        "relations": { "type": "array", "default": [], "items": { "$ref": "#/$defs/relation" } },
        "dataset":   { "$ref": "#/$defs/dataset" }
      }
    },
    "componentBlock": {
      "type": "object",
      "required": ["structure"],
      "additionalProperties": false,
      "properties": {
        "type":      { "$ref": "#/$defs/componentType" },
        "params":    { "$ref": "#/$defs/componentParams" },
        "structure": { "$ref": "#/$defs/structure" }
      }
    },
    "entity": {
      "type": "object",
      "required": ["id", "label"],
      "additionalProperties": false,
      "properties": {
        "id":       { "type": "string", "pattern": "^[a-zA-Z0-9_-]+$" },
        "label":    { "type": "string", "minLength": 1 },
        "items":    { "type": "array", "items": { "type": "string" } },
        "value":    { "type": "number" },
        "unit":     { "type": "string" },
        "emphasis": { "type": "boolean" },
        "weight":   { "type": "number" },
        "speaker":  { "type": "string" },
        "author":   { "type": "string" },
        "source":   { "type": "string" },
        "children":   { "type": "array", "items": { "$ref": "#/$defs/entity" } },
        "components": { "type": "array", "items": { "$ref": "#/$defs/componentBlock" } }
      }
    },
    "relation": {
      "type": "object",
      "required": ["type", "of"],
      "additionalProperties": false,
      "properties": {
        "type": { "enum": ["intersects"] },
        "of":   { "type": "array", "minItems": 2, "items": { "type": "string" } },
        "desc": { "type": "string" }
      }
    },
    "dataset": {
      "type": "object",
      "required": ["type"],
      "additionalProperties": false,
      "properties": {
        "type":   { "enum": ["gauge", "stacked_bar", "combo"] },
        "unit":   { "type": "string" },
        "value":  { "type": "number" },
        "target": { "type": "number" },
        "min":    { "type": "number" },
        "max":    { "type": "number" },
        "thresholds": {
          "type": "array",
          "items": {
            "type": "object",
            "required": ["at"],
            "additionalProperties": false,
            "properties": {
              "at":    { "type": "number" },
              "label": { "type": "string" },
              "state": { "enum": ["safe", "warn", "danger"] }
            }
          }
        },
        "dimensions": { "type": "array", "items": { "type": "string" } },
        "stacking":   { "type": "boolean" },
        "series":     { "type": "array", "items": { "$ref": "#/$defs/series" } }
      }
    },
    "series": {
      "type": "object",
      "required": ["id", "label", "values"],
      "additionalProperties": false,
      "properties": {
        "id":     { "type": "string", "pattern": "^[a-zA-Z0-9_-]+$" },
        "label":  { "type": "string" },
        "type":   { "enum": ["bar", "line", "area"] },
        "axis":   { "enum": ["left", "right"] },
        "unit":   { "type": "string" },
        "values": { "type": "array", "items": { "type": "number" } }
      }
    }
  }
}

생성 메커니즘 (2026-08-02 정정, 정원석) LLM 이 YAML 을 직접 생성한다. YAML 을 쓰는 이유가 추론 속도이기 때문이다. YAML 은 JSON 보다 구두점 토큰이 적어 같은 내용을 더 적은 생성 토큰으로 쓰고, 생성 토큰 수가 추론 시간을 결정한다. JSON 으로 생성한 뒤 YAML 로 직렬화하는 방식(v0.2 원문의 "structured output 으로 생성하고 직렬화만 YAML")은 이 이득이 없으므로 쓰지 않는다. 검증은 생성된 YAML 을 JSON 으로 변환한 뒤 이 JSON Schema 로 수행하고, 실패하면 재생성한다. 이 결정으로 YAML 파싱이 실경로가 되므로, 파싱 실패와 silent corruption 계측(TODO.md F-2)이 생성기 요건이 된다.

3. RLSC 매핑 (capability 테이블)#

이 규약의 핵심이다. 아래는 repo 실물 기준이다. 레이아웃 어휘는 src/app/pipeline/common/type/layoutStructure.ts 의 LayoutElementKeyType 과 ChartTypeSchema, 역할 어휘는 packages/structured-layout/src/role-v2.ts 의 Role.Element.* 이다. structure 의 각 컴포넌트가 어떤 RLSC 타입으로 대치되는지, 무엇을 요구하는지, 슬롯이 어느 Role 로 소비되는지를 선언한다.

상태 표기는 셋이다. 있음 = RLSC 에 1:1 대응이 있음. 부분 = 근접 타입은 있으나 의미나 속성이 어긋나 검증이 필요함. 갭 = RLSC 에 전용 대응이 없고 투영으로만 처리함.

structure 컴포넌트 RLSC type chartType 필수 요구 슬롯이 소비되는 Role 상태
list (floor) VStack / HStack / Grid / Group - entities >= 1 label → Role.Element.Title, items → Role.Element.Description 있음
venn VennDiagram - entities >= 2 + relations.intersects label → Title, items → Description, relation.desc → Description 있음
tree Graph - entities.children 존재 children → GraphNode + GraphEdge(from/to), label → node.label 부분
combo Chart COMBO_CHART dataset.type=combo + series >= 2 (혼합 type 또는 이중 axis) series → data 행과 열, dimensions → 축 있음
gauge Chart BAR_GAUGE / CIRCLE_GAUGE dataset.type=gauge + value + (target 또는 max) value, target, thresholds → 차트 속성 있음
nested_scope DisjointEulerDiagram - entities.children 존재 children → 중첩 스코프 부분
pyramid LayeredDiagram - entities >= 3 + component.params.apex weight → 층 크기, 방향은 I.I.S. page 추론 부분
stacked_bar Chart BAR_CHART_VER / BAR_CHART_HOR dataset.type=stacked_bar + dimensions + series >= 2 series → data, dimensions → 축 부분
big_number (전용 컨테이너 없음) - entities.value 최소 1 Text(Role.Element.Highlight) 단일 강조로 투영 갭
quote (전용 컨테이너 없음) - entities.author 또는 source Group + Description + Title(author)로 투영 갭
dialogue (전용 컨테이너 없음) - entities >= 2 + speaker VStack 반복 + Marker(speaker)로 투영 갭

강등(degradation) 규칙 (v0.2 5절 계승)

  1. 후보 컴포넌트의 필수 요구를 못 채우면 후보에서 제외한다.
  2. 모든 특수 컴포넌트가 탈락하면 list floor 로 대치한다(항상 성립).
  3. 선택된 컴포넌트가 못 그리는 relations 나 슬롯은 삭제하지 않고 note 로 흘려보낸다.
  4. component 와 실제 구조가 충돌하면 구조가 우선이고 component 는 참고값이다. 단 component.params 는 예외다. pyramid 행이 component.params.apex 를 필수 요구로 걸고 있으므로 이 값은 참고값이 아니라 성립 조건이다.

이 1번 규칙이 4-1절에서 문제가 된다. 지금은 통과 아니면 탈락 둘뿐이다.

4. 역방향 갭: RLSC 엔 있는데 structure 에 없는 것#

매핑을 양방향으로 보면, RLSC 에 의미 있는 타입인데 structure 가 표현할 수단이 없는 것들이 드러난다. 2단계 지원 범위를 정하거나 v0.5 스키마를 확장할 때의 입력이다.

RLSC 타입 무엇인가 structure 표현 수단 조치
Quadrant 2x2 매트릭스, axisLabels 보유 없음. relations 로 사분면 위치 표현 불가 신규 컴포넌트 또는 relation.type 확장 필요
Chart: RADAR_CHART 레이더 또는 방사형 dataset.type 에 radar 없음 dataset type 확장 후보 (v0.3 열린 결정)
Chart: PIE / DONUT 비율 원형 dataset.type 에 pie 없음 dataset type 확장 후보
Chart: LINE / AREA 추세선 series.type 엔 line, area 가 있으나 단독 type 없음 combo 하위로 흡수할지 별도 type 을 둘지 결정
Gantt 일정 타임라인 없음 2단계 범위 밖. 4단계 이후 검토
Table 행렬 데이터 없음 (list 로만 평탄화) 구조 손실 감수 또는 신규 컴포넌트
ZStack / Frame 배치 컨테이너 의미 구조가 아님 매핑 불필요 (물리 배치는 I.I.S. 소관)

4-1. 아직 규약에 없는 두 축: 장식과 계층#

상태: 미반영. 7/24 회의에서 방향이 나왔지만 아직 스키마에 자리가 없다. 여기에 근거만 기록하고, 어떻게 담을지는 11절 열린 결정으로 넘긴다.

장식(decoration)#

7/24 회의의 '장식(데코레이션) 처리' 항목이 이렇다.

현재 구조는 장식을 검색과 대치에 넣지 않는다. 그런데 장식 하나 때문에 컴포넌트 응집도가 떨어져 보이는 케이스가 있다. 방침은 장식 없는 깨끗한 버전을 무조건 하나 만들고, 타이틀과 디스크립션을 살린 버전, 장식을 살린 버전을 별도로 두는 것이다. 장식이 있는 건 삭제할 수 있지만 없는 건 추가가 어려우므로 장식 보존 버전도 함께 내보낸다.

N / N+α / N+β: 정의#

같은 회의의 N / N+α / N+β 버전이 위 방침을 등급으로 쪼갠 것이다. 이름이 회의록에만 있고 규약에 정의가 없어 오독되기 쉬우므로 여기에 고정한다.

출처 (원문 그대로) 2026.07.24 모르도르 추가 논의 "Discussed — 안건 1 → 컴포넌트의 N / N+α / N+β 버전" 절. repo 사본: ../2026-07-24-mordor-additional-discussion.md

"하나의 레이아웃에서 스텝스에 대해 여러 버전을 만든다: 스텝 최소 필요 조건만 만족하는 버전(N, 필수), 스텝 + 타이틀·디스크립션까지 원하는 사용자용(N+α), 요소 배치·장식까지 원하는 사용자용(N+β). 사용자 수요에 맞춰 대응."

등급 무엇을 담나 필수 여부 왜 이 등급이 있나
N 그 semantic category 의 최소 필요 조건만 만족하는 버전. 장식 없는 '깨끗한' 형태 필수 — 무조건 하나는 만든다 응집도 판정의 기준선. 장식 때문에 컴포넌트가 어수선해 보이는 케이스를 이 버전이 걷어낸다
N+α N + 타이틀과 디스크립션까지 살린 버전 선택 입력에 제목·설명이 있는 사용자를 위한 것. "컨테인즈만 원하는지, 타이틀·디스크립션까지 원하는지"를 입력만 보고 판단한다(같은 회의)
N+β N+α + 요소 배치와 장식까지 살린 버전 선택 장식은 삭제할 수 있어도 없는 걸 추가하기는 어렵다. 그래서 장식 보존 버전을 처음부터 함께 내보낸다

세 가지를 오해하지 않도록 못 박아 둔다.

첫째, α 와 β 는 누적이다. N ⊂ N+α ⊂ N+β 로, 뒤 등급이 앞 등급을 포함한다. 서로 배타적인 세 갈래가 아니다.

둘째, 이건 지금 색인(컴포넌트) 쪽 개념이다. 하나의 원본 레이아웃에서 컴포넌트 변형을 몇 벌 생성해 색인에 넣을 것인가의 이야기이지, Summary 가 무엇을 적는가의 이야기가 아니다. 회의에서 예시로 든 것이 '스텝스'인 이유도 그것이 당시 파일럿 유형이었기 때문이고, 등급 자체는 유형과 무관한다.

셋째, N 은 '장식 등급'이자 동시에 '필수 조건'이라는 두 뜻이 겹쳐 있다. 여기서 N 의 '최소 필요 조건'은 3절 capability 테이블의 '필수 요구' 와 같은 대상을 가리킨다. 그래서 4-1절이 제안하는 등급 3분(필수/선호/무시)과 장식 3등급(N/N+α/N+β)은 다른 축이다. 전자는 '이 컴포넌트가 후보로 살아남는가', 후자는 '그 컴포넌트를 몇 벌 만들어 두는가'이다. 이름이 비슷해 섞이기 쉬우니 문서에서는 항상 축을 밝혀 적는다.

문제는 이게 컴포넌트(색인) 쪽 변형 생성 전략으로만 존재한다는 것이다. 같은 회의에서 "인풋을 보고 컨테인즈만 원하는지, 타이틀과 디스크립션까지 원하는지 판단한다"고 해 놓았는데, 그 판단 결과를 담을 필드가 Summary 스키마에 없다. 현재 스키마에서 가장 가까운 것은 entity.emphasis(boolean)와 entity.weight(number)인데, 이건 '이 항목이 중요한가'이지 '장식이 얼마나 붙는가'가 아니다.

계층#

계층은 규약에 들어와 있지만 하드 필수 조건으로만 있다. 3절 capability 테이블에서 tree 와 nested_scope 의 필수 요구가 둘 다 "entities.children 존재"이고, 강등 규칙 1번은 '필수 요구 미충족이면 후보에서 제외'이다. 그래서 두 가지를 표현하지 못한다.

첫째, **'계층이 있으면 좋지만 없어도 됨'**을 못 적는다. 지금은 있으면 통과, 없으면 탈락 둘뿐이다.

둘째, **'계층이 있는데 이 컴포넌트가 평탄해서 무시해도 되는가'**를 못 적는다. 불변식 A 덕분에 list 로 강등은 되지만, 계층을 잃는 게 치명적인 경우(조직도)와 그냥 들여쓴 리스트라 잃어도 되는 경우가 스키마상 구분되지 않는다.

단어장 정의문은 이미 이 구분을 하고 있다. Hierarchical Tree 는 "레이어 넓이보다 연결과 소속이 먼저 읽힘", Nested Scope 는 "크기 차이는 범위의 크기이지 중요도 강조가 아님", Pyramid 는 "층 간 위계가 메시지이지 단순 3단 Steps 가 아님"이라고 쓴다. 말로는 구분하는데 스키마가 그걸 못 받는다. 7/24 회의에서도 "핵심은 벤에 무엇을 넣느냐가 아니라 노드트리에 계층을 주는 것"이라며 대치 정확도의 관건으로 짚었다.

여기서 말하는 계층은 entity.children, 곧 한 컴포넌트 안의 의미 계층이다. 컴포넌트 경계를 넘는 중첩(entity.components)은 다른 축이고 8-1절에 있다. 둘을 섞으면 '조직도의 하위 부서'와 '단계 칸 안에 든 빅넘버'가 같은 문제로 보인다.

등급이 필요하다는 근거#

같은 7/24 회의에서 검색 방식을 이야기하며 등급을 이미 말로 정했다. "빅넘버와 리스트가 반드시 있는지 검색하고, 없으면 카테고리별 가중치를 줘서 링킹한다. 리스트는 절대 포기하지 않고, 빅넘버는 포기 가능한 식"이다. 이것도 규약에 없다.

README.md 3절의 실데이터 분포가 이걸 뒷받침한다. venn, tree, nested_scope, pyramid 는 각 164건 이하이고 gauge, stacked_bar, combo 같은 데이터 차트는 각 51건 이하다. 8만 건 중 51건이면 필수 요구를 엄격하게 걸었을 때 후보가 0이 되어 전부 list 로 강등된다. 등급을 나눠 후보를 살려 두는 편이 낫다는 실증 근거다.

이미 있는 장치#

'고려할 수도 있고 무시할 수도 있다'는 건 새 원칙이 아니라 불변식 B 그 자체이다. v0.2 요구조건 표에도 '활용(가산)' 열이 따로 있고 emphasis, weight, thresholds, stacking 이 거기 들어가 있다. 그러니 원칙을 새로 만들 필요는 없고, 필수 요구 쪽이 이분법이라는 것 하나만 풀면 된다.

제안 (미확정, 11절에서 결정)#

필수와 가산 두 칸을 세 칸으로 벌린다. 필수(없으면 탈락), 선호(있으면 가점, 없어도 후보 유지), 무시(소화 못 해도 무방). 그리고 두 축을 명시 필드로 둔다. structure 는 컴포넌트 무관을 유지해야 하므로 컴포넌트 이름이 아니라 의미 축으로만 적는다.

yaml
# Summary(질의) 쪽: 전부 optional. 소화 못 하는 소비자는 무시 (불변식 B)
structure:
  traits:
    hierarchy: essential     # essential(계층이 메시지) | incidental(있지만 평탄화해도 됨) | none
    decoration: minimal      # minimal(N) | labelled(N+α) | rich(N+β)
yaml
# 규약(색인) 쪽: rlsc-capability.yaml
- id: tree
  requires: [ "entities.children" ]
  prefers:  [ "traits.hierarchy=essential" ]
  ignores:  [ "traits.decoration" ]
- id: list                                  # floor, 항상 성립
  requires: [ "entities>=1" ]
  ignores:  [ "traits.hierarchy", "traits.decoration" ]

두 축의 성격이 다르다. 계층은 양쪽 다 필요하다. 질의 쪽은 '이 입력에서 계층이 본질인가'를 생성기가 판단해 적고, 색인 쪽은 '이 컴포넌트가 계층을 소화하는가'를 정적으로 선언하고, 매칭은 그 둘을 대조한다. 장식은 색인 쪽이 회의 결정(3버전 생성)으로 이미 채워진다. 새로 필요한 건 질의 쪽 한 줄, '이 입력이 어느 레벨을 원하는가'이다.

5. 매핑 계층을 어디에 두나#

capability 테이블은 structure 스키마 밖의 별도 artifact 로 둔다. 이유는 둘이다.

structure 의 컴포넌트 무관성 유지 RLSC type 이나 Role 을 structure 필드에 박으면 Summary 가 RLSC 버전에 결합되고, 4단계 node 태그 확장 때 기존 Summary 가 전부 깨진다. 매핑을 밖에 두면 Summary 는 순수 의미 데이터로 남는다.

검색 모듈의 SSoT 검색 모듈은 이 테이블만 읽어 스코어링한다. RLSC 어휘가 바뀌면 테이블만 고치면 되고 생성기는 안 건드린다.

5.1 형식은 Summary 표면 포맷과 별개 축이다#

6절의 YAML 확정은 LLM 이 매 요청마다 생성하는 것에 대한 결정이다. capability 테이블은 그 반대다. 사람이 손으로 관리하고, 배포 단위로 고정되고, 실행 중에 바뀌지 않는다. 6절이 든 근거 넷(LLM 생성 벤치마크, 기존 산출물이 이미 YAML, 파서 구축 비용, 마감)은 여기에 하나도 적용되지 않는다.

코드 상수가 유력하다. 근거 둘이다.

첫째, RLSC 어휘가 이미 타입으로 존재한다. LayoutElementKeyType, ChartTypeSchema, Role.Element.* 가 그렇다. 테이블을 상수로 두면 대응이 실제 어휘와 맞는지 컴파일러가 검사한다. 데이터 파일로 두면 requires: ["entities>=2", "dataset.target|dataset.max"] 처럼 문자열 안에 규칙 DSL 을 넣게 되고, 이 DSL 은 파서를 따로 만들어야 하는 데다 오타가 런타임까지 안 걸린다.

둘째, 선례가 있다. packages/comp-sign 에는 YAML 과 JSON 자원 파일이 없다(package.json, tsconfig.json 제외). 채점 가중치는 scoringWeights.ts, 단어장은 semanticTaxonomy.gen.ts 로 전부 TS 상수다. 단어장은 원본이 Google Sheet 인데도 시트를 직접 읽지 않고 상수 파일로 내보낸다.

ts
// rlsc-capability.ts (규약 부속 상수 예시)
export const RLSC_CAPABILITIES = [
  {
    id: 'venn',
    rlsc: { type: LayoutElementKey.VennDiagram },
    requires: { minEntities: 2, relations: ['intersects'] },
    consumes: { label: Role.Element.Title, items: Role.Element.Description },
  },
  {
    id: 'gauge',
    rlsc: { type: LayoutElementKey.Chart, chartType: [ChartType.BAR_GAUGE, ChartType.CIRCLE_GAUGE] },
    requires: { dataset: 'gauge', needs: ['value'], needsOneOf: ['target', 'max'] },
  },
  {
    id: 'list',   // floor, 항상 성립
    rlsc: { type: [LayoutElementKey.VStack, LayoutElementKey.HStack, LayoutElementKey.Grid, LayoutElementKey.Group] },
    requires: { minEntities: 1 },
    consumes: { label: Role.Element.Title, items: Role.Element.Description },
  },
] as const satisfies readonly Capability[]

남은 변수 검색 모듈(BE)이 TypeScript 가 아니면 언어 중립 형식이 하나 더 필요하다. 그때도 SSoT 는 상수 하나로 두고 생성물로 내보낸다. semanticTaxonomy.gen.ts 와 같은 방식이다. 어디에 두고 어떻게 로드할지는 TODO.md B-14 다.

6. 포맷: YAML 로 확정 (초안)#

2026-08-01 초안 확정. 표면 형식은 YAML 이고 KDL 은 채택하지 않는다. 근거 넷은 README.md 2.1절에 있다. 요약하면 사전 조사에서 YAML 우세가 뚜렷하고(KDL 은 LLM 생성 벤치마크가 0건), v0.2 이후 산출물 전체가 이미 YAML 로 쓰여 있으며, KDL 로 가면 파서와 검증 연결, 정규화, 호환 계층을 직접 만들어야 하고, 규약 확정 마감이 W32 말(2026-08-07)이라 실측을 돌릴 시간이 없다.

YAML 은 LLM 이 직접 써 내는 표면이다(2절 생성 메커니즘, 2026-08-02 정정). 채택 이유는 추론 속도이고, 검증은 YAML 을 JSON 으로 변환한 뒤 2절 스키마로 한다. capability 매핑 계층은 형식과 무관하게 동일한다.

초안이라는 뜻 실측은 사후 검증으로 남겨 두었다(README.md 2.1절 사후 검증 설계). 결과가 YAML 을 뒤집으면 그때 재검토한다. 그리고 YAML 을 골랐으므로 생성기에는 파싱은 됐는데 타입이 뒤바뀌는 silent corruption 지표를 처음부터 넣는다.

7. 설계 제약: 기존 RLSC 를 변경 없이 mapping#

structure 는 기존 RLSC 노드를 수정하지 않고 그대로 투영받을 수 있는 형태여야 한다. 새 필드를 RLSC 에 심는 방향이 아니라, RLSC 가 이미 가진 부분(children, role, chart data 등)에 structure 의 슬롯이 얹히는 방향이다. 2단계와 3단계에서 RLSC 스키마는 손대지 않는 것을 원칙으로 하고, node 단위로 RLSC 를 실제 확장하는 것은 4단계(I.I.S.)로 미룬다.

Visual Summary structure 기존 RLSC 요소 변경 필요?
entities[] LayoutContainer children (VStack/HStack/Grid) 없음
entity.label Role.Element.Title 텍스트 노드 없음
entity.items[] Role.Element.Description 텍스트 노드 없음
entity.children[] 중첩 LayoutContainer children 없음
entity.components[] 중첩 LayoutContainer 안의 별도 컴포넌트 없음. 지금은 생성하지 않고, 들어와도 불변식 C 로 평탄화한다
entity.value 와 unit Chart data cell 또는 Highlight 텍스트 없음
relations.intersects VennDiagram / DisjointEulerDiagram 없음
dataset.series Chart data(2D), seriesAxis 없음
entity.speaker 와 author Role.Element.Marker / Title (슬롯 재사용) 없음

RLSC 에 신규 어휘가 필요한 것은 3절에서 갭으로 표시한 소수(big_number, quote, dialogue 전용 컨테이너)뿐이고, 그 경우도 스키마 확장이 아니라 기존 Role 조합으로 투영해 우회한다.

8. 다중 컴포넌트: 여러 category 가 든 페이지#

한 입력(한 페이지 분량)이 성격이 다른 여러 덩어리로 나뉘면 structure 하나로 담지 못한다. 이때는 page 단위로 components[] 를 두고, 각 원소가 컴포넌트 하나를 맡는다. 원소의 키는 단일 형태의 component 안에 있던 것(type, params)이 structure 와 같은 높이로 펴진 것이다. 최상위가 structure(단일) 또는 components[](다중) 중 하나를 갖도록 하며, 단일 형태 하위 호환은 유지된다. 원소 순서는 페이지 내 읽기 순서다. core 편입 여부는 11절 열린 결정에 있다.

yaml
# components 래퍼 (제안)
version: "0.4"
title: 2025 사업 성과 요약
components:
  - type: big_number
    structure:
      entities:
        - { id: rev, label: 총매출, value: 152, unit: 억원, emphasis: true }
        - { id: yoy, label: 전년 대비, value: 38, unit: "%" }
  - type: stacked_bar
    structure:
      dataset:
        type: stacked_bar
        dimensions: [1분기, 2분기, 3분기, 4분기]
        series:
          - { id: online,  label: 온라인,   type: bar, unit: 억원, values: [12, 15, 18, 22] }
          - { id: offline, label: 오프라인, type: bar, unit: 억원, values: [8, 7, 9, 10] }
  - type: list
    structure:
      entities:
        - { id: g1, label: 신규 시장 진출, items: [동남아 3개국] }
        - { id: g2, label: 반복 구매 증가, items: [구독 전환율 상승] }

8-1. 중첩 컴포넌트: 표현은 지금 열고 생성은 나중에#

왜 지금 정하나 중첩을 나중에 넣으려고 그때 스키마를 고치면, 그때까지 쌓인 Summary 가 전부 구버전이 되고 생성기, 검증기, 검색 모듈이 같이 흔들린다. 그래서 표현 가능성은 지금 열어 두고, 실제로 생성할지는 생성기 정책으로 정한다. 단계가 늘어도 version 은 그대로다.

어디에 매다나 entity.components[] 다. 원소(components[])끼리 중첩시키는 방법도 있지만 그러면 '부모의 어느 자리에 들어가는가'를 적을 수 없다. 내용 대치는 슬롯 단위라 위치가 필요하다(content-binding-contract.md). entity 는 곧 슬롯이므로 여기 매달면 위치가 따라온다.

entity 가 갖는 두 배열은 다른 것이다.

필드 무엇을 담나 예
children 같은 컴포넌트 안의 하위 entity. 의미 계층이다 조직도의 하위 부서, nested_scope 의 안쪽 범위
components 그 자리에 들어가는 다른 컴포넌트. 컴포넌트 경계를 넘는다 Steps 의 한 단계 칸 안에 들어간 big_number

단계 정책 스키마가 아니라 생성기와 capability 테이블이 진다.

시점 생성기 capability 테이블
지금(2·3단계) components 를 채우지 않는다. 깊이 0 고정 선언 없음. 들어와도 불변식 C 로 평탄화
이후 깊이 1 부터 켠다 컴포넌트별로 중첩 소화 능력을 선언한다

로드맵 1~5단계(README.md 1절)에는 이 축이 없다. 중첩을 켜는 시점은 5단계 뒤에 붙거나 별도 축으로 갈릴 수 있다.

소화 못 하면 어떻게 되나 불변식 C 다. 자식의 entities 를 부모 entity 의 items 로 편다.

yaml
# 생성된 것 (깊이 1)
- id: s1
  label: 계정 생성
  components:
    - type: big_number
      structure:
        entities:
          - { id: t, label: 소요 시간, value: 30, unit: 초 }

# 중첩을 못 받는 컴포넌트가 뽑혔을 때
- id: s1
  label: 계정 생성
  items: ["소요 시간 30초"]

9. YAML 예시#

단일 형태 하나(gauge)만 여기 둔다. 전체 모음(단일 8종과 다중 3종)은 yaml-examples.md 에 있다.

yaml
version: "0.4"
title: 3분기 목표 달성률
component: { type: gauge }
structure:
  entities:
    - { id: rate, label: 목표 달성률, value: 65, unit: "%" }   # floor 투영용 헤드라인
  dataset:
    type: gauge
    value: 65
    unit: "%"
    min: 0
    max: 100
    target: 80
    thresholds:
      - { at: 50, state: warn, label: 주의 }
      - { at: 80, state: safe, label: 목표선 }

confidence 에 대하여 예시에서는 component.confidence 를 쓰지 않기로 했다(생성기가 채우지 않는다). 스키마(2절)에는 optional 로 남겨 두었고, 스키마에서도 제거할지는 11절 열린 결정이다.

10. Smart Object 와 DataViz 정합 (참고)#

실물 조사는 smart-diagram-data-model.md 가 SSoT다. 이 절의 초판(2026-07-21 이전 상태 기준)에 있던 진술 셋이 실물과 어긋나 그 문서 11절에서 정정됐고, 이 절은 2026-08-02 에 아래처럼 요약과 포인터로 줄였다. 정정된 셋은 이렇다. SmartObjectItem 은 SmartDiagramItem 으로 이름이 바뀌었고(2026-07-06, 상속 관계 extends DataVizItem 은 그대로), 트리 시각화는 별도 TreeItem 이 아니라 hierarchical 이라는 정식 vizType 이며, 저장 contract 가 version: 2 로 바뀌어 rootComponent 와 slots 를 저장하지 않는다.

아래 대응표(SlotFill.source 3종과의 대응)는 재확인 결과 그대로 유효하다.

Visual Summary DataViz / Smart Object 대응
dataset.series[].values Matrix (2D 셀 배열) 동형
dataset.dimensions Matrix 축 + encoding.channel 대응
dataset.series[].axis (left/right) encoding.columnGroups[n].scale 대응
entity 슬롯(label, value) SlotFill source: 'data' 대응
파생 요약값(강등 시 합계) SlotFill source: 'derived' 대응
중첩 components[] 나 children SlotFill source: 'structure' (nested) 대응
entity.id 와 role 의미 semanticKey 대응

잠정 결론 structure 는 DataViz 의 Matrix 와 encoding, slot 모델과 구조적으로 어긋나지 않는다. dataset 을 DataViz props 로 넘기는 어댑터를 두면 스마트 다이어그램 렌더러를 그대로 재사용할 여지가 있다. 정합은 실제 어댑터로 실측해 확정한다. 다만 저장 contract 가 version: 2 로 바뀌었으므로 yaml-examples.md Appendix A 기준의 어댑터 설계는 다시 봐야 한다(TODO.md B-13).

tree 와 nested_scope 를 engine 의 어느 타입에 태울지는 TODO.md B-10 이다. tree 쪽은 hierarchical vizType 으로 답이 나왔고 nested_scope 쪽만 열려 있다(smart-diagram-data-model.md 11절).

11. 열린 결정#

목록의 SSoT 는 TODO.md B절이다. 한동안 이 절과 TODO 양쪽에 같은 목록이 중복돼 있었는데, 서로 어긋나기 시작해 2026-08-02 에 이 절을 포인터로 줄였다. 항목 번호(B-1 ~ B-16)와 상태는 그쪽에서 관리한다.

이 문서에서 각 결정의 맥락이 있는 자리만 적어 둔다. 장식과 계층 4건(B-1 ~ B-4)은 4-1절, 다중 래퍼(B-5)는 8절, 갭과 역방향 갭(B-7, B-8)은 3절과 4절, enum 확장(B-11)은 2절과 3절, Smart Object 정합(B-10, B-13)은 10절이다. 포맷(YAML / KDL)은 2026-08-01 YAML 초안 확정으로 닫혔다(6절).

12. 참고#

상위 이슈 MOR-1773 / 프로젝트 허브 README.md / 예시 yaml-examples.md / 원 스펙 v0.2 v0.3 / RLSC 실물 src/app/pipeline/common/type/layoutStructure.ts, packages/structured-layout/src/role-v2.ts / Smart Object miricanvas-web-2 의 packages/engine/editor/src/node/item/data-viz

↑ 목차로 · 9부 머리로

57장투영 모델 보존보존 전용 · v0.5 는 이 철학을 따르지 않는다

원문 archive/projection-model.md GitHub 에서 보기 8 KB 원문 제목 「투영 모델: Visual Summary 가 컴포넌트보다 추상적인 이유」

보존 문서다. v0.5 는 이 철학을 따르지 않는다. v0.2 와 v0.3 이 세운 설계 철학, 곧 "생성물은 표현형에 커밋하지 않는 추상 구조로 두고 표현형은 검색이 고른다"를 기록해 둔 것이다. v0.5 는 생성 단계에서 최적 semantic category 를 정한다(schema-v0.5.md 1절). 갱신하지 않는다.

무엇이 바뀌었나

v0.2 와 v0.3 (이 문서) v0.5
생성물이 표현형을 정하나 정하지 않는다. hint 는 참고값이다 정한다. type 이 그 값이다
표현형을 누가 고르나 검색이 capability 대조로 고른다 생성이 고르고, 검색은 가능한지 확인한다
못 그리면 검색이 다른 표현형을 고른다 fallback 순서를 따라간다 (capability-table.md 3절)

여기 있던 규칙 둘은 v0.5 로 옮겼다. relation 어휘를 늘리는 판정 기준은 schema-v0.5.md 2.6절, fallback 을 검색이 수행한다는 것은 capability-table.md 3절이다.

오너 정원석 (MORDOR) · 상태 보존 · 최종 갱신 2026-08-02


0. 한 문장#

Visual Summary 는 특정 표현형(컴포넌트)에 커밋하지 않는 추상 구조(structure)로 생성한다. 표현형 선택과 폴백은 생성이 아니라 검색 모듈이 capability 대조로 수행한다. 생성물은 최선의 표현형 하나(component.type)를 soft 로 명시한다. (2026-08-02 정원석 의도 확정)

1. 층위#

기본 단위를 혼동하지 않도록 고정한다.

VisualSummary 문서
└─ components[]           페이지 구성 단위. 컴포넌트 블록
   └─ structure           블록의 본체. 컴포넌트 무관 의미 구조
      ├─ entities[]       내용의 최소 단위 (label, items, value, children)
      ├─ relations[]      가산 정보. 항목 사이의 관계
      └─ dataset          가산 정보. 수치 데이터

entity 는 내용의 최소 단위이고, 페이지를 구성하는 단위는 컴포넌트 블록이다. v0.2 4절의 표현으로는 "모든 컴포넌트가 동일한 entities 기반 structure 를 공유하고, 나열형은 관계와 슬롯을 무시하는 가장 요구조건 낮은 소비자일 뿐이다."

2. 경위: 커밋형 스케치에서 추상층으로#

처음 스케치는 컴포넌트 커밋형이었고, v0.2 가 의도적으로 뒤집었다.

요구사항 원문(김수현)의 최초 스케치 둘은 둘 다 커밋형이다. 하나는 마크다운 role 확장(* venndiagram title > 전통, * venndiagram intersection desc > ...)이고, 다른 하나는 YAML 스케치(blocks: - component: venn_diagram 아래 sets: 와 intersections: 같은 컴포넌트 전용 필드)다. 같은 문서가 role 확장 방식에 대해 "사람이 보기엔 간단하지만 결국 문자열 파싱 규칙이 늘어나서, 컴포넌트가 많아질수록 깨지기 쉽다"고 경고하고, 임시 단계로만 쓰라고 적었다.

v0.2(강창석)가 2층 구조로 전환했다. 원문 그대로 "핵심: 컴포넌트 결정을 데이터에 박지 않는다. 벤이든 나열형이든 같은 structure 에서 투영(projection)한다." 선호 컴포넌트(당시 이름 hint, 지금 component)는 "이상적 컴포넌트(soft). 검색 모듈의 tie-breaker 로만 사용"이다.

3. 의도의 논리#

전제에서 결론까지 구멍 없이 적는다.

전제 1. 검색은 실패할 수 있다. 요구사항 원문의 7/16 회의록: "지금 걱정은 hit rate (검색 성공률)가 떨어질 것 같음. 일부 요소가 없는 것을 찾아서 병합, 정말 못 찾으면 enumeration type 으로 fallback 하는 전략." 실측이 이를 뒷받침한다. 색인 83,771건 중 venn 계열은 164건 이하, 데이터 차트는 각 51건 이하다(README.md 3절).

전제 2. 생성기는 색인 풀의 상태를 모른다. 어떤 컴포넌트가 몇 건 색인돼 있는지는 시점마다 바뀌고, 생성 시점의 LLM 입력에 없다.

전제 3. 어떤 입력이든 나열로는 항상 표현할 수 있다. entities 는 언제나 리스트로 읽힌다 (규약 1절 '어떤 구조든 리스트로 읽힌다').

결론 1. 생성물이 특정 표현형에 커밋하면(커밋형 스케치처럼 venn_diagram 전용 필드로 적으면), 그 표현형이 풀에 없을 때 대치가 실패한다. 전제 2 때문에 이 실패를 생성 시점에 피할 수 없다.

결론 2. 그러므로 생성물은 표현형 무관 구조로 두고, 표현형 선택은 색인 풀을 아는 검색 시점에 한다. 폴백도 같은 이유로 생성이 아니라 검색의 일이다. v0.2 4절이 이를 시연한다. 검색이 venn 대신 list 를 골라도 'structure 는 한 글자도 바꾸지 않고' 그대로 읽힌다.

이 구조에서 multiple representation 은 생성 시점이 아니라 검색 시점에 일어난다. 생성은 한 번이고, 같은 structure 가 어떤 표현형들로 투영 가능한지는 검색 모듈이 capability 대조로 판정한다. 상위 레벨의 별도 추상 산출물은 필요 없다.

4. 추상화의 상한: 왜 선호 컴포넌트를 남기는가#

추상화를 한 단계 더 올려 선호마저 없애면, 어떤 표현형이 가장 좋은지를 명시할 수 없다. 구조만으로는 최선의 표현형이 판정되지 않는 경우가 실제로 있다. v0.2 원문: "nested_scope 와 tree 는 요구조건(children 존재)이 동일하다. 구분은 hint.component 가 담당한다." 조직도(tree)와 포함 범위(nested_scope)는 구조가 같고 의도만 다르다.

그래서 설계는 두 층 사이에 선다. structure 는 컴포넌트 무관으로 두되, 최선의 표현형 하나를 component.type 으로 soft 하게 명시한다. (2026-08-02 정원석 확정)

선택적 확장 (2026-08-02 정원석). 생성 단계에서 최선 하나만이 아니라 선호 순서 목록을 추론해 적을 수 있게 스키마에 자리를 정의해 둔다. 필드는 optional 이고, 채울지는 생성기 정책으로 정한다. entity.components[] 와 같은 결정 구조다(표현은 지금 열고, 켜는 시점은 따로). 모양 제안은 8절에 있다.

5. component 의 세 역할#

원 설계와 2026-08-02 결정을 합치면 component 의 역할은 셋이다.

# 역할 근거
1 선호 표기. 검색 모듈의 tie-breaker v0.2 0절
2 구조 동형 카테고리의 구분자. 구조가 같을 때 의도를 가른다 (tree 와 nested_scope, 조직도와 들여쓴 리스트) v0.2 2절, 2026-08-02 계층 규칙
3 성립 조건의 예외적 담지자. pyramid 의 params.apex 는 참고값이 아니라 성립 조건 v0.2 3.6절, TODO.md G절 판별자 결정

역할 2 는 2026-08-02 의 계층과 장식 결정(선언 필드를 두지 않는다)과 맞물린다. '계층이 메시지의 본질인가'는 별도 필드가 아니라 component.type 이 Hierarchy 계열인지가 말한다.

6. 폴백의 소유와 카테고리 fallback 순서#

폴백은 검색이 소유하고, 두 장치로 수행한다.

첫째, capability 대조. 후보 컴포넌트가 이 structure 를 소화할 수 있는지를 색인 쪽 선언(capability 상수)과 대조한다.

둘째, 카테고리 fallback 순서. 원하는 카테고리의 후보가 없을 때 어느 카테고리로 물러날지의 매핑이다.

씨앗이 이미 원 스펙에 있다. v0.3 4절이 "gauge → big_number(value + unit)로 1차 fallback → 그마저 없으면 list" 라고 적었다. 7/24 회의의 '폴백(대치 가능) 카테고리 테이블'(TODO.md E-2, I-3)이 이것을 전 카테고리로 일반화하는 작업이다.

fallback 순서는 capability 상수 안에 카테고리별로 선언한다(예: Gauge → BigNumber → List, ComparisonTable → ListingTable → List).

계층과 장식도 같은 원리로 처리한다 (2026-08-02 확정, 선언 필드 없음).

# 규칙 담당
1 계층의 존재는 구조가 표현한다 entity.children(같은 컴포넌트 안), entity.components[](컴포넌트 경계를 넘는 중첩)
2 계층이 본질인지는 선언하지 않는다 component.type 이 담고, 평탄화 허용 비용은 카테고리 fallback 순서가 정한다
3 타이틀과 디스크립션 수준(N+α)은 구조에서 파생한다 entities 의 label 과 items 보유 여부
4 그림과 도형 장식(N+β)은 질의 쪽에 적지 않는다 입력에 대응물이 없다. 색인 쪽 변형 생성과 채점이 정한다

7. relation 어휘의 판정 기준#

기준 (2026-08-02 확정). 관계 어휘는 구조(배열 순서, entity 내부 필드, children)로 적을 수 없는 '제3의 내용'을 가진 관계만 만든다. type 과 구조에서 자명한 관계는 어휘로 만들지 않는다.

이 기준으로 후보를 전수 판정한 결과다. 비교 관계의 실물 증거는 둘이다. 스마트 다이어그램 versus 타입의 versus_center 슬롯(smart-diagram-data-model.md 5절)과 yaml-examples.md 1.8 의 desc '수작업에서 자동화로 전환' 이다.

관계 제3의 내용 판정
교집합 (intersects) 있다. 교집합 영역의 텍스트는 어느 entity 에도 속하지 않는다. 요구사항 원문의 venndiagram intersection desc > 가 핵심 사례였다 유지
비교 (compares) 있다. 비교되는 항목들 사이를 설명하는 텍스트(비교 기준, 전환 문구) 추가
전후 변화 경계선. ax 에 transformation_arrow 슬롯은 있으나, 화살표에 얹을 텍스트가 실사용 입력에서 관측되는지 미확인 보류. 관측되면 추가
문제와 해결, 원인, 결합 없다. entity 하나가 짝을 담거나(label 이 문제, items 가 해결), 허브형이라 type 에서 자명하다(결과 하나에 나머지 전부가 원인) 기각

비교 관계의 이름 (2026-08-02 확정, compares). 처음 후보는 contrasts(대비)였는데, 대비는 2항 대립의 뉘앙스가 있다. 비교는 3항 이상도 있으므로(2026-08-02 정원석 지적: "versus 가 항상 left, right 일까? 3개 비교는?") 항목 수에 중립적인 이름이 맞다. compares 로 정했고 규약 스키마에 들어갔다. of 는 비교되는 항목들(2개 이상), desc 는 그 사이를 설명하는 텍스트다. versus_left, versus_right 는 색인 쪽 렌더 슬롯 이름일 뿐이며 질의 스키마에 들어오지 않는다.

8. 선호 순서 목록의 스키마 모양#

4절의 선택적 확장을 담는 자리다. 컴포넌트 블록 원소에 optional 배열을 둔다. 2026-08-02 에 alternatives 로 규약에 들어갔다 (schema-v0.5.md 2절).

yaml
components:
  - type: Hierarchy.HierarchicalTree # 최선 1개
    alternatives: # 선택. 대신 쓸 것부터 선호 순서대로
      - ScopeCoverage.NestedScope
      - Enumeration.List
    structure: { ... }

생성기가 채우지 않으면 없는 것이고, 검색 모듈은 있으면 카테고리 fallback 순서보다 먼저 참고할 수 있다. 질의 쪽 선호는 입력 의도에서 나오고 색인 쪽 fallback 순서는 풀 상태에서 나온다. 서로를 대체하지 않는다.

9. semantic tag 와의 관계#

질의 쪽 semantic tag 는 Visual Summary 에서 뽑는 것이 아니다. 별도 모듈(Layout Description 추출 후 Taxonomy 분류 2단)이 만든다(pipeline-flow.md 1절). Visual Summary 의 component.type 은 선호 표기이고, 2026-08-02 의 {major, minor} 표기 통일 결정으로 두 산출물이 같은 v7 단어장을 쓴다.

10. 참고#

↑ 목차로 · 9부 머리로

색인

용어로 찾기

용어가 실제로 나오는 장만 골라 많이 나온 순으로 적었다. 숫자는 책 전체에서 센 횟수다.

파이프라인과 저장소

Search Signature 12회
10장1장49장3장55장
RLSC 360회
25장56장5장1장10장3장4장
comp-sign 309회
11장24장25장21장3장52장22장
comp-frame 56회
42장30장15장19장5장1장14장
I.I.S. 328회
25장22장3장1장12장5장11장
Garbage Collector 19회
12장1장15장42장
Eregion 39회
17장1장23장24장51장21장52장
aippt 48회
3장32장15장19장24장9장23장
iui.comp_frames 17회
42장1장15장25장
Smart Diagram 50회
5장37장3장1장4장6장33장
text capacity 16회
42장11장1장3장21장29장52장

대치와 투영

내용 대치 84회
13장3장17장51장15장2장10장
요소 대치 35회
13장55장50장1장4장
투영 (projection) 159회
3장11장25장22장1장56장5장
구조 대치 5회
26장31장
이종 컴포넌트 84회
26장3장4장45장50장54장11장
중첩 179회
4장3장50장26장56장54장5장
peel 33회
3장5장11장
평탄화 88회
26장3장4장50장54장5장13장
N1~N5 244회
5장50장3장11장54장4장39장
L1/L2/L3 269회
30장43장3장4장50장1장17장

Jira 티켓으로 찾기

본문에 나오는 티켓 69건이다. 마우스를 올리면 어느 장에 나오는지 보인다.

무엇을 기준으로 만들었나

원본 mordor-wiki projects/mordor/visual-summary, 커밋 a0f8b01961db9396206b1ddf097c66cb44bde137. 이 커밋의 폴더 트리 해시는 8044827c0855c57fc1be9782c64d817a44ee9a5a 이고, 2026-10-22 에 다시 받은 origin/main(61bfcfa)과 이 폴더만 놓고 보면 내용이 같다 — 그래서 요청한 커밋과 최신 main 이 여기서는 같은 글이다.

넣은 것 폴더 안 Markdown 57편 전량을 줄이지 않고 실었다. 표·코드·목록·각주까지 원문 그대로이고, 바꾼 것은 링크가 가리키는 자리뿐이다.

빼고 링크만 남긴 것 PDF 2편, 판정 CSV·HTML(`results-1000`), 다이어그램 PNG 10장과 .mmd 소스, 빌드 스크립트(report/build/, evaluation/scripts/ 의 .py·.sql), JSON 스키마 파일이다. 모두 본문에서 GitHub 링크로 간다.

링크 책 안으로 이은 것 1,050개, GitHub 원문 123개, 바깥 자료 534개다. 원문이 가리키지만 이 커밋에 없는 경로가 2개 있고(dashboard/index.html·dashboard/data.js) 없음 으로 표시했다.

고치지 않은 것 원문의 날짜·상태·수치·오타를 그대로 뒀다. 「최종 갱신 2026-08-22」 같은 표기는 그 문서의 것이지 이 책의 것이 아니다. 원문이 서로 어긋나는 곳도 맞추지 않았다 — 어느 쪽이 정본인지는 배지로 구분한다.

이 사전 안의 관련 문서 — RLSC 컴포넌트의 일생 · I.I. Structuriser · comp-frame 의 검색과 내용 대치 · MOR-2516 투영 규칙 · Title·Subtitle·Description 대치 · 표·차트 대치 · 결함 지도 · 입문 강의 · 퀴즈 50 · Component Signature