컴포넌트의 지문이다. 디자인 문서(sheetJson)와 RLSC 하나에서
구조 해시 · 텍스트 맵 · 채점 feature · visual tree 를 뽑아 두면,
사용자가 쓴 글도 같은 방식으로 해시해 등호 하나로 담을 컴포넌트를 찾을 수 있다.
검색·중복 판정·내용 대치가 모두 이 지문 위에 서 있다.
지금 3세대(sig3) 가 검증 세트에서 돌고 있고, 기본 세트는 아직 2세대다.
text_map · score_features · visual_tree 를 만들어 내는 DB 비의존 계산. iui.comp_signs* 에 적재해 검색 인덱스로 쓴다.H 로 정규화해 해시에 넣는다.comp_signs_v3_4_sig3 검증 세트에서 v38 → v50 까지 왔고, 기본 세트 교체는 judge 기준으로 v2 대비 검증한 뒤다.
코드·설계 사실은 miricanvas-iui 리포지터리를 직접 읽어 적었다 —
커밋 d5e7490f 기준이고 인용한 파일은 워킹트리와 같음을 확인했다.
조직·일정·의사결정 맥락은 Slack 논의 요약이 출처이고, 그런 항목은
Slack 으로 표시했다.
첨부된 두 링크는 이 문서 작성 시점에 열지 못했다 —
Confluence 보고서(/wiki/x/vIjMp)는 로그인 리다이렉트, PoC
(iui.miricanvas.com/comp-match)는 스크립트로 그려지는 앱이라 본문이 비어 온다.
그 둘의 내용은 리포지터리 안의 정본 문서
(docs/comp-sign-exact-match-plan.md)로 대체해 적었다.
목차
패키지 설명 한 줄이 정의다 — «Build component signatures (structure hash, text map, score features, visual tree) from a design document + RLSC. DB-free.» 컴포넌트 하나(디자인 오브젝트)에서 내용·색과 무관한 골격만 남긴 트리를 만들고, 그 트리를 문자열로 직렬화해 해시한다. 그 해시가 검색 인덱스 키다.
핵심은 양쪽이 같은 형식을 쓴다는 것이다. 컴포넌트는 RLSC 에서, 사용자 글은 markdown 요약에서 각각 트리를 만드는데 직렬화 함수는 하나(제네릭)라, 매칭이 «비슷한가» 가 아니라 «해시가 같은가» 가 된다.
compSignature
Slack 에 정리된 「compSignature 는 4개 필드」가 바로 이것이다. 실제 산출물
(CompSignature)에는 이 넷 외에 행 레벨 메타가 몇 개 더 붙는다 —
MORDOR 배치가 채우는 public.comp_signatures.signature jsonb 가 이 타입 그대로다.
| 필드 | 무엇 | 쓰이는 곳 |
|---|---|---|
structureHash | 시그니처 트리의 구조 해시. xxhash h64 → signed int64 10진 문자열 | 검색 인덱스 키. PK 구성 요소 (design_object_id, structure_hash) |
textMap | 시그니처 트리의 path → 텍스트 노드 맵. {id, chars, lines, charsPerLine, box, styleKey, fontSize, 정렬, role} | 내용 대치 시 텍스트를 넣을 자리 · 채점 입력 전부 |
scoreFeatures | 내용과 무관해 미리 계산 가능한 채점 feature(면적비 · 형제 구분 · 그림 패턴 · 페이지 크기) | 검색 때 doc 없이 채점 |
visualTree | 디자인 문서를 화면상 포함 관계로 구조화한 트리(node-labeler 저장본) | 용량 계산 · 형제 구분 감점 · 텍스트 가로 확장 |
| — 같은 행에 함께 저장되는 메타 — | ||
capLines · capChars | textMap 의 줄/글자 용량 합 | DB WHERE 용량 prefilter |
textNodeRoles | 모든 RLSC Text 노드의 {role, chars}(v36+) | rlsc 없이 내용 대치 재현 · placeholder 감점 |
fingerprint | 대치 텍스트 원문 합 해시(doc 비의존) | 후보 중복 제거(dedup) |
hasHeaderOrFooter | 머리/바닥글 role 포함 여부 | 검색 결과 필터 |
number 가 아니고 string 인가
64-bit 해시는 IEEE-754 double 의 안전 정수(253)를 넘어 정밀도를 잃는다.
10진 문자열로 두면 PostgreSQL int8 컬럼에 그대로 INSERT 되며 자동 cast 되고,
큰 정수 정밀도 손실도 없다.
노드 하나는 <자식수>(<자식1>)(<자식2>)…, leaf 는 0.
forest 는 가상 루트의 자식으로 본다. 여기에 3세대가 둘을 더한다 —
머리글 노드 앞의 H 마크, 그리고 표·차트 자리의 [table]/[chart] 토큰.
do_id 1717971 의 v49 라이브 해시로,
이 카드가 6벌 + 카테고리 Title 3개인 forest 전체의 값이다.
격자 토큰은 Table→[table], Chart→[chart] 로 갈라
문자 데이터 표 쿼리가 차트 컴포넌트로 흘러가지 않게 한다(§5).
콘텐츠 문서 생성 파이프라인에서 검색 쿼리를 기존 Content Signature 와 다른 구조로 바꿔야 할 필요가 생겼다.
거기서 「Component Search Signature」 개념이 처음 제안됐다.
MORDOR 스쿼드 안의 Search Signature 파트가 담당한다. Blockifier Pod 과 밀접하게 붙어 있다.
「Search Signature v2 만 구현해도 내용시각화에서 제기한 문제 중 중요한 것 대부분이 해결됨」이 실제로 확인됐다. Slack
덱 쪽 근거는 Visual Summary 규약 문서에 있다 — BAD 200건 분류에서 85% 가 매칭 문제였다.
「검색 후보를 넉넉히 가져온 뒤 FE 에서 랭킹한다」로 정리됐고, 그 모양이 지금 코드에 그대로 있다 —
서버는 해시·용량으로만 거칠게 거르고, 전수 채점·중복 제거·정렬은 클라이언트가 한다
(rankCompCandidates). 무거운 doc 은 상위 50개에만 지연 fetch 한다(§6 Phase A/C).
docs/comp-sign-exact-match-plan.md 의 「배경」이 정본이다. 요약하면,
해시가 담는 정보와 쿼리가 아는 정보가 어긋나 있었다.
role 도 스타일도 해시에 직접 안 들어간다. 스타일은 트리 변형(승급 tie-break · demoteStyleBreaks)을 통해 간접 반영될 뿐이다.
markdown 요약 트리에 폰트·크기가 있을 수 없다. 그러니 스타일이 만든 구조는 쿼리가 원리적으로 예측할 수 없다 — miss 의 원천이다.
거기에 스타일 tie-break 는 fontSize 반올림 ±1 노이즈에 뒤집히고 순서 의존적이었다.
cap_chars/cap_lines 의 >= 하나라, 한 노드가 크게 부족해도 총합만 맞으면 통과했다.
v3 첫 공유의 결론이 «개선이 거의 안 되었어요» 였다. 진단은 「다각형 배경」과 「tightBBox」로 개선한 visualTree 로 컴포넌트를 더 걸러내고 텍스트 영역을 확보해야 효과가 난다는 것.
그 진단이 코드에 실제로 반영됐다 — VT2(사각형 대신 실제 외곽 다각형으로 포함 판정하는 visual tree)가 후보 v46 · 캐노니컬 v40 으로 들어갔고, 텍스트 가로 확장도 VT2 를 쓴다(§7 연표).
comp-sign 패키지는 이름만 「시그니처」지만, 사실 컴포넌트 매칭 파이프라인 전체다.
네 단계가 같은 계산 코어(요약 트리 · 변형 · 용량 모델 · 채점)를 공유하므로
적재/검색/랭킹/대치의 결과가 서로 어긋나지 않는다. 그리고 DB 를 모른다 —
fetch·upsert 와 design_object_id·version·source 부여는 호출자 몫이다.
iui.comp_signs* 에 적재. xxhash-wasm init 을 1회만 하는 빌더를 재사용한다.text_node_roles(v36+)가 담아서, 대치 때 RLSC 를 다시 안 본다.comp-match 도 이 API 들을 그대로 소비한다 — 화면과 파이프라인이 다르게 계산하는 일을
구조적으로 막는다. 패키지명은 comp-sign 이지만 매칭 점수·node-labeler·text-metrics 의
공유 계산 코어까지 함께 담는다(시그니처 빌드가 그것들에 의존하기 때문).
| # | 주체 | 동작 | 이 라이브러리 |
|---|---|---|---|
| 1 | 클라이언트 | 사용자 글을 서버로 전송 | — (앱) |
| 2 | 서버 | 글을 markdown 요약으로 변환 | — (요약 서비스/LLM) |
| 3 | 서버 | markdown 을 클라이언트로 | — (전송) |
| 4 | 클라이언트 | markdown → text_tree | buildCompSearchData → textTree |
| 5 | 클라이언트 | 변형별 해시를 만들어 서버로 | buildCompSearchData → variants |
| 6 | 서버 | 해시로 comp_signs 조회 | — (where … in) |
| 7 | 서버 | 후보 선정 후 클라이언트로 (변형별 demand 로 용량 prefilter, 각 후보에 variant 부착) | — (fetch) |
| 8 | 클라이언트 | 후보 중 상위 n 선정 | rankCompCandidates |
| 9 | 클라이언트 | 상위 n 내용 대치 · 렌더 | buildContentReplacement |
buildCompSearchData(markdown) 한 번이 textTree(4)와 조회 variants(5)를
함께 낸다. markdown 을 한 번만 파싱하므로 8·9 에서 복원한 paths 가 5 에서 만든 해시와
정확히 맞물린다. 파싱이 두 번이면 그 사이에 미묘하게 어긋난다.
서버는 매칭된 각 행에 그 해시의 variant 를 실어 돌려줘야 한다 —
변형 정보 없이는 클라이언트가 후보를 채점하거나 대치할 문자열을 고를 수 없다.
그래서 «해시만 반환하는 축약형» 을 두지 않았다.
| 변형 | 무엇을 바꾸나 | 랭킹 |
|---|---|---|
| 기본 | 원본 트리 그대로 | — |
키:밸류 분리 transformed | "key: value" leaf 를 key/value 부모-자식으로 분해 | 가점(고정) |
제목 제외 unwrapped | 단일 root 트리의 최상위 제목을 벗긴다 | 감점(고정) |
자식 수 확장 expandedChildCount | 형제 그룹의 자식 수 불균형을 빈 노드로 채운다 | 감점 × 확장량 |
자식 병합 mergeStep | 자식을 단계적으로 묶는다(제목+본문 3줄 → 제목+본문 1덩어리) | 감점 × step(step+1)/2 |
격자 위치 gridIndex | 표·차트를 형제 내 다른 자리로 옮긴 트리(sig3 전용) | 후순위 stage |
표 승급 gridPromoted | 격자의 부모·형제를 버리고 격자만 승급 | 감점 10(기본값) |
변형은 정책 순서로 정렬돼 나온다 — 기본 → 키:밸류 → 제목 제외 → 자식 수 확장 → 병합(step 오름차순).
앞쪽이 원본에 가까우니 앞에서부터 채우면 검색 UI 의 stage 순서와 같아진다.
개수 상한은 DEFAULT_MAX_VARIANTS = 100 인데, 예제 입력 73개로 측정해 정한 값이다 —
중앙값 6개, 100 초과는 3개(346·162·162)뿐이라 거의 모든 입력은 온전히 통과하고
자식 확장·병합이 폭증하는 소수만 잘린다(잘리는 건 항상 저우선 tail).
2026-08 논의의 정본이 docs/comp-sign-exact-match-plan.md 다.
목표는 한 문장 — «후보가 줄어도 정확히 매치». 결정 넷이 그 방향을 정한다.
demoteStyleBreaks 의 스타일 브레이크 · 형제 Text 스타일 비교.
남는 것: «최고 role 이 유일하면 보스» · role 브레이크 · 컨테이너 승급·강급·평탄화.
결과로 시그니처가 «role + 포함 관계» 의 순수 함수가 된다.
H 를 전 노드에 마킹{Title, Subtitle, Hl} → H, Desc·컨테이너는 무마크.
literal 'Title' 마킹은 하지 않는다 — 실측에서 depth 1 내부 노드 role 이
Title 41% / Hl 22% / Subtitle 14% / Desc 22% 로 갈려, Title 등호 매칭은 코퍼스 절반 이상을 조용히 잃는다.
H 정규화가 그중 78% 를 수렴시킨다. 루트만 마킹하는 안은 기각 — 루트의 98.9% 가 Title 이라 변별력이 없다.
chars/lines 는 해시에 넣지 않는다cap_lines_arr/cap_chars_arr(int2[])을
RPC 에서 NOT EXISTS element-wise 로 early-exit 비교한다.
do_id 2054129Text:Subtitle/Text:Hl strip 변형이 이질 노드를 제거해
살아남을 수 있고, 전 변형이 탈락하면 그때 컴포넌트가 탈락한다. 실측 게이트 탈락률은
10.4%(사유: Desc 51 · Hl 2 · Title 1)로 fallback 임계 30% 를 크게 하회해 「버림」 정책을 유지했다.
| 지표 | 값 |
|---|---|
| 루트(depth 0)가 Title 인 행 | 98.9% |
| Title 이 depth≥1 에도 있는 행 | 11.1% |
| Title 여러 개인 행(카드류) | 38.5% |
| depth 1 내부 노드 role | Title 41 / Hl 22 / Subtitle 14 / Desc 22 % |
| depth≥1 leaf 노드 role | Desc ~97% |
| 시그니처 내 텍스트 role 어휘 | Title · Subtitle · Hl · Desc 4종뿐 |
| 후보 v38 샘플 실측 (520 컴포넌트) | 값 |
|---|---|
| (sanity) 현재 코드 캐노니컬 ∈ DB 적재본 | 100.0% |
| 구조 변화(후보 ∉ 적재본) | 23.7% |
| 게이트 탈락 | 10.4% |
| 탈락 건 중 구조도 변한 것 | 54/54 = 100% |
| 최대 해시 점유율 | 1(1(0)) 37.5% → 1(H1(0)) 36.7% |
| distinct 구조 수 | 94 → 90 |
마지막 줄이 중요하다 — 최대 해시(Title[Desc])는 H 마킹으로 갈라지지 않는다.
그건 본질적 쏠림이고, 결정 3(per-node 용량 벡터)이 담당할 몫임을 실측으로 재확인한 것이다.
TextItem 과 LayoutContainer 만 남긴다. 텍스트 role 은 화이트리스트 {Title, Subtitle, Desc, Hl} 만 통과(v39). 자식이 하나뿐인 컨테이너는 해제한다 — markdown 에 «래퍼» 개념이 없으니 대칭에 유리하다.demoteToSoleTitleForest — 보스 선출보다 먼저, 형제 중 role 이 Title 인 노드가 정확히 하나면 나머지를 그 Title 밑으로 내린다. 이게 없으면 섹션 안 카드들이 먼저 승급돼 Title role 이 된 뒤 섹션 레벨에서 Title 후보가 여럿으로 보여 평탄화된다(실물 do_id 1717971 — 3개 섹션의 카드 6장이 전부 평탄화됐다).findBossRoleOnly — 최고 role 이 유일하면 그 노드가 컨테이너를 대체하고 나머지 형제는 그 자식이 된다. 동률이면 평탄화. 단 Subtitle·Hl 이 보스일 때는 형제 중 맨 앞일 때만 승급한다(v49) — 뒤쪽 보스가 자기보다 앞선 무관한 형제까지 거슬러 감싸는 reading-order 역행 승급을 막는다. Title 은 위치 무관하게 항상 보스다(Hl 뱃지가 Title 앞에 오는 카드가 흔하다).groupSiblingsByRank — markdown 헤딩 스택과 같은 규칙. 직전 head 보다 낮은 rank 만 그 아래로, 같은 rank 는 형제 유지, 높은 rank 는 새 head. 비-leaf 경계마다 스택을 리셋한다. 구 demoteStyleBreaks 의 체인 중첩과 형제 재정렬을 대체한 단계다.checkSignatureStyleGate — 변형별로 통과/탈락. sig3 빌더는 탈락 변형을 아예 만들지 않는다(전 변형 탈락 시 컴포넌트 미적재).
표와 차트는 일반 트리 시그니처가 맞지 않는다 — 표는 셀 스타일 혼재로 게이트에서 97% 전멸하고,
차트는 텍스트 노드가 아예 없다(Chart 노드 + chartProps).
그런데 둘 다 «표 형태 데이터» 를 표현한다. 그래서 격자 차원을 시그니처에 넣었다.
| 규칙 | 내용 | 왜 |
|---|---|---|
| 트리에 남긴다 | Table/Chart 노드를 grid leaf 로 남기고 내부 셀 텍스트 서브트리는 제거 | 「제목+표」 쿼리가 제목+표 구조에 정확 매치되고 제목까지 대치된다. 구 flat 축은 표만 대치하고 제목을 버렸다 |
| doc 검증 | type='Chart'/'Table' 이어도 doc 에 chartProps·cellProps·dataVizProps 가 없으면 leaf 가 아니라 컨테이너로 풀린다 |
진짜 차트를 감싸는 RLSC 래퍼를 leaf 로 자르면 내부 텍스트·차트가 시그니처에서 사라지고 대치가 렌더러 모르는 id 에 글을 붙였다 |
| 격자 위치 순열 | 단일 grid leaf 를 형제 내 다른 자리로 옮긴 트리를 후순위 변형으로 낸다(GDD → DGD · DDG) | 형제 순서가 해시에 들어가는데 컴포넌트마다 표 위치가 제각각. 실측: 원 어순 0건 → 표-마지막 순열로 11건 |
| 표 승급 | 격자를 감싼 부모 노드를 지우고 격자만 그 자리로 올린다. 다른 변형과 조합 불가 | 「제목+본문 사이 표 하나」 컴포넌트를 표만 남은 컴포넌트와 매치시키려고. 대신 고정 감점 10 |
| 다중 격자 제외 | 격자가 2개 이상인 컴포넌트는 통째로 버린다 | 표 1개짜리 쿼리에 채우면 나머지 표가 placeholder 로 남는다. 반대로 다중 표 쿼리는 0건이 된다(§8) |
source — 어떤 풀에서 온 시그니처인가
두 축이 직교한다. 세트는 «어떤 design_object 풀» 인지, SIGNATURE_VERSION 은
«어떻게 계산했는지» 를 가른다. 그리고 세트 안에서 source 가 «어떤 선정 기준으로 들어왔는지» 를 라벨한다.
| 세트 | 테이블 | 비고 |
|---|---|---|
| v3.4 기본 | iui.comp_signs_v3_4 | 신규 재추출 풀. 현재 DEFAULT_SIGNATURE_SET — 아직 2세대 시그니처 |
| v3.4-sig3 검증 | iui.comp_signs_v3_4_sig3 | 풀은 v3.4 와 같고 시그니처만 3세대. H 마킹 해시 공간이 무마크와 겹칠 수 있어 같은 테이블 공존 안 함 |
| v3.2 | iui.comp_signs | 기존 풀 보존. 다이어그램 서브셋(source=1)은 여기에만 있다 |
source | 무엇 |
|---|---|
| 0 | 기본 풀(bundle 기준 선정) |
| 1 | 다이어그램 — v3.2 전용 |
| 2 · 3 | CHART · TABLE dedicated — 사람이 확정한 uuid CSV |
| 4 | 스마트블록 — smart_component_batch_results active |
| 5 · 6 | CHART · TABLE general — archive 태그 키매칭 |
| 7 · 8 | CHART · TABLE common — 위 둘의 후보가 겹치는 do_id |
common(7·8) 이 왜 생겼나 — PK 가 source 를 안 걸어서
dedicated 와 general 의 후보가 상당히 겹친다(실측: CHART 163개 중 111개, TABLE 158개 중 3개).
comp_signs_v3_4_sig3 의 PK 는 (design_object_id, structure_hash) 뿐이라
source 가 키에 없다 — 두 스크립트가 같은 do_id 에 각자 다른 source 로 upsert 하면
나중 실행이 앞을 조용히 덮어써 「표/차트 Pool 선택」이 무의미해졌다.
그래서 겹치는 do_id 는 새로 넣지 않고 공통 pool 로 source 만 옮긴다
(그래서 CSV 스크립트를 먼저 돌려야 한다).
rlsc_json 에 sb_gap·sb_padding·sb_direction 같은 자동 레이아웃 주석이 붙어 있다. 일반 structure_json 과 다르다.rlsc_json 을 복사하지 않고, 내용 대치 때 원본 테이블을 다시 조회해 postMessage 에 실어 보낸다.해시로 걸린 후보는 수천 건이다. 여기서 doc 을 안 읽고 전수 채점한 뒤, 상위 50개에만 무거운 doc 을 가져와 내용 대치한다. 이 두 겹이 v2 의 «후보를 넉넉히, 랭킹은 앞단에서» 결정이 코드로 굳은 모습이다.
text_map + score_features 만으로 채점 · 중복 제거 · 정렬text_map·score_features 에 넣어 뒀다.SIGNATURE_VERSION 재적재가 끝난 뒤에 새 채점을 배포해야 정확하다.rank ≥ 100 이라 정렬 맨 뒤.
| 항목 | 부호 | 무엇을 보나 |
|---|---|---|
| 스타일 불일치 | − | 같은 depth 텍스트끼리 스타일이 다르면 쌍마다 weight ÷ 공통조상까지 거리 |
| 부모-자식 동일 스타일 | − | 제목·본문은 달라야 자연스럽다 |
| 글꼴 크기 보너스 | + | 제목이 본문보다 크면 가점 |
| 줄 수 초과 | − | 노드 용량의 charsPerLine 폭으로 줄바꿈한 줄 수가 lines 를 넘을 때만 초과분에 감점. 합산 prefilter 가 못 잡는 노드별 부족을 여기서 본다 |
| 자식 확장 · 제목 제외 · 키:밸류 · 자식 병합 · 표 승급 | − / + | 변형별 고정·비례 가감점(§3 표) |
| 면적 비율 | − | 컴포넌트 면적 ÷ 페이지 면적. 임계는 트리 «면적»에 따라 동적 |
| 형제 구분 불가 | − | 시그니처상 비형제인데 화면(visual tree)상 형제인 노드 쌍 |
| 항목별 그림 | + | 같은 depth·다른 부모·비슷한 크기 leaf 그림 ≥2 패턴 |
| 제목 글꼴 | − | 최상위 제목 글꼴이 임계 미만일수록 |
| 대치 후 축소 | − | 글을 넣었을 때 예측 bbox 가 줄어든 폭·높이 |
| 임시 문구 | − | 입력에 매핑 안 된 텍스트 노드 수 × weight |
가중치는 UI 에서 조정하면 재검색 없이 다시 합산된다. 정렬 위에 절대 우선순위가 두 겹 있다 — 의미 태그 군(tier) 과 스마트블록 최상위 배치. 둘 다 «점수보다 위» 라서, 1군은 아무리 점수가 높아도 0군을 앞서지 못한다. (의미 태그 UI 는 현재 꺼져 있다 — §8)
comp_signs_v3_4_sig3 에만 적재하고 production 두 테이블은 무영향.
기본 세트 상수는 아직 'v3.4' — 주석에 «sig3 검증 완료 후 교체 예정».
SIGNATURE_VERSION_CANDIDATE| ver | 무엇이 바뀌었나 | 해시 |
|---|---|---|
| 38 | role-only 파이프라인 + H 마킹 + 스타일 자격 게이트 결정 1·2·4 구현. sig3 세트·용량 벡터 컬럼·per-hash RPC 신설(08-06) | 신규 |
| 39 | 노드 필터 3건 — ① 격자 leaf 를 [grid] 토큰으로 트리에 남김 ② 텍스트 role 화이트리스트 ③ 보존 텍스트 제외 v47 에서 철회 | 변경 |
| 40 | 격자 토큰 분리 — [table] / [chart] | 변경 |
| 41 | 격자 leaf 를 doc 검증으로 판정(래퍼는 leaf 아님) | 변경 |
| 42 | v41 마무리 — 검증 실패 래퍼의 type 을 Group 으로 중화(해시는 [chart] 인데 트리엔 래퍼가 남는 불일치 수정) | 변경 |
| 43 | text_node_roles 에서 표 셀 텍스트 노드 제외 — 표 하나당 −100 고정 감점이 붙던 문제 | 불변 |
| 44 | 유일 Title 형제 선강급(캐노니컬 v39 와 동일 규칙) | 변경 |
| 46 | visualTree 형제-only + VT2(외곽선 기반) 지원. 45 는 건너뜀 — 병렬 브랜치가 이미 45 로 191,049행을 적재해 뒀다 | 불변 visual_tree 변경 |
| 47 | v39 ③ 철회 — 보존 텍스트(한 글자·vs·TAM/SAM/SOM·숫자+단위) 노드를 시그니처에서 빼던 것을 되돌림. 매칭 자체를 막아 버렸다 | 변경 |
| 48 | 승급·H 마킹을 Title 하나로 축소(원래 이 브랜치에선 v44 였다) | 변경 |
| 49 | v48 되돌림 + 승급에 「보스는 맨 앞, Title 은 예외」 제약 추가 | 변경 |
| 50 | dataVizProps(차트 preset v2.0.0)를 격자 실체로 인식 — 실 데이터가 있는데 «래퍼» 로 오판돼 트리에서 사라졌다 | 변경 |
① 번호와 시간 순서가 어긋난다. 계획 문서의 절 제목은 «v44 — Title 하나로 축소(08-18)» · «v45 — 되돌림(08-19)» 인데, 병합 시점에 다른 브랜치가 44·46·47 을 먼저 차지해 코드 상수는 48 · 49 로 밀렸다. 내용·순서는 그대로다.
② 번호 재사용은 사고가 된다. 이미 적재된 행이 같은 번호를 갖고 있으면 버전 비교에서 «이미 최신» 으로 오판되어 그 변경이 반영 안 된 채 스킵된다 — 실제로 캐노니컬 v39/v44 재적재 때 겪은 문제다.
SIGNATURE_VERSION 은 40 에서 멈춰 있다| 구간 | 무엇 |
|---|---|
| v21 · v22 | 쓰레기 tightBoundingBox 무시 → 승급 로직 확정(2단계 승급 + 형제-보스 중첩 + 최상위 강급·평탄화). 구조 변환은 v22 이후 동결 — 그래서 「현재 코드 캐노니컬 ∈ DB 적재본」이 100%다 |
| v24 → v37 | 전부 용량 모델 보정 이력이다 — 고정 박스 → 세로 확장 박스 → 코퍼스 평균 글자폭 → RLSC 대비 보정 계수 → charsPerLine clamp → styleKey 압축 → text_node_roles 확장 → 추출 전용 여백 상수 분리. 구조 해시는 불변, text_map 만 계속 바뀌었다 |
| v38 | 컴포넌트 그룹화 5건 — 배치(위/아래 vs 좌/우) 일치 · 구분선은 멤버가 아니다 · 텍스트만으로 된 컴포넌트 허용 · 배경은 겹침으로 세지 않는다 · 구분선을 끊는 건 나중에 그려진 형제뿐 |
| v39 | 유일 Title 형제 선강급 — 구조 해시 변경, 재적재 |
| v40 현재 | visualTree 형제-only + VT2 지원. VT2 는 DB 를 못 보는 패키지 특성상 호출자가 런타임에 외곽선·tight box 를 계산해 넘겨야 한다(헤드리스 크롬 실측 — 문서당 평균 119ms, 178,095건 직렬 약 5.9시간) |
| 날짜 | 내용 | 코드 대응 |
|---|---|---|
| 08-12 | 「시그니처 v3 작업 중이라 거기에 통합해서 개선 가능」 | — |
| 08-13 | 개선 요구사항 정리(Confluence) | — |
| 08-20 | 표/차트 컴포넌트를 프로덕션과 동일하게 검색풀에 반영 + 위계(상하 위치) 안 맞는 컴포넌트는 매치 제외. 「표 승급」 옵션 신설(형제·부모 모두 버리고 승급, 10점 감점 조절 가능) | promoteGridLeaf 변형 + gridPromotedPenalty: 10 · 표/차트 Pool 라디오 토글 |
| 08-21 | 위계 불일치 컴포넌트 22건(CHART 9 · TABLE 13) 검색 대상 탈락 처리 | 이 22건을 집계하는 코드 지점은 특정하지 못했다 — 적재 스크립트가 찍는 「다중 격자 제외 · 중복 제외 · 게이트 전탈락」 집계가 그 계열이다 |
| 08-21 | 「시그니처를 설명 → 차트 순으로 만들면 좋지만, 지금은 RLSC 에 의존해 그렇게 만들 수 없다」 — 근본 한계 언급 | §8 이슈 ① |
fingerprint 로 중복 판정. 검색 — structureHash 등호 + 용량 하한.
랭킹·대치 — textMap·scoreFeatures·visualTree.
텍스트 노드 수용량을 합산만이 아니라 개별 비교로 정교화한 것(줄 수 초과 항목)이 이 축의 최근 개선이다.
결정 1 의 귀결이 그대로 한계다 — 시그니처가 «role + 포함 관계» 의 순수 함수가 되면서, 그 role 을 주는 RLSC 가 틀리면 시그니처도 틀린다. 「시그니처를 설명 → 차트 순서로 만들면 이상적이지만 지금은 RLSC 에 의존해 그렇게 만들 수 없다」가 이 얘기다.
스타일을 지렛대로 쓰던 2세대는 RLSC 가 틀려도 스타일이 일부를 보정했다. 3세대는 그 보정을 의도적으로 버렸다(예측 가능성과 맞바꿨다). RLSC 생성 여정 · IIS 가 같은 문제의 상류다.
최대 해시 Title[Desc] 하나가 풀의 37.5% 를 먹는다. H 마킹으로도 안 갈라지는 본질적 쏠림이라
결정 3(용량 벡터)이 담당해야 하는데, 뚱뚱한 해시(약 8.6만 행) 최악 0.5~2초가 예상된다
(LIMIT 도달 시 조기 종료). 계획 문서의 표현으로는 기존 타임아웃 문제(뚱뚱한 해시 40% · Postgres 57014)를 악화시킨다.
LIMIT early-exit 이 없어 전체 스캔이 된다(슬롯 과대 추정은 무해)PARALLEL SAFE 병렬 스캔, 또는 뚱뚱한 해시만 클라이언트 필터로 남기는 하이브리드벤치 실측이 아직 남아 있다 — 계획 문서의 「열린 문제」 첫 항목.
샘플 520 컴포넌트에서 10.4%. fallback 임계 30% 를 하회해 「버림」 을 유지했지만,
적재된 트리로는 정확한 측정이 불가능하다 — 구 demoteStyleBreaks 가 이질성을
구조 중첩으로 숨겨 놓았기 때문이다. 탈락률이 과하면 「버림」 대신 감점/후순위(tier)로 낮추는 안이 대기 중.
격자가 2개 이상인 컴포넌트는 제외한다(표 1개 쿼리에 채우면 나머지가 placeholder 로 남으니까).
그래서 쿼리 측이 표 2개짜리 트리(…([table])([table]))를 그대로 내도
풀에 대상이 없어 0건이 된다. 한때 컴포넌트 측도 결합 시그니처로 적재했으나 제외로 선회했다.
rlsc ↔ doc 불일치 17.6%
스냅샷 4,358건 중 82.4% 는 노드 id 구성이 design_objects.structure_json 과 완전히 같지만
(sb_* 주석만 추가), 17.6%(769건)는 노드 집합 자체가 다르다(그중 256건은 공유 노드의 type 도 다름).
시그니처는 rlsc_json 을 신뢰하고 doc 만 design_objects 에서 읽는데,
이 17.6% 는 대치 시 rlsc 가 가리키는 노드가 현재 doc 에 없을 수 있다는 뜻이다. 처리 방침 결정 보류 중.
군(tier) 3단계 검색 정책 · fillByTagQuota · GIN 인덱스 컬럼까지 다 있는데,
base 풀 백필이 막혀(bundle 기반 타겟 선정과 --tags-version 이 상호 배타)
컬럼이 전 풀에서 비어 있다. 그래서 UI 전체를 플래그 하나로 가렸다
(SEMANTIC_TAG_UI_ENABLED = false) — 기능을 뜯어내지 않고 남겨 뒀다.
배경에 사고가 하나 있다 — --tags-version 2.2 로 base 풀을 «태그 붙은 아무 design_object» 로 채운 적이 있는데,
그러면 전용 서브셋에 못 들어간 컴포넌트가 source=0 에 섞여 들어와
「표/차트 Pool 선택」이 무의미해진다(실물 do_id 6149525).
그래서 base 풀은 항상 --bundle-id 로 잡는다.
--fresh 는 서브셋까지 지운다
--fresh 는 세트 테이블을 통째로 TRUNCATE 하는데, 그 명령이 다시 채우는 건
base 풀(source=0)뿐이다. 표/차트·스마트블록 행은 별도 스크립트가 채우므로
직후 이어 돌리지 않으면 그 source 들이 0건으로 빈 채 남는다 —
게다가 재적재 자체는 에러 없이 «성공» 으로 끝나 눈치채기 어렵다.
실제로 한 스크립트가 런북에서 빠져 있어 general pool(5·6)이 며칠간 0건이었다.
tsx scripts/build-comp-signs.ts --set v3.4-sig3 --bundle-id <uuid> --fresh --vt2 db tsx scripts/add-tablechart-comps-from-csv.ts tsx scripts/add-keymatch-tablechart-comps.ts tsx scripts/import-smart-blocks.ts
승급·H 마킹을 Title 하나로 좁히면 정밀도가 오를 것 같았다. 표 컴포넌트 하나
(do_id 7511480)에서 Hl[Table] 연관이 사라지는 걸 «트레이드오프» 로 인지하고 진행했는데,
실제 검색 쿼리가 전혀 안 걸리는 걸 사용자가 발견했다.
원인은 쿼리 측과의 비대칭이다 — 쿼리는 지금도 markdown 헤딩 레벨을 전부 H 로 마킹해
Title(H)[Hl(H)[Desc, Desc]](중첩)를 내는데, 컴포넌트 측은 Hl 이 랭크에서 빠져
Title[Hl, Desc, Desc](flat)이 됐다. 해시가 어긋난다.
실측상 depth-1 노드의 Hl 22% + Subtitle 14% ≈ 36% 를 깨는 회귀였다.
되돌린 뒤 목적만 남겼다 — 「보스는 형제 중 맨 앞일 때만, Title 은 예외」.
Title 예외는 실측에서 나왔다(do_id 1717971 의 카드 6개 전부가
[Hl 뱃지, Title, Desc, Desc] 순서였다).
남는 규칙 — 한쪽만 엄격하게 만들면 대칭이 깨진다. 시그니처 변경은 쿼리 측 마킹 규칙과 함께 봐야 한다.
| 항목 | 상태 |
|---|---|
| 게이트 탈락률 실측(샘플 재빌드 후) | 대기 |
| 뚱뚱한 해시 벤치 → RPC / 하이브리드 결정 | 대기 |
H 마킹 하의 파생 변형별 head 전파 규칙 확정 | 대기 |
rank 그룹핑 세부 — 같은 rank head 가 연속될 때([Hl, Hl]) 경계 처리 | 대기 |
| 보스-위치 제약이 실 코퍼스에서 얼마나 걸리는지(v43 대비 해시 변경률) | 미측정 |
| 스마트블록 rlsc/doc 불일치 17.6% 처리 방침 | 보류 |
| iui VLM judge 기준 · 시그니처 v2/v3 점수 비교 Slack | 08-31 마감 |
SIGNATURE_VERSION 이 바뀌는 릴리스는 소비 측이 comp_signs 를 재적재해야 반영된다 —
패키지를 게시만 해서는 저장된 text_map 이 바뀌지 않는다.
그리고 채점은 재적재가 끝난 뒤 배포해야 한다(미재빌드 행은 중립값으로 빠진다).
줄 번호는 커밋 d5e7490f 기준이고, 인용 전 그 파일들의 git diff --stat HEAD 가 비어 있음을 확인했다.
| 무엇 | 어디 |
|---|---|
| 패키지 정본 문서(4단계 API 전문) | packages/comp-sign/README.md · AGENTS.md |
| 3세대 설계 정본(결정 1~4 · 실측 · 마이그레이션 · 열린 문제) | docs/comp-sign-exact-match-plan.md |
| 버전 연표(캐노니컬 · 후보 changelog 주석) | signatureTree.ts#L105 · #L240 |
| 트리 변환 본체 — 승급 · rank 그룹핑 · 선강급 | findBossRoleOnly#L1144 · groupSiblingsByRank#L1208 · demoteToSoleTitleForest#L731 |
| 스타일 자격 게이트 | checkSignatureStyleGate#L1362 |
| 구조 문자열 직렬화(무마크 · H 마킹) | buildStructureString#L1465 · buildMarkedStructureString#L1430 |
| 공개 API 네 개 | buildCompSignatures · buildCompSearchData · rankCompCandidates · buildContentReplacement |
| 검색 변형 · 표 승급 · 격자 위치 | buildSearchVariations#L1123 · promoteGridLeaf#L125 |
| 차트 적합성 판별 | isChartlikeTableCells#L198 |
| 채점 · 가중치(표 승급 감점 10) | computeScoreBreakdown#L443 · scoringWeights.ts#L65 |
| 세트 매핑 · 기본 세트 상수 | signatureSet.ts#L24 |
| 적재 배치 · 서브셋 스크립트 | build-comp-signs.ts · tablechart-archive-import.md |
| 검색 UI(Phase A/C · 채점 항목 전문) | src/comp-match/README.md |
| 컴포넌트 그룹화(반복 단위 찾기) 해설 | docs/component-grouping-lattice.md |
2세대는 스타일로 구조를 설명했다. 쿼리는 스타일을 모르니 그 구조를 예측할 수 없었고, 그게 miss 의 원천이었다. 3세대는 그 지렛대를 버리고 시그니처를 «role + 포함 관계» 의 순수 함수로 만든 뒤, 스타일을 «이건 채우면 이상해진다» 는 자격 게이트로 옮겼다. 대가는 RLSC 정확도에 그대로 종속된다는 것 — 그래서 다음 개선은 시그니처 안이 아니라 상류(RLSC·IIS)와 하류(용량 벡터·judge)에 있다.